通过将对象的原型方法设置为Array方法,该对象的行为类似于对象和数组之间的混合。以下是一个简单的例子:
function Foo() {}
Foo.prototype.push = Array.prototype.push;
Foo.prototype.forEach = Array.prototype.forEach;
var foo = new Foo();
foo.push('abc');
foo.length; // = 1 as expected. But wait, why isn't foo.length undefined? How/when did this property get attached to foo?
foo[1] = 'def';
foo.length; // still = 1. But foo={0:'abc',1:'def'}, Why not =2?
foo.forEach(function(item) {
console.log(item)
}); //shows only'abc' and not 'def'
foo.push('ghi');
foo.length; // = 2, and now foo = {0:'abc', 1:'ghi'}. So it overwrote the key=1, which means its accessing the same location, but the first approach did not change the length ( didn't become a part of the array ) why ?
foo.forEach(function(item) {
console.log(item)
}); //now shows 'abc' and 'ghi'
为什么所有这些奇怪的行为都会发生,为什么模仿像这样的阵列不好?
答案 0 :(得分:8)
length
属性?当您调用Array#push
或本例Foo#push
方法时。根据{{3}}:
22.1.3.17 Array.prototype.push(...... items)
[...]
- [...]
醇>[...]
d。让 len 为 len + 1
- 让 setStatus 为ECMAScript 2015 Specification( O ,
醇>"length"
, len ,的真强>)。
因此,当您调用该函数时,它会自动将length属性(如果不存在)设置为值len
,每次推送时该值都会递增。
现在,当您在数组中直接设置索引时,不会更新length属性。这是因为JavaScript中的数组是Set,当您直接设置属性时,它会在内部增加长度。按照规范再次:
9.4.2数组外来对象
[...]
每当创建或更改Array对象的自有属性时,都会根据需要调整其他属性以保持此不变量。具体来说,每当添加名称为数组索引的属性时,如果需要,
length
属性的值将更改为该数组索引的数值之一;
根据定义,异域对象是覆盖所有普通对象所具有的内部方法的任何对象。在这种情况下,数组外来对象会覆盖内部[[DefineOwnProperty]]
方法,以便在定义数组的属性(如设置索引)时,需要执行额外的步骤来确保属性length
等内容已更新。您的Foo
构造函数不会创建奇异的对象,因此它不会像数组那样覆盖[[DefineOwnProperty]]
内部方法 - 因此当您直接定义值时不会更新length
索引。
push
会推送到上一个索引?由于Foo
s不是外来对象,因此当直接使用length
添加元素时不会自动增加foo[1] = 'def'
,因此当您使用length
时,Array#push
仍为1试着第二次推动它。如果我们再次查看"length"
:
22.1.3.17 Array.prototype.push(...... items)
[...]
- 让 len 为exotic objects(ToLength( O ,
foo[1] = 'def'
))。< / LI> 醇>[...]
因此,由于您的数组长度仍为1,因为length
未修改forEach
属性,因此它将在索引1处设置新的待推元素,因为长度为1。 / p>
同样的原则适用于ToString。 length
依赖于foo[1] = 'def'
来遍历数组。由于当您执行forEach
并且仍为1时未修改长度,[[DefineOwnProperty]]
仅从索引[0,1]迭代,导致它仅记录第一个元素。推送更新长度并使其从[0,2]迭代并记录两个元素。
这是因为数组是异国情调的对象。它们与常规对象不相处,因为常规对象从根本上不能实现与外来对象相同的行为。根据定义,异类对象会覆盖内部方法的默认行为,以实现功能所需的某些行为。在这种情况下,数组必须特别处理设置索引和管理长度 - 使用内部方法[[DefineOwnProperty]]
完成。使用常规对象时,它不会覆盖[[DefineOwnProperty]]
,因此许多基本操作无法正常工作 - 因此您不应该这样做。
但是,您可以使用诸如Array#forEach
对象之类的奇异对象来实现您自己的{{1}}类似数组的代码来模拟行为。另一种方法,如Proxy
所述,您可以正确使用ES2015类loganfsmyth,从而模仿数组行为。
答案 1 :(得分:1)
foo.push('abc');
foo.length; // = 1 as expected. But wait, why isn't foo.length undefined?
//How/when did this property get attached to foo?
.push()
方法设置.length
,就像使用真实数组一样。我并不担心.length
已经存在,并将其视为0
- 因此在关键0
和{{1}处添加了新元素设置为.length
。
1
你的对象不是一个数组,所以虽然添加带有数字索引的元素有效(虽然它们被转换为字符串,实际上是真正的数组对象索引),但它并没有神奇地设置{ {1}}对于你来说,它喜欢真正的数组。 foo[1] = 'def';
foo.length; // still = 1. But foo={0:'abc',1:'def'}, Why not =2?
的现有.length
因此保持不变。
.length
1
方法使用foo.push('ghi');
foo.length; // = 2, and now foo = {0:'abc', 1:'ghi'}. So it overwrote the key=1,
// which means its accessing the same location, but the first approach
// did not change the length ( didn't become a part of the array ) why ?
的现有值来决定添加新元素的位置。根据{{1}},您只有一个元素,因此新的元素在索引.push()
处被推入,.length
已更改为.length
。
答案 2 :(得分:0)
push()
函数更新数组的长度,因此在foo[1] = 'def'
之后,长度仍为1. foo是一个对象,因此分配&#39; def&#39;键1的作用但不会增加长度并且foo.push('ghi')
长度增加后。此外,foo.push('ghi')
使用当时为1的当前长度foo作为&#39; ghi&#39;的索引。
在您第一次调用push()
函数之前foo.length
实际上是未定义的。 push()
函数第一次定义它,之后为每次调用递增它。