创建对象
7种模式:工厂模式、构造函数模式、原型模式、组合使用构造函数模式和原型模式、动态原型模式、寄生构造函数模式、稳妥构造函数模式
1、工厂模式:
1 function createPerson(name, age, job){ 2 var o = new Object(); 3 o.name = name; 4 o.age = age; 5 o.job = job; 6 o.sayName = function(){ 7 alert(this.name); 8 }; 9 return o; 10 } 11 12 var person1 = createPerson("Nicholas",29,"Software Engineer"); 13 var person2 = createPerson("Greg",27,"Doctor");
工厂模式虽然解决了创建多个相似对象的问题,但却没有解决对象识别的问题(即怎样知道一个对象的类型)
2、构造函数模式:
1 function Person(name, age, job){ 2 this.name = name; 3 this.age = age; 4 this.job = job; 5 this.sayName = function(){ 6 alert(this.name); 7 }; 8 } 9 10 var person1 = new Person("Nicholas", 29, "Software Engineer"); 11 var person2 = new Person("Greg", 27, "Doctor");
此模式没有显示地创建对象,直接将属性和方法赋给了this对象,且没有return语句。
要创建Person的新实例,必须使用new操作符。以这种方式调用构造函数实际上会经历以下4个步骤:
(1)创建一个新对象;
(2)将构造函数的作用于赋给新对象(因此this就指向了这个新对象);
(3)执行构造函数中的代码(为这个新对象添加属性);
(4)返回新对象。
在例子中,person1和person2分别保存着Preson的一个不同的实例。这两个对象都有一个constructor(构造函数)属性,该属性指向Person,如下所示。
1 alert(person1.constructor == Person); //true 2 alert(person2.constructor == Person); //true
对象的constructor属性最初用来标识对象类型。但是,提到检测对象类型,还是instanceof操作符更可靠一些。这个例子中创建的所有对象既是Object的实例,同时也是Person的实例,这一点可以通过instanceof操作符可以得到验证。
1 alert(person1 instanceof Object); //true 2 alert(person1 instanceof Person); //true 3 alert(person2 instanceof Object); //true 4 alert(person2 instanceof Person); //true
使用构造函数的主要问题,就是每个方法都要在每个实例上重新创建一遍,每定义一个函数,也就是实例化了一个对象。以这种方式创建函数,会导致不同的作用域链和标识符解析。
3、原型模式:
1 function Person(){ 2 } 3 4 Person.prototype.name = "Nicholas"; 5 Person.prototype.age = 29; 6 Person.prototype.job = "Software Engineer"; 7 Person.prototype.sayName = function () { 8 alert(this.name); 9 }; 10 11 var person1 = new Person(); 12 person1.sayName(); //"Nicholas" 13 14 var person2 = new Person(); 15 person2.sayName(); //"Nicholas" 16 17 alert(person1.sayName == person2.sayName); //true
我们创建的每个函数都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。使用原型对象的好处是可以让所有对象实例共享它所包含的属性和方法。换句话说,不必在构造函数中定义对象实例的信息,而是将这些信息添加到原型对象中。
在此,Person.prototype指向了原型对象,而Person.prototype.constructor又指回了Person。
1 alert(Person.prototype.isPrototypeOf(person1)); //true 2 alert(Person.prototype.isPrototypeOf(person2)); //true
这里,我们用原型对象的isPrototypeOf()方法测试了person1和person2.因为它们内部都有一个指向Person.prototype的指针,因此都返回了true。
ECMAScript5增加了一个新方法,叫Object.getPrototypeOf(),在所有支持的实现中,这个方法返回[[Prototype]]的值。例如:
1 alert(Object.getPrototypeOf(person1) == Person.prototype); //true 2 alert(Object.getPrototypeOf(person1).name); //"Nicholas"
原型模式省略了为构造函数传递初始化参数这一环节,结果所有实例在默认情况下都将取得相同的属性值。如:
1 function Person(){ 2 } 3 4 Person.prototype.name = "Nicholas"; 5 Person.prototype.age = 29; 6 Person.prototype.job = "Software Engineer"; 7 Person.prototype.friends = ["Shelby","Court"]; 8 Person.prototype.sayName = function () { 9 alert(this.name); 10 }; 11 12 var person1 = new Person(); 13 var person2 = new Person(); 14 15 person1.friends.push("Van"); 16 17 alert(person1.friends); //"Shelby,Court,Van" 18 alert(person2.friends); //"Shelby,Court,Van" 19 alert(person1.friends === person2.friends); //true
4、组合使用构造函数模式和原型模式:
创建自定义类型的最常见方式,就是组合使用构造函数模式与原型模式。构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性。结果,每个实例都会有自己的一份实例属性的副本,但同时又共享着对方法的引用,最大限度地节省了内存。另外,这种混成模式还支持向构造函数传递参数;可谓是集两种模式之长。
1 function Person(name,age,job) { 2 this.name = name; 3 this.age = age; 4 this.job = job; 5 this.friends = ["Shelby","Court"]; 6 } 7 8 Person.prototype = { 9 constructor:Person, 10 sayName:function () { 11 alert(this.name); 12 } 13 } 14 15 var person1 = new Person("Nicholas",29,"Software Engineer"); 16 var person2 = new Person("Greg",27,"Doctor"); 17 18 person1.friends.push("Van"); 19 alert(person1.friends); //"Shelby,Court,Van" 20 alert(person2.friends); //"Shelby,Court" 21 alert(person1.friends === person2.friends); //false 22 alert(person1.sayName === person2.sayName); //true
这种构造函数与原型混成的模式,是目前在ECMAScript中使用最广泛、认同度最高的一种创建自定义类型的方法。
5、动态原型模式:
1 function Person(name,age,job) { 2 //属性 3 this.name = name; 4 this.age = age; 5 this.job = job; 6 7 //方法 8 if(typeof this.sayName != "function"){ 9 Person.prototype.sayName = function () { 10 alert(this.name); 11 }; 12 } 13 } 14 15 var friend = new Person("Nicholas",29,"Software Engineer"); 16 friend.sayName();
注意构造函数代码中8到12行的部分,这里只在sayName()方法不存在的情况下,才会将它添加到原型中。这段代码只会在初次调用构造函数时才会执行。此后,原型已经完成初始化,不需要再做什么修改了。
6、寄生构造函数模式:
通常,在前述的几种模式都不适用的情况下,可以使用寄生(parasitic)构造函数模式。这种模式的基本思想是创建一个函数,该函数的作用仅仅是封装创建对象的代码,然后再返回新创建的对象。
1 function Person(name,age,job) { 2 var o = new Object(); 3 o.name = name; 4 o.age = age; 5 o.job = job; 6 o.sayName = function () { 7 alert(this.name); 8 }; 9 return o; 10 } 11 12 var friend = new Person("Nicholas",29,"Software Engineer"); 13 friend.sayName(); //"Nicholas"
除了使用new操作符并把使用的包装函数叫做构造函数之外,这个模式跟工厂模式其实是一模一样的。构造函数在不返回值的情况下,默认会返回新对象实例。而通过在构造函数的 末尾添加一个return语句,可以重写调用构造函数时返回的值。
关于寄生构造函数模式,有一点需要说明:首先,返回的对象与构造函数或者与构造函数的原型属性之间没有关系;也就是说,构造函数返回的对象与在构造函数外部创建的对象没有什么不同。为此,不能依赖instanceof操作符来确定对象类型。由于存在上述问题,建议在可以使用其他模式的情况下,不要使用这种模式。
7、稳妥构造函数模式:
稳妥对象,指的是没有公共属性,而且其方法也不引用this的对象。稳妥对象最适合在一些安全的环境中(这些环境中会禁止使用this和new),或者在防止数据被其他应用程序改动时使用。
稳妥构造函数遵循寄生构造函数类似的模式,但有两点不同:一是新创建对象的实例方法不引用this;二是不使用new操作符调用构造函数。
按照稳妥构造函数的要求,可以将前面的Person构造函数重写如下:
1 function Person(name,age,job) { 2 //创建要返回的对象 3 var o = new Object(); 4 5 //可以在这里定义私有变量和函数 6 7 //添加方法 8 o.sayName = function () { 9 alert(name); 10 }; 11 12 //返回对象 13 return o; 14 }
注意,在以这种模式创建的对象中,除了使用sayName()方法之外,没有其他办法访问name的值。可以像下面使用稳妥的Person构造函数:
1 var friend = new Person("Nicholas",29,"Software Engineer"); 2 friend.sayName(); //"Nicholas"
这样,变量friend中保存的是一个稳妥对象,而除了调用sayName()方法外,没有别的方式可以访问其数据成员。即使有其他代码会给这个对象添加方法或数据成员,但也不可能有别的办法访问传入到构造函数中的原始数据。稳妥构造函数模式提供的这种安全性,使得它非常适合在某些安全执行环境下使用。