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

Why does Array.prototype.push return the new length instead of something more useful?

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

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

The arguments are appended to the end of the array, in the order in which they appear. The new length of the array is returned as the result of the call.

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

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

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

  • 正在返回新添加的项目:

鉴于 JavaScript 使用 C 风格的赋值来发出分配的值(相对于 Basic 风格的赋值,它不会)你仍然可以有这种行为:

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

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

  • 返回变异数组本身:

那将是 "fluent" API 的风格,它在 ECMAScript 3 完成后获得了显着的流行,它不会保持 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() );

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

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

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

Why was it done like this, and is there a historical record of how these decisions came to be made?

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

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

newArray = oldArray.concat([newItem]);

数组链接可以通过使用 .concat() 来完成,因为它 return 是一个数组,
但不是 .push(),因为它 return 是一个整数(数组的新长度)。

这是 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)
  }
));

state 对象有一个 books 属性,它包含一个 book.
的数组 book 是具有 idshelf 属性(以及其他属性)的对象。
setState() 接受一个对象,该对象包含要分配给 state

的新值

selectedBook 已经在 books 数组中,但它的 属性 shelf 需要更改。
但是,我们只能给 setState 一个顶级对象。
我们不能告诉它去找那本书,在那本书上寻找 属性,然后给它这个新值。

所以我们照原样使用 books 数组。
filter 删除 selectedBook 的旧副本。
然后 concat 重新添加 selectedBook ,更新后是 shelf 属性.

想要链接的绝佳用例 push
然而,正确的做法实际上是 concat.

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

posted this in TC39's communication hub,并且能够更多地了解这背后的历史:

push, pop, shift, unshift were originally added to JS1.2 (Netscape 4) in 1997.

There were modeled after the similarly named functions in Perl.

JS1.2 push followed the Perl 4 convention of returning the last item pushed. In JS1.3 (Netscape 4.06 summer 1998) changed push to follow the Perl 5 conventions of returning the new length of the array.

see 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.
 */

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

但我也认为不清楚并且不直观push()returns 数组的长度 如下所示:

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

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

console.log(test); // 3

然后,如果你想使用clear直观的方法而不是push(),您可以使用 concat() 其中 returns 数组及其值 如下所示:

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

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

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

问题在您提到的文档(Ecma 262 第 3 版)中得到了部分回答,有些方法会改变数组,有些方法不会。改变数组的方法将 return 改变数组的长度。用于添加将被推入、拼接和取消移位的元素(取决于您希望新元素进入的位置)。

如果你想获得新的变异数组,你可以使用concat。 Concat 将输入您想要添加到原始数组的任意数量的数组,并将所有元素添加到一个新数组中。即:

const array1 = ['a', 'b', 'c'];
const array2 = ['d', 'e', 'f'];
const array3=['g','h'];
const array4 = array1.concat(array2,array3);

创建的新数组将包含所有元素,其他三个不会更改。还有其他(许多)方法可以将元素添加到数组中,既可以是可变的,也可以是非可变的。所以这是你的答案,它 return 的长度是因为它正在改变它,它不需要 return 整个数组。