虽然构造函数或对象字面量都可以用来创建单个对象,但这些方式有个明显的缺点:使用同一个接口创建很多对象会产生大量重复的代码。
一、工厂模式
function createPerson(name,age,job){
var o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayName = function () {
alert(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 () {
alert(this.name);
};
}
var person1 = new Person('Nicholas',29,'Software Engineer');
var person2 = new Person('Greg', 27, 'Doctor');
与工厂模式相比,没有显示的创建对象;直接将属性和方法赋给了this对象;没有return语句。
要创建Person的新实例,必须使用new操作符。以这种方式调用构造函数实际会经历以下四个步骤
1.创建一个新对象;
2.将构造函数的作用域赋给新对象(因此this就指向了这个新对象);
3.执行构造函数中的代码(为这个新对象添加属性);
4.返回新对象;
创建自定义构造函数意味着将来可以将它的实例标识为一种特定的类型;而这正是构造函数模式胜过工厂模式的地方
alert(person1 instanceof Object);//true
alert(person1 instanceof Person);//true
alert(person2 instanceof Object);//true
alert(person2 instanceof Person);//true
构造函数如果不通过new操作符调用,与普通函数并没有什么区别
//作为普通函数使用
Person('Mike', 27, 'teacher');
window.sayName();//Mike
//在另一个对象的作用域中调用
var o = new Object();
Person.call(o, 'Kristen', 28, 'Nurse');
o.sayName();//Kristen
构造函数也存在问题,最主要的就是每个方法在每个实例上都要重新创建一遍。为了解决这个问题,可以把函数定义转移到构造函数外部
function Person (name, age, job){
this.name = name ;
this.age = age ;
this.job = job ;
this.sayName=sayName;
}
function sayName () {
alert(this.name);
}
var person1 = new Person('Nicholas', 27, 'Software Engineer');
这样操作虽然解决了上面的问题,可是新问题是,在全局作用域中定义的函数实际上只能被某个对象调用,而且如果对象需要定义很多方法,那么就要定义很多个全局函数。
三、原型模式
创建的每个函数都有一个prototype(原型)属性,prototype是通过调用构造函数儿创建的那个对象实例的原型对象。我们可以不必在构造函数中定义对象实例的信息,而是直接将这些信息添加到原型对象中。
function Person () {}
Person.prototype.name = 'Nicholas' ;
Person.prototype.age = 27 ;
Person.prototype.job = 'Software Engineer' ;
Person.prototype.sayName = function () {
alert(this.name);
};
var person1 = new Person();
person1.sayName();//'Nicholas'
以上例子中,每添加一个属性和方法就要敲一遍Person.prototype。为减少不必要的输入,也为了从视觉上更好地封装原型的功能,更常见的做法是用一个包含所有属性和方法的对象字面量来重写整个原型对象。
function Person (){}
Person.prototype = {
constructor : Person,//将Person.prototype以对象字面量的形式创建为新对象时,相当于重写了prototype对象,其constructor属性会默认指向Object构造函数,所以需要设置回原值
name : 'Nicholas',
age : 29,
job : 'Software Engineer',
sayName : function(){
alert(this.name);
}
};
原型对象的问题:
首先,它省略了为构造函数传递初始化参数这一环节,结果所有实例在默认情况下都将取得相同的属性值。然后,原型中所有属性是被很多实例共享的,这种共享对于函数非常合适,对于那些包含基本值的属性也说的过去,但是对于包含引用类型值的属性来说,问题就比较严重了。
function Person (){}
Person.prototype = {
constructor : Person,
name : 'Nicholas',
age : 29,
job : 'Software Engineer',
friends : ['Shelby', 'Court'],
sayName : function () {
alert(this.name);
}
};
var person1 = new Person();
var person2 = new Person();
person1.friends.push('Van');
alert(person1.friends);//'Shelby,Court,Van'
alert(person2.friends);//'Shelby,Court,Van'
四、组合使用构造函数模式和原型模式
创建自定义类型的最常见方式,就是组合使用构造函数模式与原型模式。构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性
function Person(name,age,job){
this.name = name;
this.age =age;
this.job =job;
this.friends = ['Shelby', 'Court'];
}
Person.prototype = {
constructor : Person,
sayName : function () {
alert(this.name);
}
}
var person1 = new Person('Nicholas', 27, 'Software');
var person2 = new Person('Greg', 29, 'Doctor');
person1.friends.push('Van');
alert(person1.friends);//'Shelby,Court,Van'
alert(person2.friends);//'Shelby,Court'
这种构造函数与原型混成的模式是目前使用最广泛,认同度最高的一种创建自定义类型的方法。可以说,这是用来定义引用类型的一种默认模式。
五、动态原型模式
这个模式把所有信息都封装在了构造函数中,儿通过在构造函数中初始化原型(仅在必要的情况下),又保持了同时使用构造函数和原型的优点
function Person(name,age,job){
this.name=name;
this.age=age;
this.job=job;
if(typeof this.sayName != 'function'){
Person.prototype.sayName = function(){
alert(this.name);
};
}
}