3.5 原型克隆
有时你并不希望在原型上做数据共享,相反,你想让每个实例都拥有一份属于自己的原型拷贝。多数主流JavaScript类库中早已经存在了一个用来对原型进行克隆的方法,以extend()方法最为常见,它首先被传入一个待扩展的对象,之后所传入的所有对象都是对第一个对象的扩展。
不过extend()方法并没有出现在JavaScript规范中(尽管ES6规范中定义的Object.assign()方法与extend()方法十分相似), 在jQuery与Underscore中可以找到它的身影,它的实现也极为简单。下面的代码摘自Underscore。
_.extend = function(obj) {
each(slice.call(arguments, 1), function(source) {
for (var prop in source) {
obj[prop] = source[prop];
}
});
return obj;
};
如你所见,它将第一个入参作为目标对象,随后遍历剩余的入参,将剩余入参对象中的公共属性逐一拷贝至目标对象。当多个对象上的同名属性值存在冲突时,永远是后一个优先覆盖前一个。
来看看如何使用extend()方法来进行原型克隆:
var switchProto = {
isOn: function isOn() {
return this.state;
},
toggle: function toggle() {
this.state = !this.state;
return this;
},
meta: {
name: 'Light switch'
},
state: false
},
switch1 = extend({}, switchProto),
switch2 = extend({}, switchProto);
test('Prototype clones.', function () {
switch1.isOn.isShared = true;
ok(!switch2.isShared,
'Methods are copied for each instance, not shared.'
);
ok(switch1.toggle().isOn(),
'.toggle() works.'
);
ok(!switch2.isOn(),
'instance safe.'
);
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.'
);
});
在上述示例的extend()方法调用中,我们传入了一个空对象字面量作为目标对象,switchProto在这里被当作源对象。
原型代理与原型克隆间主要的不同之处在于,原型克隆中的实例属性都是经过拷贝的,而原型代理在不同实例间只共享一份属性拷贝,在你对实例属性进行重写前,外界访问到的永远是原型中所设置的属性值。如此看来,原型代理对内存的消耗更少。
在实际应用中,为了创建各个实例间的共有方法与私有属性,常常将这两种方法混合使用。
时间: 2025-01-25 17:20:48