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

时间:2017-06-29 03:34:16

标签: javascript arrays object ecmascript-6 prototype

通过将对象的原型方法设置为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'

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

3 个答案:

答案 0 :(得分:8)

如何设置length属性?

当您调用Array#push或本例Foo#push方法时。根据{{​​3}}:

  

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

     

[...]

     
      
  1. [...]
  2.         

    [...]

         

    d。让 len len + 1

         
        
    1. setStatus ECMAScript 2015 Specification O "length" len ,的)。
    2.   

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

为什么长度不是2?

现在,当您在数组中直接设置索引时,不会更新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)

     

[...]

     
      
  1. len exotic objectsToLength O foo[1] = 'def' ))。< / LI>         

    [...]

         
        
    1. Get,其元素按从左到右的顺序排列,传递给此函数调用的参数。
    2.         

      [...]

           
          
      1. 重复,而项目不为空白   一个。从中删除第一个元素,并让 E 成为元素的值。
          湾设 setStatus List O Set len ), E ,的)。
      2.   

因此,由于您的数组长度仍为1,因为length未修改forEach属性,因此它将在索引1处设置新的待推元素,因为长度为1。 / p>

同样的原则适用于ToStringlength依赖于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()函数第一次定义它,之后为每次调用递增它。