虽然Object构造函数或对象字面量都可以用来创建单个对象,但这些方式有个明显的缺点:使用同一个接口创建很多对象,会产生大量的重复代码。为解决这个问题,人们开始使用工厂模式的一种变体。
一、工厂模式
工厂模式是软件工程领域一种广为人知的设计模式,这种模式抽象了创建具体对象的过程。如下所示:
function createPerson(name,age,job) {
var o=new Object();
o.name=name;
o.age=age;
o.job=job;
o.sayName=function () {
console.log(this.name);
};
return o;
}
var person1=createPerson("Nicholas",29,"Software Engineer");
var person2=createPerson("Greg",27,"Doctor");
工厂模式虽然解决了创建多个相似对象的问题,但却没有解决对象识别的问题(即怎样知道一个对象的类型)。
二、构造函数模式
可以使用构造函数的模式将前面的例子重写如下:
function Person(name,age,job) {
this.name=name;
this.age=age;
this.job=job;
this.sayName=function () {
console.log(this.name);
};
}
var person1=new Person("Nicholas",29,"Software Engineer");
var person2=new Person("Greg",27,"Doctor");
以上代码中的特点:
①没有显示的创建对象
②直接将属性和方法赋给了this对象
③没有return语句
要创建Person新实例,必须使用new操作符。以这种方式调用构造函数实际上会经历一下4个步骤:
①创建一个新对象
②将构造函数的作用域赋给新对象(因此this就指向了这个新对象)
③执行构造函数中的代码(为这个新对象添加属性)
④返回新对象
在上面的例子中,person1和person2分别保存着Person的一个不同实例。这两个对象都有一个constructor(构造函数)属性,该属性指向Person,如下所示:
console.log(person1.constructor==Person);
console.log(person2.constructor==Person);
对象的constructor属性最初使用来标识对象类型的。但是检查对象类型,还是instanceof操作符更可靠一些。
console.log(person1 instanceof Person);
console.log(person2 instanceof Person);
console.log(person1 instanceof Object);
console.log(person2 instanceof Object);
三、原型模式
我们创建的每个函数都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。那么prototype就可以理解为通过调用构造函数而创建的那个对象实例的原型对象。使用原型对象的好处是可以让所有对象实例共享它所包含的属性和方法。如下所示:
function Person() {
}
Person.prototype.name="Nicholas";
Person.prototype.age=29;
Person.prototype.job="Software Engineer";
Person.prototype.sayName=function () {
console.log(this.name);
};
var person1=new Person();
person1.sayName();
var person2=new Person();
person2.sayName();
无论什么时候,只要创建了一个新函数,就会根据一组特定的规则为该函数创建一个prototype属性,这个属性指向函数的原型对象。在默认情况下,所有原型对象都会自动获得一个constructor(构造函数)属性,这个属性包含一个指向prototype属性所在的函数的指针。就拿前面的例子来说,Person.prototype.constructor指向Person。而通过这个构造函数,还可以继续为原型对象添加其他属性和方法。
创建了自定义的构造函数之后,其原型对象默认只会取得constructor属性,至于其他方法,则都是从Object继承而来的。当调用构造函数创建一个新实例后,该实例的内部将包含一个指针(内部属性),指向构造函数的原型对象。在firefox、safari和chrome在每个对象上都支持一个属性__proto__,而在其他实现中,这个属性对脚本则是完全不可见的。这个连接存在于实例与构造函数的原型对象之间。
四、组合使用构造函数模式和原型模式
创建自定义类型的最常见方式,就是组合使用构造函数模式与原型模式。构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性。结果,每个实例都会有自己的一份实例属性的副本,但同时有共享着对方法的引用,最大限度的节省了内存。另外,这种混成模式还支持向构造函数传递参数。
function Person(name,age,job) {
this.name=name;
this.age=age;
this.job=job;
this.friends=["shellby","court"];
}
Person.prototype={
constructor:Person,
sayName:function () {
console.log(this.name);
}
}
var person1=new Person("Nicholas",29,"Software Engineer");
var person2=new Person("Greg",27,"Doctor");
person1.friends.push("val");
console.log(person1.friends);
console.log(person2.friends);
console.log(person1.friends===person2.friends);
console.log(person1.sayName===person2.sayName);
五、动态原型模式
动态原型模式把所有信息都封装在了构造函数中,而通过在构造函数中初始化原型(仅在必要的情况下),又保持了同时使用构造函数和原型的优点。换句话说,可以通过检查某个应该存在的方法是否有效,来决定是否需要初始化原型。
function Person(name,age,job) {
this.name=name;
this.age=age;
this.job=job;
if(typeof this.sayName!="function"){
console.log("sayName不存在");
Person.prototype.sayName=function () {
console.log(this.name);
};
}
}
var friend=new Person("Nicholas",29,"Software Engineer");
friend.sayName();
六、寄生构造函数模式
寄生构造函数模式的基本思想是创建一个函数,该函数的作用仅仅是封装创建对象的代码,然后再返回新创建的对象;但从表面上看,这个函数有很像是典型的构造函数。
七、稳妥构造函数模式