对象包含相同的值但不应该

Objects contain same values but shouldn't

我尝试实现对象继承,其中每个对象共享相同的功能但包含不同的 values/containers。这样做我发现了一个奇怪的行为;每个单独的对象都像一个魅力,但它们都共享相同的选项。请澄清我的意思,我添加了示例代码。

我想做的是从每个元素读取数据值并将其绑定到选项。这些在某些函数中是必需的才能正常工作!

<div class="first" data-value="first div"></div>
<div class="second" data-value="second div"></div>
<script>
    var original = {
        options: {
            value: null,
            factor: 13
        },

        init: function (container) {
            this.options.value = container.getAttribute('data-value');
        }
    };

    var variation = Object.create(original);
    variation.options.factor = 37;

    var firstDiv = Object.create(variation);
    var secondDiv = Object.create(variation);

    firstDiv.init(document.getElementsByTagName('div')[0]);
    secondDiv.init(document.getElementsByTagName('div')[1]);

    alert('firstDiv.options === secondDiv.options: ' + (firstDiv.options === secondDiv.options)); // but should be false!
</script>

请注意,这只是展示了实物的一小部分。其他的在我看来都是无关紧要的。

我希望你清楚问题出在哪里。另外,我故意使用 Object.create()

objects contain same values but shouldn't

它们应该,因为您没有更改 variation 上的 options 属性,所以它仍然指向 original.options 指向的同一个对象。

当你这样做时:

var original = {
    options: {
        value: null,
        factor: 13
    },

    init: function (container) {
        this.options.value = container.getAttribute('data-value');
    }
};

这是你记忆中的内容(省略了一些细节):

                                  +−−−−−−−−−−−−−−−−−−−−+
            +−−−−−−−−−−−−+   +−−−>| (Object.prototype) |<−+
original−−−>|  (object)  |   |    +−−−−−−−−−−−−−−−−−−−−+  |
            +−−−−−−−−−−−−+   |                            |
            | __proto__  |−−−+    +−−−−−−−−−−−−−+         |
            | options    |−−−−−−−>|  (object)   |         |
            | init       |−−−+    +−−−−−−−−−−−−−+         |
            +−−−−−−−−−−−−+   |    | __proto__   |−−−−−−−−−+
                             |    | value: null |
                             |    | factor: 13  |
                             |    +−−−−−−−−−−−−−+
                             |
                             |    +−−−−−−−−−−−−+
                             +−−−−| (function) |
                                  +−−−−−−−−−−−−+

...其中 __proto__ 是伪 属性 显示对象的原型是什么(具体来说,规范调用对象内部的值 [[Proto]] 属性). (在浏览器上,在 ES2015 中,实际上有一个 __proto__ 访问器,但不应该使用它。)

那么当你这样做时:

var variation = Object.create(original);

您有:

            +−−−−−−−−−−−−+
variation−−>|  (object)  |
            +−−−−−−−−−−−−+
            | __proto__  |−−+
            +−−−−−−−−−−−−+  |
                            |                              
         +−−−−−−−−−−−−−−−−−−+     +−−−−−−−−−−−−−−−−−−−−+   
         |                     +−>| (Object.prototype) |<−+
         \  +−−−−−−−−−−−−+     |  +−−−−−−−−−−−−−−−−−−−−+  |
original−−−>|  (object)  |     |                          |
            +−−−−−−−−−−−−+     |                          |
            | __proto__  |−−−−−+  +−−−−−−−−−−−−−+         |
            | options    |−−−−−−−>|  (object)   |         |
            | init       |−−−+    +−−−−−−−−−−−−−+         |
            +−−−−−−−−−−−−+   |    | __proto__   |−−−−−−−−−+
                             |    | value: null |
                             |    | factor: 13  |
                             |    +−−−−−−−−−−−−−+
                             |
                             |    +−−−−−−−−−−−−+
                             +−−−−| (function) |
                                  +−−−−−−−−−−−−+

您可以看到 originalvariation 仍然指向 相同的 选项对象。

如果您想要单独的选项对象,则必须创建一个新对象。你可以让 variation.options 使用 original.options 作为它的原型:

var variation = Object.create(original);
variation.options = Object.create(original.options);

...但是对于 firstDivsecondDiv:

,您必须再次
var firstDiv = Object.create(variation);
firstDiv.options = Object.create(variation.options);
var secondDiv = Object.create(variation);
secondDiv.options = Object.create(variation.options);

...这表明我们需要做一些不同的事情。

您可以使用一个函数来执行此操作:

function createVariation(source) {
    var v = Object.create(source);
    v.options = Object.create(source.options);
    return v;
}

var variation = createVariation(original);
var firstDiv = createVariation(variation);
var secondDiv = createVariation(variation);

...但是您可能会查看构造函数或制造商函数,以及称为“扩展”函数的相当典型的模式(例如 jQuery's or Underscore's)。

firstDivsecondDiv 都没有自己的 options 属性。两者都从 original 继承了 options。您可以通过观察下面的所有表达式都是 false:

来测试这一点
variation.hasOwnProperty("options") // false
firstDiv.hasOwnProperty("options")  // false
secondDiv.hasOwnProperty("options") // false

您的 init 函数执行 this.options.value = ...,但暂时只考虑表达式 this.options。您永远不会创建另一个 options 对象,因此当您为其 options 对象请求 firstDivsecondDiv 时,会将 this.options 查找升级到 original原型对象。

因此,当您访问 firstDivsecondDivoptions 属性 时,您会导致 属性 查找升级为 original(原型链中第一个真正具有 options 属性 的对象)。这意味着 firstDiv.optionssecondDiv.options 是完全相同的对象(即 original.options)。因此,当您设置 secondDiv.options.value 时,您也在设置 firstDiv.options.value.

此处的解决方案是确保 firstDivsecondDiv 明确拥有自己的 options 属性。但是,您还希望 options 值本身继承自 variation.options,并且您希望 variation 也有自己的 options 值,它继承自 original.options. 用他的 createVariation 方法详细解决了这个问题:

function createVariation(source) {
    var v = Object.create(source);
    v.options = Object.create(source.options);
    return v;
}

var variation = createVariation(original);
var firstDiv = createVariation(variation);
var secondDiv = createVariation(variation);