设置数组索引属性时调用`Object.prototype` setter的原因

时间:2017-03-05 10:44:00

标签: javascript chromium

给出以下代码:

Object.defineProperty(Object.prototype, '0', { // Will behave similarly if we define on `Array.prototype`
    configurable: true,
    get: function () {
        console.log('prototype getter accessed');
        return this['_0']; // Allow continued testing
    },
    set: function (v) {
        console.log('prototype setter accessed');
        console.log('setValue', v);
        this['_0'] = v; // See getter above
    }
});

console.log('creating array with 0 index pre-set');
var a = ['a'];

var b = [];

console.log('pushing to empty array to set 0 index');
b.push('b');

var c = [];

console.log('Setting 0 index directly on empty array');
c[0] = 'c';

var d = ['d'];
console.log('Setting 0 index directly on non-empty array');
d[0] = 'dd';
.as-console-wrapper {
  max-height: 100% !important;
}

... Chrome中显示以下控制台输出:

creating array with 0 index pre-set
pushing to empty array to set 0 index
prototype setter accessed
setValue b
Setting 0 index directly on empty array
prototype setter accessed
setValue c
Setting 0 index directly on non-empty array

换句话说,显然发生的事情是当一个项目被添加到数组中时(在它已经创建之后),Chrome会出于某种原因上升到原型链,但更奇怪的是,没有得到财产,但设置它。

我想知道这是一个错误还是在规范中定义了这种行为。

(并且FWIW,在尝试允许以不同方式设置数组时,使用以下内容,仅从属性的定义发生无限递归:

var e = [];
Object.defineProperty(e, '0', {configurable: true, writable: true, enumerable: true, value: 'got 0'});

1 个答案:

答案 0 :(得分:3)

这种行为是符合规范的,虽然看起来Chrome很大程度上是靠它自己做的(Firefox和IE11没有)。这里的关键是在数组创建过程中设置theArray[0] (您的ad示例)与之后设置(b不同和c例子;稍后更多内容)。这取决于如何处理数组初始值设定项。

  

换句话说,显然发生的事情是当一个项目被添加到数组中时(在它已经创建之后),Chrome会出于某种原因上升到原型链,但更奇怪的是,没有得到财产,但设置它。

也适用于get;您的代码中没有任何get,您看到的内容会因您在get上执行的阵列而异(您会看到b的getter运行1}}和c,但不是ad)。这种变化的原因是您在"0"a上创建了d作为拥有属性,但在b和{{c上1}}它是一个带有getter / setter的继承属性。如果我们访问b[0]

,我们会看到触发器已触发

Object.defineProperty(Object.prototype, '0', { // Will behave similarly if we define on `Array.prototype`
    configurable: true,
    get: function () {
        console.log('prototype getter accessed');
        return this['_0']; // Allow continued testing
    },
    set: function (v) {
        console.log('prototype setter accessed');
        console.log('setValue', v);
        this['_0'] = v; // See getter above
    }
});

var b = [];

console.log('pushing to empty array to set 0 index');
b.push('b');

console.log("accessing b[0]");
console.log(b[0]);
.as-console-wrapper {
  max-height: 100% !important;
}

正在进行什么

标准数组aren't really arrays at all *,它们是使用Array.prototype的对象,并且对一类属性名称(“数组索引”)和length属性进行特殊处理。这解释了您所看到的大部分内容,因为您正在使用属性get触发set"0"操作。

问题的关键似乎是访问d[0]不会触发getter / setter的原因。答案是:因为d有一个名为"0"拥有属性,而不是使用Object.prototype中的继承属性。原因是您创建了d,如下所示:

var d = ['d'];

数组初始化程序的处理不会通过原型链设置属性。如果我们在规范中查看ArrayInitialization,我们会看到它通过ArrayAccumulation operation处理初始化程序中的属性,这会产生一件有趣的事情:它会直接转到CreateDataProperty来添加属性而不是更典型的PutValue operation。 CreateDataProperty不遍历原型链,它直接在对象上创建数据属性。

这意味着

"0"属性存在很大差异
var b = [];
b.push('b');

var d = ['d'];

d有一个拥有属性,这是一个简单的数据属性。 b有一个继承的属性,带有一个getter / setter。

我们可以很容易地向自己证明:

Object.defineProperty(Object.prototype, '0', { // Will behave similarly if we define on `Array.prototype`
    configurable: true,
    get: function () {
        console.log('prototype getter accessed');
        return this['_0']; // Allow continued testing
    },
    set: function (v) {
        console.log('prototype setter accessed');
        console.log('setValue', v);
        this['_0'] = v; // See getter above
    }
});

var b = [];
b.push('b');
console.log("b.hasOwnProperty(0)? ", b.hasOwnProperty(0));

var d = ['d'];
console.log("d.hasOwnProperty(0)? ", d.hasOwnProperty(0));
.as-console-wrapper {
  max-height: 100% !important;
}

最后两个console.log向我们展示:

b.hasOwnProperty(0)?  false
d.hasOwnProperty(0)?  true

那是因为数组初始化程序绕过原型链在"0"上创建d属性,但我们没有使用b。所以我们在记忆中的内容(细节省略)是:

                                                     +−−−−−−−−−−−−−−−−−+
Object.prototype−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−+−>|    (object)     |
                                                  |  +−−−−−−−−−−−−−−−−−+
                                                  |  | get 0: function |
                                                  |  | set 0: function |
                                                  |  | ...             |
                                                  |  +−−−−−−−−−−−−−−−−−+
                                                  |
                               +−−−−−−−−−−−−−−−+  |
Array.prototype−−−−−−−−−−−−−++>| [[Prototype]] |>−+
                           //  +−−−−−−−−−−−−−−−+ 
                          / |
                          | |
                          | |
       +−−−−−−−−−−−−−−−+  | |
b−−−−−>|    (array)    |  | |
       +−−−−−−−−−−−−−−−+  | |
       | [[Prototype]] |>−+ |
       | length: 1     |    |
       | _0: "b"       |    |   * Notice this is _0, not 0
       +−−−−−−−−−−−−−−−+    |
                            |
       +−−−−−−−−−−−−−−−+    |
d−−−−−>|    (array)    |    |
       +−−−−−−−−−−−−−−−+    |
       | [[Prototype]] |>−−−+
       | length: 1     |
       | 0: "d"        |
       +−−−−−−−−−−−−−−−+

注意b如何具有"_0"属性(由setter创建),而不是"0"属性(它继承自Object.prototype)。但是d有自己的"0"属性。

* (这是我贫血的小博客上的帖子)