常规创建对象的不足
使用Object构造函数或对象字面量都可以用来创建单个对象,但是这些方式有个明显的缺点:使用同一个接口创建很多对象,会产生大量重复代码。
为了解决这个问题,可以使用以下的模式进行创建对象:
1、工厂模式
工厂模式是用函数来封装特定接口创建对象的细节
代码示例:
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");
这种模式可以接受参数,构建包含所有必要信息的对象。可以无数次地调用这个函数,而每次它都返回一个新的相似对象。
缺点:
无法解决对象识别的问题。
2、构造函数模式
它是使用构造函数来创建特定类型的对象。
下面我们改写工厂模式的示例:
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");
构造函数模式与工厂模式不同之处:
a)没有显式地创建对象
b)直接将属性和方法赋给了this对象
c)没有return 语句
注意:按照惯例,构造函数都应该以一个大写字母开头,而非构造函数则应该以小写字母开头。在js中,构造函数本质上也是个函数。
使用构造函数模式来创建实例,必须要使用new操作符。以这种方式调用构造函数实际上会发生以下4个步骤:
a) 创建一个新对象
b) 将构造函数的作用域赋给新对象(因此this就指向了这个新对象)
c)执行构造函数中的代码(为这个新对象添加属性)
d)返回新对象
同一个构造函数生成的不同实例,它们的constructor属性,指向都一样。
alert(person1.constructor === Person); //true
alert(person2.constructor === Person); //true
构造函数模式
优点:解决了工厂模式对象无法识别的问题。
缺点:每个方法都要在每个实例上重新创建一遍。每次创建实例都会创建所有的方法。以下代码可以证明这一点
alert(person1.sayName === person2.sayName); //false
3、原型模式
每个函数都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。
使用原型对象的好处是可以让所有对象实例共享它包含的属性和方法。
原型模式不是在构造函数中定义对象实例的信息,而是可以将这些信息直接添加到原型对象中。
把上面例子改成原型模式:
function Person(){}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Enginner";
Person.prototype.sayName = function(){
alert(this.name);
};
var person1 = new Person();
person1.sayName(); //"Nicholas"
var person2 = new Person();
person2.sayName(); //"Nicholas"
alert(person1.sayName === person2.sayName); // true
上面示例中,sayName()方法和所有属性直接添加到了Person的prototype属性中,构造函数变成了空函数。通过调用构造函数来创建的新对象,而且新对象还具有相同的属性和方法。下图展示了各个对象的关系:
原型模式与构造函数模式不同的是,新对象的这些属性和方法都是由所有实例共享的。
每当代码读取某个对象的某个属性时,都会执行一次搜索,目标是具有给定名字的属性。搜索首先从对象实例本身开始。如果在实例中找到了具有给定名字的属性,则返回该属性值;如果没有找到,则继续搜索指针指向的原型对象,在原型对象中查找具有给定名字的属性。如果在原型对象中找到了这个属性,则返回该属性的值。
虽然对象实例可以访问保存在原型中的值,但是不能通过对象实例重写原型的值。如果我们在实例中添加一个属性,而该属性与实例原型中的一个属性同名,那就会在实例中创建该属性,该属性将会屏蔽原型中的那个属性。
4、组合使用构造函数模式和原型模式
创建自定义类型的最常见方式,就是组合使用构造函数模式与原型模式。构造函数模式用于定义实例属性,而原型模式用于定义方法和共享属性。
因此,每个实例都会有自己的一份实例属性的副本,但同时又共享着对方法的引用,最大限度地节省了内存。
示例代码:
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", 29, "Software Enginner");
var person2 = new Person("Greg", 27, "Doctor");
person1.friends.push("Van");
alert(person1.friends); //"Shelby,Count,Van"
alert(person2.friends); //"Shelby,Count"
alert(person1.friends === person2.friends); //false
alert(person1.sayName === person2.sayName); //true
5、动态原型模式
动态原型模式为了解决构造函数和原型独立分开的问题,而提出的解决方案。它把所有信息都封装在了构造函数中,而通过在构造函数中初始化原型(仅在必要的情况下)。
示例代码:
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);
};
}
}
var friend = new Person("Nicholas", 29, "Software Engineer");
friend.sayName();
注意:使用动态原型模式时,不能使用对象字面量重写原型。这样会切断现有实例与新原型之间的联系。
6、寄生构造函数模式
这种模式的思想是创建一个函数,该函数的作用仅仅是封装创建对象的代码,然后在再返回新创建的对象。
代码示例:
function Person(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 friend = new Person("Nicholas", 29, "Software Engineer");
friend.sayName(); //"Nicholas"
注意:返回的对象与构造函数或者与构造函数的原型属性之间没有关系,因此,不能使用instanceof操作符来确定对象类型。建议在可以使用其他模式的情况下,不要使用这种模式。
7、稳妥构造函数模式
稳妥对象,指的是没有公共属性,而且其方法也不引用this的对象。稳妥对象最适合在一些安全的环境中(这些环境中会禁止使用this和new),或者在防止数据被其他应用程序改动时使用。
稳妥构造函数模式和寄生构造函数模式相似,但是有两点不同。一是新创建对象的实例方法不引用this,二是不使用new操作符调用构造函数。
代码示例:
function Person(name, age, job){
var o = new Object();
o.sayName = function(){
alert(name);
};
return o;
}
var friend = Person("Nicholas", 29, "Software Engineer");
friend.sayName(); //"Nicholas"
看了《javascript高级程序设计》,发现好多创建对象的方式,所以记录下来方便学习。