为什么这个类似数组的对象表现得像这样?

mut*_*nka 2 javascript arrays prototype object ecmascript-6

通过将对象的原型方法设置为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'
Run Code Online (Sandbox Code Playgroud)

为什么所有这些奇怪的行为都会发生,为什么模仿像这样的数组并不好?

Li3*_*357 8

length物业如何设定?

当您调用Array#push或在本例中调用Foo#push方法时.根据ECMAScript 2015规范:

22.1.3.17 Array.prototype.push(...... items)

[...]

  1. [...]

     [...]

     d.设lenlen + 1

  1. setStatus设置(Ø,"length",LEN,).

因此,当您调用该函数时,它会自动将length属性(如果它不存在)设置len为每次推送时递增的值.

为什么长度不是2?

现在,当您在数组中直接设置索引时,不会更新length属性.这是因为JavaScript中的数组是异常对象,当您直接设置属性时,这些对象会在内部增加长度.根据规范再次:

9.4.2数组外来对象

[...]

无论何时创建或更改Array对象的自有属性,都会根据需要调整其他属性以保持此不变量.具体来说,每当添加名称为数组索引的length属性时,如果需要,属性的值将更改为该数组索引的数值之一;

根据定义,异域对象是覆盖所有普通对象所具有的内部方法的任何对象.在这种情况下,数组奇异对象会覆盖内部[[DefineOwnProperty]]方法,以便在定义数组的属性(如设置索引)时,需要执行额外的步骤以确保length更新属性等属性.您的Foo构造函数不会创建奇异的对象,因此它不会[[DefineOwnProperty]]像数组那样覆盖内部方法 - 因此length在直接在索引处定义值时不会更新.

为什么要push推到以前的指数?

由于Foos不是外来物体,因此length在直接添加元素时不会自动递增,因此当您尝试第二次按下它时foo[1] = 'def',length保留1.如果我们Array#push再看一遍:

22.1.3.17 Array.prototype.push(...... items)

[...]

  1. len成为ToLength(Get(O,"length")).

[...]

  1. itemsList,其元素按从左到右的顺序,传递给此函数调用的参数.

[...]

  1. 重复,而物品不是空的
    a.从项中删除第一个元素,让E为元素的值.
    湾 设setStatusSet(O,ToString(len),E,true).

因此,因为你的数组长度仍然是1 foo[1] = 'def'未修改length属性,因此它将在索引1处设置新的待推元素,因为长度为1.

同样的原则适用于Array#forEach.forEach依赖于length迭代数组.由于您的长度未被修改foo[1] = 'def'并且保持为1,因此forEach仅从索引[0,1]进行迭代,使其仅记录第一个元素.推送更新长度并使其从[0,2]迭代并记录两个元素.

我为什么不这样做?

这是因为数组是异国情调的对象.它们与常规对象不相处,因为常规对象从根本上不能实现与外来对象相同的行为.根据定义,异类对象会覆盖内部方法的默认行为,以实现功能所需的某些行为.在这种情况下,数组必须特别处理设置索引和管理长度 - 使用内部方法完成[[DefineOwnProperty]].对于常规对象,它不会覆盖[[DefineOwnProperty]]这么多基本操作因此无法正常工作 - 所以你不应该这样做.

但是,您可以使用诸如对象之类的奇异对象Proxy来实现您自己的[[DefineOwnProperty]]类似数组的代码来模拟行为.另一种方式,正如loganfsmyth所提到的,您可以使用ES2015类来正确扩展和子类内置的外来对象构造函数,从而模仿数组行为.

  • 需要注意的是,你_can_扩展`Array`和一个真正的ES6`类Foo extends Array`,因为ES6类语法正确地返回了一个奇特的数组,它只是不适用于标准的ES5原型扩展. (2认同)