本节书摘来华章计算机出版社《JavaScript应用程序设计》一书中的第3章,第3.4节,作者:Eric Elliott 更多章节内容可以访问云栖社区“华章计算机”公众号查看。
3.4 原型代理
在JavaScript中,所有对象内部都有一个指向其原型的引用,在获取对象的属性与方法时,JavaScript引擎会首先检索当前对象是否有值,如果没有则去检索其原型,如果还没有,再沿着原型链的上游继续查找,直到抵达原型链的末端,也就是Object对象的原型上。
当创建了一个对象字面量时,会隐式地将其与Object的原型关联起来。或者,你可以通过Object.create()方法构造对象,此方法允许你为对象设置原型。Object.create()方法在ES5规范中才被加入,所以可能你需要自己去实现它。下面是MDN(Mozilla Developer Network)文档中一项对Object.create()方法的实现(http://mzl.la/1pFH3jo):
if (!Object.create) {
Object.create = function (o) {
if (arguments.length > 1) {
throw new Error('Object.create implementation'
+ ' only accepts the first parameter.');
}
function F() {}
F.prototype = o;
return new F();
};
}
当你通过new关键字调用构造函数时,构造函数的原型对象会被设置为新实例对象原型的引用。如你所见,Object.create()的模拟方案仅仅是一个快捷方式,它先是创建了一个新的构造函数,随后将传入的对象赋值给构造函数的原型,最后返回由原型生成的实例。下面来看看如何在实际场景中使用这个方案。
var switchProto = {
isOn: function isOn() {
return this.state;
},
toggle: function toggle() {
this.state = !this.state;
return this;
},
state: false
},
switch1 = Object.create(switchProto),
switch2 = Object.create(switchProto);
test('Object.create', function () {
ok(switch1.toggle().isOn(),
'.toggle() works.'
);
ok(!switch2.isOn(),
'instance safe.'
);
});
被传入Object.create()方法中的任何对象,都会被视为新实例对象的原型,相信你也发现了,这个原型对象有一些不寻常之处。
注意原型中的state值,更改实例switch1上的state值不会影响实例switch2中的state值。原型上的属性好比是默认值一样,会被实例对象中的同名属性所覆盖。
需要特别留意的是,如果你在实例中对原型上的一些引用类型的变量(如对象或数组)做了修改,那么此项修改会在所有使用该原型的实例上生效,但如果你直接替换了实例中的某个属性值,所产生的修改影响仅限于实例本身。
var switchProto = {
isOn: function isOn() {
return this.state;
},
toggle: function toggle() {
this.state = !this.state;
return this;
},
meta: {
name: 'Light switch'
},
state: false
},
switch1 = Object.create(switchProto),
switch2 = Object.create(switchProto);
test('Prototype mutations.', function () {
switch2.meta.name = 'Breaker switch';
equal(switch1.meta.name, 'Breaker switch',
'Object and array mutations are shared.'
);
switch2.meta = { name: 'Power switch' };
equal(switch1.meta.name, 'Breaker switch',
'Property replacement is instance-specific.'
);
});
在这个例子中,switchProto对象拥有一个名为meta的属性,当你在实例中对meta的子属性值进行修改时,所有原型链中关联对象的子属性值都会被改写,但如果你将实例中的meta对象直接替换为一个新对象时,改动效果仅仅限于实例本身。
警告: 在JavaScript社区中的大部分开发者看来,将所有属性(不包括方法)都搬至原型上做共享,是一种非常危险的编码反模式,因为实际应用中,多数代码异常事故都源于对共享属性的意外修改。