本节书摘来华章计算机出版社《JavaScript应用程序设计》一书中的第3章,第3.5节,作者:Eric Elliott 更多章节内容可以访问云栖社区“华章计算机”公众号查看。
有时你并不希望在原型上做数据共享,相反,你想让每个实例都拥有一份属于自己的原型拷贝。多数主流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在这里被当作源对象。原型代理与原型克隆间主要的不同之处在于,原型克隆中的实例属性都是经过拷贝的,而原型代理在不同实例间只共享一份属性拷贝,在你对实例属性进行重写前,外界访问到的永远是原型中所设置的属性值。如此看来,原型代理对内存的消耗更少。在实际应用中,为了创建各个实例间的共有方法与私有属性,常常将这两种方法混合使用。
相关资源:敏捷开发V1.0.pptx