menu 光风霁月。
原型代理与原型克隆
286 浏览 | 2020-06-17 | 阅读时间: 约 3 分钟 | 分类: Javascript 原理 | 标签: Javascript
请注意,本文编写于 128 天前,最后修改于 128 天前,其中某些信息可能已经过时。

前言

之前我详细介绍了与原型有关的内容,点击这里 可以进行复习。里面有我们熟悉的 __proto__prototype,现在我们一起来看一下对象的深拷贝和浅拷贝。

原型代理

let 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);

这里声明了一个作为原型材料的对象 switchProto, 它的两个拷贝对象为 switch1switch2, 现在要对两个对象做一些修改。

switch1.meta.name = "Henrenx";
console.log(switch1.meta.name);                    // Henrenx
console.log(switch2.meta.name);                    // Henrenx
console.log(switch1.meta === switch2.meta);        // true

两个拷贝的对象的属性竟然惊人的一致(其他属性也都是相同的)。

冷静下来, 我们从 Object.create() 函数开始, 逐步探究其中的秘密, 下面的代码是 MDN上对 Object.create() 方法的实现。

if (!Object.create) {
    Object.create = function (o) {
        if (arguments.length > 1) {
            throw new Error('Object.creaate implementation'
                + 'only accepts the first parameter');
        }
        function F() {};
        F.prototype = o;
        return new F();
    }
}

可以看到, switch1switch2 的拷贝过程其实就是把 switchProto 作为原型, 因此两个拷贝对象实际上是在共享一份属性。

下面再做一次不同的修改:

switch1.meta = { name: 'Henrenx' };
console.log(switch1.meta.name);                    // Henrenx
console.log(switch2.meta.name);                    // Light switch
console.log(switch1.meta === switch2.meta);        // false

为什么这里两个拷贝对象就不同了呢?

如果你在实例中对原型上的一些引用类型的变量(对象或数组)做了修改,那么此项修改会在所有使用原型的实例上生效,但如果你直接替换了实例中的某个属性值, 所产生的修改仅限于实例本身。

可以把上面的话记为一条 rule,配合这里的图就更好理解了, 其中[[Prototype]] 就是 __proto__

第一次修改的是一个对象 {name: 'Henrenx'}, meta 属性是在引用这个对象, 因此 switch1switch2 中都生效了。

第二次修改的是 meta 属性, 将它引用到另外一个崭新的对象, 产生的修改仅限于 switch1 本身。

实际上 switch1 中有两个 meta, 一个在原型里, 一个在自身属性里。只是原型查找的过程中自身属性优先, 导致了这样的结果。

原型克隆

把所有的属性(不包括方法)放在原型上面共享是一种非常危险的编码模式,很多代码的异常事故都源于对共享属性的意外修改,所以每个对象都拥有一份自己的属性显得格外重要。

Object.assign 方法就是在做上面期待的工作。

let switchProto = {
    // ... 此对象和前面的相同
},
switch1 = Object.assign({}, switchProto),
switch2 = Object.assign({}, switchProto);

Object.assign 方法在背地里做了如下的工作 (摘自 Underscore)

_.extend = function (obj) {
    each(slice.call(arguments, 1), function(source) {
           for(let prop in source) {
            obj[prop] = source[prop];
        } 
    });
    return obj;
}

可以明显且直观的看到通过这个方法拷贝出来的对象的属性是放在每个对象里的, 而原型代理是直接放在了 prototype 然后通过 new 实例化出来的。对于硬件部分来说, 原型克隆很明显占据了更多的内存。因此在实际应用中, 为了创建 共享方法私有属性, 常把这两者结合起来使用。

结束语

如果在原型克隆中做一些和原型代理一样的属性更改的小测试,二者结果是相同的,与是否是原型代理还是原型克隆无关呢~

原型代理与原型克隆最主要的区别,原型克隆中的实例属性都是经过拷贝得来的,而原型代理在不同实例间只共享一份属性拷贝,在对实例属性进行重写前,外界访问到的永远是原型中所设置的属性值。

知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议

发表评论

email
web

全部评论 (暂无评论)

info 还没有任何评论,你来说两句呐!