# 前言
摘自《JavaScript高级程序设计》第三版 6.2 创建对象章节
# 工厂模式
工厂模式定义一个用于创建对象的接口。考虑到在 ECMAScript 中无法创建类,开发人员就发明了一种函数,用函数来封装以特定的接口创建 对象的细节。
function createPerson(name) {
var o = new Object();
o.name = name;
o.sayName = function() {
alert(this.name);
}
return o;
}
var person1 = createPerson('foo');
var person2 = createPerson('bar');
console.log(person1.__proto__ === person2.__proto__); // true, 实例的__proto__指向原型
2
3
4
5
6
7
8
9
10
11
12
缺点:对象无法识别,因为所有实例都指向同一个原型。
# 构造函数模式
function Person(name) {
this.name = name;
this.sayName = function() {
console.log(this.name)
}
}
var person1 = new Person('foo');
console.log(person1.constructor == Person); // true 可以识别为构造函数Person
console.log(person1 instanceof Person); // true
2
3
4
5
6
7
8
9
10
优点:实例可以识别为一个特定的类型。
缺点:每次创建实例时,构造函数的方法都被创建一次。
# 原型模式
function Person(name) {
}
Person.prototype.name = 'foo';
Person.ptototype.getName = function() {
console.log(this.name); // foo
}
var person1 = new Person();
2
3
4
5
6
7
8
9
10
优点:方法不会重新创建。
缺点:
- 所有属性和方法都共享;
- 不能初始化参数。
# 原型模式优化v1
function Person(name) {
}
Person.prototype = {
name: 'foo',
sayName: function() {
console.log(this.name)
}
}
var person1 = new Person();
2
3
4
5
6
7
8
9
10
11
12
优点:减少了不必要的输入,封装性好一点;
缺点:重写了原型(Person.prototype设置为等于一个以对象字面量形式创建的新对象),丢失了 constructor 属性。
# 原型模式优化v2
function Person(name) {
}
Person.prototype = {
name: 'foo',
sayName: function() {
console.log(this.name)
}
}
var person1 = new Person();
2
3
4
5
6
7
8
9
10
11
12
优点:减少了不必要的输入,封装性好一点;
缺点:重写了原型(Person.prototype设置为等于一个以对象字面量形式创建的新对象, constructor 属性指向 Object 构造函数),丢失了 constructor 属性。
console.log(person1.constructor === Person) // false
console.log(person1.constructor === Object) // true
2
# 原型模式优化v3
将 constructor 设置回适当的值
这种方式重设 constructor 属性会导致它的 [[Enumerable]] 特性被设置为true。默认情况下,原生的 constructor 属性是不可枚举的,可以用 Object.defineProperty() 修改。
function Person(name) {
}
Person.prototype = {
constructor: Person,
name: 'foo',
sayName: function() {
console.log(this.name)
}
}
// 重设构造函数
Object.defineProperty(Person.prototype, 'constructor', {
enumerable: false,
value: Person
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
优点:实例可以通过 constructor 属性找到所有构造函数。
缺点:还是原型模式的缺点。
# 组合模式
组合使用构造函数模式与原型模式。构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性。
function Person(name) {
this.name = name;
}
Person.prototype = {
constructor: Person,
sayName: function() {
console.log(this.name);
}
}
var person1 = new Person('foo');
2
3
4
5
6
7
8
9
10
11
12
优点:很好的分开私有和共享,使用最广泛的方式。
缺点:有人还是希望能写到一块。
# 动态原型模式
function Person(name) {
this.name = name;
if (typeof this.getName != 'function') {
Person.prototype.sayName = function () {
console.log(this.name)
}
}
}
var person1 = new Person('foo');
person1.sayName(); // foo
2
3
4
5
6
7
8
9
10
11
优点:原型方法只在构造函数初次调用时才会执行。 缺点:不能使用对象字面量重写原型,因为在已经创建了实例情况下重写原型,会切断现有实例与新原型之间的联系。
如果想用字面量重写原型,可以在重写原型后 return
新的实例出来。
function Person(name) {
this.name = name;
if (typeof this.getName != "function") {
Person.prototype = {
constructor: Person,
getName: function () {
console.log(this.name);
}
}
return new Person(name);
}
}
var person1 = new Person('kevin');
person1.getName(); // kevin
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 寄生构造函数模式
function Person(name) {
var o = new Object();
o.name = name;
o.sayName = function() {
console.log(this.name)
}
return o
}
var person1 = new Person('foo');
console.log(person1 instanceof Person); // false
console.log(person1 instanceof Object); // true
2
3
4
5
6
7
8
9
10
11
12
13
14
寄生构造函数模式跟工厂模式很类似,多了一个 return
重写调用构造函数时的返回值。但是无法用 instanceof
来确定对象类型。
这样方法可以在特殊情况下使用。比如我们想创建一个具有额外方法的特殊数组,但是又不想直接修改Array构造函数,我们可以这样写:
function SpecialArray() {
var values = new Array();
values.push.apply(value, arguments);
values.toPipedString = function () {
return this.join("|");
};
return values;
}
var colors = new SpecialArray('red', 'blue', 'green');
var colors2 = SpecialArray('red2', 'blue2', 'green2');
console.log(colors);
console.log(colors.toPipedString()); // red|blue|green
console.log(colors2);
console.log(colors2.toPipedString()); // red2|blue2|green2
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 稳妥构造函数模式
function Person(name) {
var o = new Object();
o.sayName = function() {
console.log(name)
}
return o;
}
var person1 = Person('foo');
person1.sayName(); // foo
person1.name = 'bar';
person1.sayName(); // foo
console.log(person1.name); // bar
2
3
4
5
6
7
8
9
10
11
12
13
14
稳妥方式,指的是没有公共属性,而且其方法也不引用 this 的对象。 与寄生构造函数模式不同的是:
- 新创建的实例方法不引用 this
- 不使用 new 操作符调用构造函数
稳妥模式适合在一些安全的环境中,但和工厂模式一样,也是无法识别对象所属类型。