以下内容总结自《JavaScript高级程序设计(第3版)》
一. 工厂模式
ES5中无法创建类,所以开发人员用函数封装以特定接口创建对象的细节。
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", 20, "Software Engineer");
var person2 = createPerson("Greg", 23, "Doctor");
- 优点: 可以解决创建多个相似对象写重复代码的问题。
- 缺点: 这里的对象都是Object类型,没有解决对象识别的问题(即怎样知道一个对象的类型)。
二. 构造函数模式
ECMAScript中可以创建自定义的构造函数。
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", 20, "Software Engineer");
var person2 = new Person("Greg", 23, "Doctor");
- 优点: 指定了实例的类型,弥补了工厂模式的缺点。
- 缺点: 每个方法都要在每个实例中重新创建一遍。
是否有办法弥补这个缺点呢?有。
将sayName函数定义转移到构造函数外部,如下:
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", 20, "Software Engineer");
var person2 = new Person("Greg", 23, "Doctor");
alert(person1.sayName === person2.sayName); // false
- 缺点: 上面这种做法解决问题的同时又带来了新的问题,就是如果一个对象里面要定义很多方法的话,我们就要定义很多的全局函数,这样我们自定义的引用类型就毫无封装性可言了。
构造函数模式和工厂模式的不同:
- 没有显示地创建对象;
- 直接将属性和方法赋值给了this对象;
- 没有return语句。
使用new创建对象实际会经历以下步骤:
- 创建一个新对象;
- 将构造函数的作用域赋值给新对象(此时this就指向这个新对象);
- 执行构造函数中的代码(为这个新对象添加属性);
- 返回新对象。
使用构造函数创建对象的经典用法:
// 当做构造函数使用
var person = new Person("Nicholas", 20, "Software Engineer");
person.sayName();
// 当做普通函数使用,此时的this指向window
Person("Nicholas", 20, "Software Engineer");
window.sayName();
// 在另一个对象的作用域调用
var o = new Object();
Person.call(o, "Nicholas", 20, "Software Engineer");
o.sayName();
三. 原型模式
每一个函数都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。
也就是说,prototype是通过调用构造函数而创建的那个对象实例的原型对象。
function Person() {
}
Person.prototype.name = "Nicholas";
Person.prototype.age= 20;
Person.prototype.job= "Software Engineer";
Person.prototype.sayName = function() {
alert(this.name);
};
var person1 = new Person();
person1.sayName(); // Nicholas
var person2 = new Person();
person1.sayName(); // Nicholas
alert(person1.sayName === person2.sayName); // true
- 优点: 使用原型对象可以让所有对象实例共享它所包含的属性和方法,解决了构造函数模式的缺点。
- 缺点:
(1). 省略了构造函数传参的环节,这会导致所有的实例都默认取得相同的属性。在例子 中表现为无法为不同的实例定义不同的name,age等属性。
(2). 当原型对象中存在引用类型的属性时,所有的实例会共享同一个该引用类型的值。
例:
function Person() {
}
Person.prototype = {
constructor: Person,
name: "Nicholas",
age: 20,
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
alert(person1.friends === person2.friends); // true
由上面的代码可知,person1和person2两个实例共享同一个friends属性。而正常来讲,我们是希望每一个实例都有自己的属性,不受其他实例的影响。
由于上述问题,人们很少单独使用原型模式,而是将原型模式和构造函数模式组合起来一起使用。
四. 组合使用原型模式和构造函数模式
原型模式用于定义方法和共享的属性,构造函数模式用于定义实例属性。
这样每个实例都会有自己的一份实力属性的副本,同时又共享对方法的引用。
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", 20, "Software Engineer");
var person2 = new Person("Greg", 23, "Doctor");
person1.friends.push("Van");
alert(person1.friends); // Shelby,Court,Van
alert(person2.friends); // Shelby,Court
alert(person1.friends === person2.friends); // false
alert(person1.sayName === person2.sayName); // true
- 优点: 结合了构造函数模式和原型模式的优点。
- 缺点: (未知,待填)
五. 动态原型模式
该模式将分离的构造函数和原型全部封装在了构造函数里,并通过检查某个应存在的方法是否有效,来决定是否初始化原型。
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 person1 = new Person("Nicholas", 20, "Software Engineer");
person1.sayName();
上面的sayName方法会在初次调用构造函数的时候添加到原型当中去,之后再调用时,由于能够在原型中找到sayName方法,所以不会再重新执行。
六. 寄生构造函数模式
该模式和工厂模式只有两点不同:
- 使用new操作符。
- 把使用的包装函数叫做构造函数。
示例:
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 person1 = new Person("Nicholas", 20, "Software Engineer");
person1.sayName();
这个模式可以在特殊情况下为对象创建构造函数。比如我们想为数组添加额外方法,但是不能直接修改Array构造函数,此时可以使用这个模式。
function SpecialArray() {
// 创建数组
var values = new Array();
// 添加值
values.push.apply(values, arguments);
// 添加方法
values.toPipedString = function () {
return this.join("|");
}
// 返回数组
return values;
}
var color = new SpecialArray("red", "blue", "green");
color.toPipedString(); // "red|blue|green"
- 优点: 解决为对象创建构造函数的问题。
- 缺点: 返回的对象与在构造函数外面创建的对象没有区别,不能使用instanceof确定对象类型。
七. 稳妥构造函数模式
首先明确什么是稳妥对象(durable object):所谓稳妥对象,指的是没有公共属性,而且其方法也不能引用this的对象。
稳妥对象最适合在一些安全的环境中(这些环境会禁止使用this或new),或者在防止数据被其他应用程序(如Maphup程序)改动时使用。
示例:
function Person(name, age, job) {
// 创建要返回的对象
var o = new Object();
// 可以在这里定义私有变量和函数
// 方法
o.sayName = function () {
alert(name);
};
// 返回对象
return o;
}
var person1 = Person("Nicholas", 20, "Software Engineer");
person1.sayName(); // "Nicholas"
person1.name; // undefined
以这种模式创建的对象中,name属性只能通过sayName方法访问。
即使有其他代码给这个对象添加方法或者数据成员,但也不可能有别的办法访问传入到构造函数中的原始数据。
稳妥构造函数模式与寄生构造函数模式的区别:
- 新创建的实例方法不引用this;
- 不使用new操作符调用构造函数。