为什么 Array.prototype.push 返回新长度而不是更有用的东西?

IT技术 javascript specifications return-type array-push ecma
2021-01-26 21:39:06

自从在ECMA-262, 3rd Edition 中引入以来,该Array.prototype.push方法的返回值是Number

15.4.4.7 Array.prototype.push ( [ item1 [ , item2 [ , … ] ] ] )

参数按照它们出现的顺序附加到数组的末尾。数组的新长度作为调用结果返回

返回数组的新长度背后的设计决策是什么,而不是返回一些可能更有用的东西,例如:

  • 对新附加项的引用
  • 变异数组本身

为什么要这样做,是否有关于如何做出这些决定的历史记录?

5个回答

在 TC39 的通信中心发布了这个,并且能够更多地了解这背后的历史:

push, pop, shift,unshift最初于 1997 年添加到 JS1.2 (Netscape 4) 中。

以 Perl 中类似命名的函数为模型。

JS1.2 推送遵循 Perl 4 约定返回最后推送的项目。在 JS1.3(Netscape 4.06 夏季 1998)中,将 push 更改为遵循 Perl 5 返回数组新长度的约定。

https://dxr.mozilla.org/classic/source/js/src/jsarray.c#804

/*
 * If JS1.2, follow Perl4 by returning the last thing pushed.  Otherwise,
 * return the new array length.
 */

我理解array.push()返回变异数组而不是新长度的期望并且出于链接原因希望使用此语法。
但是,有一种内置的方法可以做到这一点:array.concat(). 请注意,concat期望得到一个数组,而不是一个项目。因此,请记住将要添加的项目包装起来[],如果它们尚未在数组中。

newArray = oldArray.concat([newItem]);

数组链接可以通过 using 来完成.concat(),因为它返回一个数组,
但不能通过 来完成.push(),因为它返回一个整数(数组的新长度)。

以下是用于React根据state变量的先验值更改变量的常见模式

// the property value we are changing
selectedBook.shelf = newShelf;
       
this.setState((prevState) => (
  {books: prevState.books
           .filter((book) => (book.id !== selectedBook.id))
           .concat(selectedBook)
  }
));

stateobject 有一个books属性,该属性包含一个book.
book是一个具有id, 和shelf属性(等等)的对象。
setState()接收一个包含要分配给的新值的对象state

selectedBook已在books数组中,但其属性shelf需要更改。然而,
我们只能给出setState一个顶级对象。
我们不能告诉它去寻找这本书,并在这本书上寻找一个属性,并赋予它这个新值。

所以我们按books原样使用数组。
filter删除 的旧副本selectedBook
然后在更新它的属性concat重新添加selectedBookshelf

想要链接push.
但是,正确的方法实际上是使用concat.

总结:
array.push()将返回一个数字(变异数组的新长度)。
array.concat([])将返回一个新的“变异数组。
从技术上讲,它返回一个新的数组,其中添加了修改后的元素,并保持初始数组不变。返回一个新的数组实例,与回收现有数组实例相反,这是一个重要的区别,这使得 React 应用程序中的状态对象非常有用,可以获取更改的数据以重新渲染。

是的 - 为了记录,我认为你也可以连接一个项目 [].concat(3) 和 [].concat([3]) 都做同样的事情。
2021-03-14 21:39:06
我认为你有一个错字@Tao。它应该是oldArray.concat(newItem)
2021-03-19 21:39:06
嗯,我不明白。oldArray.concat[newItem];在任何普通的 JS 环境中为我返回 undefined 。2ality.com/2016/11/concat-array-literal.html 上有一个有趣的相关讨论,但没有解释您的语法如何工作。我认为这是特定于 React 的事情?一些魔法弄乱了数组原型?
2021-03-21 21:39:06
谢谢@NickManning,这确实是重点;在我发表评论大约 5 个月后,@SherylHohman 编辑了答案,将明显错误的newArray = oldArray.concat[newItem];更改为更正的newArray = oldArray.concat([newItem]);,从而回答了我的问题。然而,他们确实在底部的摘要中留下了错误的语法,我现在也更正了。我的困惑得到了充分的解决。
2021-03-28 21:39:06

我无法解释为什么他们选择返回新的长度,但为了回应您的建议:

  • 返回新附加的项目:

鉴于 JavaScript 使用发出分配值的 C 样式赋值(与不发出分配值的基本样式赋值相反),您仍然可以具有该行为:

var addedItem;
myArray.push( addedItem = someExpression() );

(尽管我承认这确实意味着您不能将它作为声明+赋值组合中 r 值的一部分)

  • 返回变异数组本身:

这将是在 ECMAScript 3 完成后显着流行的“流畅”API 的风格,它不会保持 ECMAScript 中其他库功能的风格,同样,启用您通过创建自己的push方法追求的场景

Array.prototype.push2 = function(x) {
    this.push(x);
    return this;
};

myArray.push2( foo ).push2( bar ).push2( baz );

或者:

Array.prototype.push3 = function(x) {
    this.push(x);
    return x;
};

var foo = myArray.push3( computeFoo() );
Re fluent APIs:改变数组的方法都不会返回该数组。如果您主要将它们用于副作用,它们实际上不应该是可链接的。
2021-03-14 21:39:06

既然你问了,我就很好奇。我制作了一个示例数组并在 Chrome 中对其进行了检查。

var arr = [];
arr.push(1);
arr.push(2);
arr.push(3);
console.log(arr);

在此处输入图片说明

由于我已经引用了数组以及我推入其中的每个对象,因此只有一个其他属性可能有用……长度。通过返回 Array 数据结构的这一附加值,我现在可以访问所有相关信息。这似乎是最好的设计选择。如果您想为了节省 1 条机器指令而争论,那或者什么都不返回。

为什么要这样做,是否有关于如何做出这些决定的历史记录?

没有线索 - 我不确定这些方面的基本原理记录是否存在。这取决于实施者,并且可能会在任何实施 ECMA 脚本标准的给定代码库中进行注释。

[1,2,3].map(x => x+=4).push(1) //=== 4,如果返回[5,6,7,1]就太好了,不会? 但是... [1,2,3].map(x => x+=4).concat(1) //=== [5, 6, 7, 1]
2021-03-20 21:39:06

我不知道“为什么要这样做,是否有关于如何做出这些决定的历史记录?” .

但我也认为这是不明确不直观的是推()返回数组的长度象下面这样:

let arr = ["a", "b"];

let test = arr.push("c");

console.log(test); // 3

然后,如果你想用清晰直观的方法,而不是推() 您可以使用CONCAT()返回其值的数组象下面这样:

let arr = ["a", "b"];

let test = arr.concat("c");

console.log(test); // ["a", "b", "c"]