gpt4 book ai didi

javascript - 为什么这个类似数组的对象会有这样的行为?

转载 作者:行者123 更新时间:2023-12-04 23:45:32 25 4
gpt4 key购买 nike

通过将对象的原型(prototype)方法设置为数组方法,对象的行为就像对象和数组的混合体。下面是一个简单的例子:

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'

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

最佳答案

length 属性是如何设置的?

当您调用 Array#push 时,或者在本例中,调用 Foo#push 方法。根据 ECMAScript 2015 Specification :

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

[...]

  1. [...]

     [...]

     d. Let len be len+1

  1. Let setStatus be Set(O, "length", len, true).

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

为什么长度不是2?

现在,当您直接在数组中设置索引时,长度属性不会更新。那是因为 JavaScript 中的数组是 exotic objects当您直接设置属性时,它会在内部增加长度。再次按照规范:

9.4.2 Array Exotic Objects

[...]

whenever an own property of an Array object is created or changed, other properties are adjusted as necessary to maintain this invariant. Specifically, whenever an own property is added whose name is an array index, the value of the length property is changed, if necessary, to be one more than the numeric value of that array index;

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

为什么push会推送到上一个索引?

由于 Foo 不是奇异对象,因此当使用 foo[1] = 'def 直接添加元素时,不会自动增加 length ',当您第二次尝试推送时,length 仍为 1。如果我们再次查看 Array#push:

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

[...]

  1. Let len be ToLength(Get(O, "length")).

[...]

  1. Let items be a List whose elements are, in left to right order, the arguments that were passed to this function invocation.

[...]

  1. Repeat, while items is not empty
    a. Remove the first element from items and let E be the value of the element.
    b. Let setStatus be Set(O, ToString(len), E, true).

因此,由于 foo[1] = 'def' 没有修改 length 属性,因此您的数组长度仍为 1,因此它将设置新的 to-be - 将索引 1 处的元素压入,因为长度为 1。

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

我为什么不应该这样做?

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

不过,您可以使用奇异对象,例如 Proxy对象来为 [[DefineOwnProperty]] 实现您自己的代码,例如模拟行为的数组。另一种方式,如loganfsmyth提到,你可以使用 ES2015 类来 extend and subclass builtin exotic object constructors从而正确模仿数组行为。

关于javascript - 为什么这个类似数组的对象会有这样的行为?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44815940/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com