给出以下代码:
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'});
答案 0 :(得分:3)
这种行为是符合规范的,虽然看起来Chrome很大程度上是靠它自己做的(Firefox和IE11没有)。这里的关键是在数组创建过程中设置theArray[0]
(您的a
和d
示例)与之后设置(b
不同和c
例子;稍后更多内容)。这取决于如何处理数组初始值设定项。
换句话说,显然发生的事情是当一个项目被添加到数组中时(在它已经创建之后),Chrome会出于某种原因上升到原型链,但更奇怪的是,没有得到财产,但设置它。
也适用于get
;您的代码中没有任何get
,您看到的内容会因您在get
上执行的阵列而异(您会看到b
的getter运行1}}和c
,但不是a
或d
)。这种变化的原因是您在"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"
属性。
* (这是我贫血的小博客上的帖子)