一、使用Object构造函数或对象字面量创建对象
var person = new Object()
person.name = 'adhehe';
person.age = 23;
//...
//或
var person = {
name:'adhehe',
age:23,
//...
}
缺点:使用同一个接口创建很多的对象,会产生大量的重复代码。
二、工厂模式
这种模式抽象了创建具体对象的过程。用函数来封装以特定接口创建对象的细节。
function createPerson(name,age){
//将创建的具体细节封装起来,最后返回一个对象 var o = new Object(); o.name = name; o.age = age; o.getName = function(){ return this.name; } return o; } var person1 = createPerson('adhehe',23); var person2 = createPerson('Jhone',45);
缺点:虽然解决了创建多个相似对象的问题,但没有解决 对象识别的问题,即怎么知道一个对象的类型
三、构造函数模式
构造函数可以用来创建特定类型的对象。如Object、Array等。也可以创建自定义构造函数。
function Person(name,age){ this.name = name; this.age = age; this.getName = function(){ return this.name; } } var person1 = new Person('adhehe',23); var person2 = new Person('Jhone',45);
以new关键字创建Person的实例,经历以下4个步骤:
- 创建一个新对象
- 将构造函数的作用域赋给了新对象(即this指向了这个新对象)
- 执行 构造函数中的代码(即为新对象添加属性)
- 返回新对象
instanceof 操作符 可以检测对象的类型
//以person为例 console.log(person1 instanceof Person) console.log(person2 instanceof Person) console.log(person1 instanceof Object) console.log(person2 instanceof Object) //都输出 true
构造函数与普通函数的唯一区别是:调用的方式不同。只要通过new操作符调用,它就是构造函数。
缺点:每个方法都要在每个实例上重新创建一遍。
//以上面 person为例 person1.getName == preson2.getName //false
四、原型模式
创建的每个函数都有一个prototype(原型)属性,它是一个指针,批向一个对象,而这个对象的用途就是:包含可以共享的属性和方法,给那些特定类型的所有实例。
function Person(){} Person.prototype.name = 'adhehe' Person.prototype.age = 23 Person.prototype.getName = function(){ return this.name; } var preson1 = new Person() var preson2 = new Person() console.log(person1.getName()) console.log(person2.getName()) //都返回 adhehe person1.getName == person2.getName //true
1、理解原型对象
只要创建了函数,就会为该函数创建一个prototype属性,这个属性指向函数的原型对象。
所有原型对象都会自动获得一个constructor属性(构造函数),这个属性指向prototype属性所在的函数的指针。
当调用构造函数创建一个实例后,实例内部将包含一个指针(__ptoto__),指向构造函数的原型对象。
Person.prototype.constructor == Person
person1.constructor == Person
person2.constructor == Person
person1.__proto__ == Person.prototypeperson2.__proto__ == Person.prototype
isPrototypeOf()方法 确定对象之间是否存在原型关系
Person.prototype.ifPrototypeOf(person1) //true Person.prototype.ifPrototypeOf(person2) //true
Object.getPrototypeOf()方法 是ECMAScript5新增的检测方法
Object.getPrototypeOf(person1) == Person.prototype //true Object.getPrototypeOf(person2) == Person.prototype //true
每当读取某个对象中的某个属性时,首先搜索该对象本身,再搜索该对象指向的原型对象。如果在实例对象中某个属性名与原型中的一个属性同名,该属性将会屏蔽原型中的属性。
function Person(){} Person.prototype.same = 1; var person1 = new Person() var person2 = new Person()
person1.same = 2; console.log(person1.same) //2 console.log(person2.same) //1
使用delete操作符 可以完全删除实例属性,从而能够重新访问原型中的属性。
//接上例 delete person1.same; console.log(person1.same) //1
hasOwnProperty()方法可以检测一个属性是存在于实例中,还是存在于原型中。
person1.hasOwnProperty('name') // true person1.hasOwnProperty('hello') //false
2、in 操作符
有两种方式使用:
- 单独使用
- for-in
单独使用:通过对象能够访问指定属性时返回true,无论该属性存在于实例还是原型中。
"name" in person1 //true
同时使用 hasOwnProperty()方法和 in 操作符,就可以确认该属性到底是存在于对象中,还是存在于原型中。
function has(obj,name){ return !obj.hasOwnProperty(name) && (name in obj); } //in为true,hasOwnProperty()返回false,就可以确定属性是在原型中
for-in 循环:返回所有能够通过对象访问的、可枚举的属性,其中既包括存在于实例中的属性,也包括存在于原型中的属性。屏蔽了原型中不可枚举的属性的实例属性也会在for-in循环中返回。
Object.keys()方法 可以取得对象上所有可枚举的实例属性。
function Person(){} Person.prototype.name = 'adhehe' Person.prototype.age = 23 Person.prototype.getName = function(){ return this.name; } var keys = Object.keys(Person.prototype) console.log(keys) // "name,age,getName" var person1 = new Person() person1.name = 'Rob' var person1Keys = Object.keys(person1) console.log(person1Keys) //"name"
Object.getOwnPropertyNames()方法 可以得到所有实例属性,无论它是否可枚举。
var keys = Object.getOwnPropertyNames(Person.prototype); console.log(keys) //"constructor,name,age,getName"
3、更简单的原型语法
function Person(){} Person.prototype = { name:'adhehe', age:23, getName:function(){ return this.name } }
在这里,constructor属性不再指向Person。可以 将constructor属性值设置回适当的值
function Person(){} Person.prototype = { constructor:Person, name:'adhehe', age:23, getName:function(){ return this.name } }
以上面这种方式重设constructor属性会导致它的Enumerable特性被设置为true。默认情况下原生的constructor是不可枚举的。可以使用Object.defineProperty()修改
function Person(){} Person.prototype = { name:'adhehe', age:23, getName:function(){ return this.name } } Object.defineProperty(Person.prototype,'constructor',{ enumberable:false, value:Person })
4、原型的动态性
对原型对象的修改都能够立即 从实例上反映出来。可以随时为原型添加属性和方法。
var o = new Person(); Person.prototype.sayHi = function(){ //... } o.sayHi() //没有问题
但是,如果重写原型对象,则会切断 构造函数与最初原型之间的联系。实例中的指针仅指向原型,而不指向构造函数。
function Person(){} var o = new Person(); Person.prototype = { constructor:Person, name:'adhehe', age:23, getName:function(){ //.... } } o.getName() // 报错
5、原生对象的原型
所有原生的引用类型,都在其构造函数的原型上定义了方法。
我们也可以在原生对象的原型上定义新的方法或修改原型,但不推荐这么做。
缺点:
- 省略了为构造函数传递初始化参数的环节,导致所有的实例在默认情况下都将取到相同的属性值
- 由于原型的共享本性的原因,在其中一个实例对象上添加或修改了原型属性的值,其他实例对象都会被修改
function Person(){} Person.prototype = { constructor:Person, name:'adhehe', age:23, friends:['a','b'], getName:function(){ //... } } var o1 = new Person() var o2 = new Person() o1.friends.push('c') console.log(o1.friends) //a,b,c console.log(o2.friends) //a,b,c //两个对象实例的friends返回了相同的值
五、构造函数模式 与 原型模式 组合
- 构造函数模式用于定义实例属性
- 原型模式用于定义方法和共享的属性
function Person(name,age){ this.name = name; this.age = age; this.friends = ['a','b'] } Person.prototype = { constructor:Person, getName:function(){ //... } } var o1 = new Person('adhehe',23); var o2 = new Person('Greg',45); o1.friends.push('c'); console.log(o1.friends) //a,b,c console.log(o2.friends) //a,b console.log(o1.friends === o2.friends) //false console.log(o1.getName === o2.getName) //true
六、动态原型模式
在构造函数中初始化原型(仅在必要的情况下),它保持了同时使用构造函数和原型的优点。
它只会在初次调用构造函数时才会执行,此后,原型已经完成初始化,不需要再做什么修改。
使用动态原型模式时,不能使用对象字面量重写原型。会切断现有实例与新原型之间的联系。
function Person(name,age){ this.name = name; this.age = age; if(typeof this.getName != 'function'){ Person.prototype.getName = function(){ //... } } } var o = new Person('adhehe',23) o.getName()
七、寄生构造函数模式
创建一个函数,该函数的作用仅仅是封装创建对象的代码,然后再返回新创建的对象。
这个模式与工厂模式是一模一样的,不同的是这里使用 new 操作符创建实例对象,并把包装函数叫作构造函数。
function Person(name,age){ var o = new Object(); o.name = name; o.age = age; o.getName = function(){ //... } return o; } var person1 = new Person('adhehe',23) person1.getName();
缺点:返回的对象与构造函数或构造函数的原型属性之间没有任何关系,所以不能依赖 instanceof 操作符来确定对象类型。在有其他模式可用的情况下,不推荐使用这种模式
八、稳妥构造函数模式
稳妥对象:指的是没有公共属性,其方法也不引用 this 的对象。
稳妥对象 最适合在一些安全的环境中,或者在防止数据被其他应用程序改动时使用。
此模式 与 寄生构造函数类似的模式,有两点不同:
- 新创建对象的实例方法不引用 this
- 不使用 new 操作符调用构造函数
function Person(name,age){ var o = new Object(); //在这里定义私有变量与属性 o.getName = function(){ return name; } return o; } var person1 = Person('adhehe',23) person1.getName() //adhehe //在这种模式创建的对象中,除了使用getName()方法之外,没有其他办法可以访问name
缺点:与寄生构造函数模式类似,返回的对象与构造函数或构造函数的原型属性之间没有任何关系,所以不能依赖 instanceof 操作符来确定对象类型。