如何使用另一个数组扩展现有JavaScript数组,而无需创建新数组

Dzi*_*inX 899 javascript arrays concatenation

似乎没有办法用另一个数组扩展现有的JavaScript数组,即模拟Python的extend方法.

我想实现以下目标:

>>> a = [1, 2]
[1, 2]
>>> b = [3, 4, 5]
[3, 4, 5]
>>> SOMETHING HERE
>>> a
[1, 2, 3, 4, 5]
Run Code Online (Sandbox Code Playgroud)

我知道有一种a.concat(b)方法,但它创建了一个新的数组,而不是简单地扩展第一个.我想要一种算法,当a它明显大于b(即不复制的算法)时,它可以有效地工作a.

注意:不是如何将某些内容附加到数组的副本 - 这里的目标是将一个数组的全部内容添加到另一个数组中,并"就地"执行,即不复制扩展数组的所有元素.

Dzi*_*inX 1381

.push方法可以采用多个参数.您可以使用spread运算符将第二个数组的所有元素作为参数传递给.push:

>>> a.push(...b)
Run Code Online (Sandbox Code Playgroud)

如果您的浏览器不支持ECMAScript 6,则可以使用.apply:

>>> a.push.apply(a, b)
Run Code Online (Sandbox Code Playgroud)

或许,如果你认为它更清楚:

>>> Array.prototype.push.apply(a,b)
Run Code Online (Sandbox Code Playgroud)

请注意,如果数组b太长,所有这些解决方案都将失败并出现堆栈溢出错误(故障从大约100,000个元素开始,具体取决于浏览器).如果你不能保证b足够短,你应该使用另一个答案中描述的基于循环的标准技术.

  • 如果"b"(要延长的数组)很大(根据我的测试,Chrome中约> 150000个条目),此答案将失败.你应该使用for循环,或者甚至更好地使用"b"上的内置"forEach"函数.见我的回答:http://stackoverflow.com/questions/1374126/how-to-append-an-array-to-an-existing-javascript-array/17368101#17368101 (41认同)
  • @Deqing:Array的`push`方法可以接受任意数量的参数,然后将它们推送到数组的后面.所以`a.push('x','y','z')`是一个有效的调用,将`a`扩展为3个元素.`apply`是一个任何函数的方法,它接受一个数组并使用它的元素,好像它们都被明确地作为函数的位置元素给出.所以`a.push.apply(a,['x','y','z'])`也会将数组扩展3个元素.另外,`apply`将上下文作为第一个参数(我们再次传递`a`以附加到'a`). (32认同)
  • 我认为这是你最好的选择.其他任何事情将涉及迭代或另一次施加apply() (3认同)
  • 有人可以解释为什么这有效吗?谢谢 (3认同)
  • 另一个稍微不那么令人困惑(也不会太长)的调用是:`[].push.apply(a, b)`。 (2认同)

odi*_*ont 245

更新2018年:更好的答案是我的新人:a.push(...b).不要再投票了,因为它从来没有真正回答过这个问题,但这是2015年的第一次攻击谷歌的黑客攻击:)


对于那些只是搜索"JavaScript数组扩展"并到达这里的人,你可以很好地使用它Array.concat.

var a = [1, 2, 3];
a = a.concat([5, 4, 3]);
Run Code Online (Sandbox Code Playgroud)

Concat将返回一个新数组的副本,因为线程启动程序不想要.但你可能不在乎(当然对于大多数用途来说这样会很好).


以扩展运算符的形式还有一些不错的ECMAScript 6糖:

const a = [1, 2, 3];
const b = [...a, 5, 4, 3];
Run Code Online (Sandbox Code Playgroud)

(它也复制.)

  • 问题显然是:*"没有创建新阵列?"* (40认同)
  • @Wilt:这是真的,答案说为什么虽然:)这是谷歌很长一段时间以来首次出现.另外,其他解决方案都很丑陋,想要一些理想而且好看的东西.虽然现在`arr.push(... arr2)`更新更好,技术上更正(tm)回答了这个特殊问题. (9认同)
  • 我听到你了,但是我搜索了"javascript追加数组而没有创建一个新数组"*,然后很遗憾看到高达60次的upvoted答案没有回答实际问题;)我没有downvote,因为你在答案中说清楚了. (7认同)
  • @Wilt 和我搜索了“js extends array with array”并找到了这里,谢谢你:) (4认同)

jcd*_*ude 193

您应该使用基于循环的技术.此页面上基于使用的其他答案.apply可能会因大型阵列而失败.

一个相当简洁的基于循环的实现是:

Array.prototype.extend = function (other_array) {
    /* You should include a test to check whether other_array really is an array */
    other_array.forEach(function(v) {this.push(v)}, this);
}
Run Code Online (Sandbox Code Playgroud)

然后,您可以执行以下操作:

var a = [1,2,3];
var b = [5,4,3];
a.extend(b);
Run Code Online (Sandbox Code Playgroud)

.apply当我们追加的阵列很大时,DzinX的答案(使用push.apply)和其他基础方法都失败了(测试表明,对我来说,Chrome中大约150,000个条目,Firefox中大于500,000个条目).您可以在此jsperf中看到此错误.

发生错误是因为当使用大数组作为第二个参数调用'Function.prototype.apply'时,超出了调用堆栈大小.(MDN记录了使用Function.prototype.apply超出调用堆栈大小的危险- 请参阅标题为"应用和内置函数"的部分.)

要与本页面上的其他答案进行速度比较,请查看此jsperf(感谢EaterOfCode).基于循环的实现在使用速度上类似Array.push.apply,但往往比它慢一点Array.slice.apply.

有趣的是,如果你追加的数组是稀疏的,那么forEach上面的基础方法可以利用稀疏性并优于.apply基础方法; 如果你想亲自测试一下,请查看这个jsperf.

顺便说一句,不要被诱惑(像我一样!)进一步缩短forEach实现:

Array.prototype.extend = function (array) {
    array.forEach(this.push, this);
}
Run Code Online (Sandbox Code Playgroud)

因为这会产生垃圾结果!为什么?因为Array.prototype.forEach它调用的函数提供了三个参数 - 这些参数是:(element_value,element_index,source_array).forEach如果你使用"forEach(this.push,this)",所有这些都将被推送到你的第一个数组中进行每次迭代!

  • 好答案.我不知道这个问题.我在这里引用了你的答案:http://stackoverflow.com/a/4156156/96100 (5认同)
  • `.push.apply`实际上比v8中的`.forEach`快得多,最快的仍然是内联循环. (2认同)

odi*_*ont 150

这些天我觉得最优雅的是:

arr1.push(...arr2);
Run Code Online (Sandbox Code Playgroud)

有关传播运营商MDN文章在ES2015(ES6)中提到了这种不错的含糖方式:

更好的推动

示例:push通常用于将数组推送到现有数组的末尾.在ES5中,这通常是这样做的:

var arr1 = [0, 1, 2];
var arr2 = [3, 4, 5];
// Append all items from arr2 onto arr1
Array.prototype.push.apply(arr1, arr2);
Run Code Online (Sandbox Code Playgroud)

在带有传播的ES6中,这变为:

var arr1 = [0, 1, 2];
var arr2 = [3, 4, 5];
arr1.push(...arr2);
Run Code Online (Sandbox Code Playgroud)

请注意arr2不能很大(保持在大约10万个项目下),因为调用堆栈溢出,根据jcdude的答案.

  • 只是我刚刚犯下的一个错误的警告。ES6 版本非常接近 `arr1.push(arr2)`。这可能是一个像 `arr1 = []; 这样的问题。arr2=['a', 'b', 'd']; arr1.push(arr2)` 结果是数组 `arr1 == [['a','b','d']]` 的数组,而不是两个数组的组合。这是一个很容易犯的错误。出于这个原因,我更喜欢你下面的第二个答案。http://stackoverflow.com/a/31521404/4808079 (2认同)

Ali*_*eza 45

首先apply()在JavaScript中说几句,以帮助理解我们使用它的原因:

apply()方法调用具有给定this值的函数,并将参数作为数组提供.

Push需要一个要添加到数组的项列表.apply()但是,该方法将函数调用的预期参数作为数组.这允许我们push使用内置push()方法将一个数组的元素轻松地转换为另一个数组.

想象一下,你有这些数组:

var a = [1, 2, 3, 4];
var b = [5, 6, 7];
Run Code Online (Sandbox Code Playgroud)

并简单地这样做:

Array.prototype.push.apply(a, b);
Run Code Online (Sandbox Code Playgroud)

结果将是:

a = [1, 2, 3, 4, 5, 6, 7];
Run Code Online (Sandbox Code Playgroud)

在ES6中可以使用扩展运算符(" ...")完成同样的事情,如下所示:

a.push(...b); //a = [1, 2, 3, 4, 5, 6, 7]; 
Run Code Online (Sandbox Code Playgroud)

目前所有浏览器都更短,更好但不完全支持.

此外,如果您想所有内容从数组移动ba,b在此过程中清空,您可以执行以下操作:

while(b.length) {
  a.push(b.shift());
} 
Run Code Online (Sandbox Code Playgroud)

结果如下:

a = [1, 2, 3, 4, 5, 6, 7];
b = [];
Run Code Online (Sandbox Code Playgroud)

  • 或 `while (b.length) { a.push(b.shift()); }`,对吗? (2认同)

Jul*_*lle 37

如果你想使用jQuery,有$ .merge()

例:

a = [1, 2];
b = [3, 4, 5];
$.merge(a,b);
Run Code Online (Sandbox Code Playgroud)

结果:a = [1, 2, 3, 4, 5]


Piz*_*ola 19

我喜欢a.push.apply(a, b)上面描述的方法,如果你想要,你总是可以像这样创建一个库函数:

Array.prototype.append = function(array)
{
    this.push.apply(this, array)
}
Run Code Online (Sandbox Code Playgroud)

并像这样使用它

a = [1,2]
b = [3,4]

a.append(b)
Run Code Online (Sandbox Code Playgroud)

  • 不应该使用push.apply方法,因为如果你的"数组"参数是一个大数组(例如Chrome中的> ~150000条目),它可能导致堆栈溢出(因此失败).你应该使用"array.forEach" - 请参阅我的回答:http://stackoverflow.com/a/17368101/1280629 (6认同)

gma*_*man 19

正如投票最高的答案所说,a.push(...b)考虑到大小限制问题,这可能是正确的答案。

另一方面,一些关于性能的答案似乎已经过时了。

以下数字适用于 2022 年 5 月 20 日

在此输入图像描述

在此输入图像描述

在此输入图像描述

这里

看来push2022 年是全面最快的。未来这种情况可能会改变。

忽略问题(生成新数组)的答案没有抓住要点。由于可能存在对同一数组的其他引用,因此许多代码可能需要/想要就地修改数组

let a = [1, 2, 3];
let b = [4, 5, 6];
let c = a;
a = a.concat(b);    // a and c are no longer referencing the same array
Run Code Online (Sandbox Code Playgroud)

这些其他引用可能深入某个对象,在闭包中捕获的东西等等......

作为一个可能很糟糕的设计,但作为一个例子,想象一下你有

const carts = [
  { userId: 123, cart: [item1, item2], },
  { userId: 456, cart: [item1, item2, item3], },
];
Run Code Online (Sandbox Code Playgroud)

和一个函数

function getCartForUser(userId) {
  return customers.find(c => c.userId === userId);
}
Run Code Online (Sandbox Code Playgroud)

然后您想将商品添加到购物车

const cart = getCartForUser(userId);
if (cart) {
  cart.concat(newItems);      // FAIL 
  cart.push(...newItems);     // Success! 
}
Run Code Online (Sandbox Code Playgroud)

顺便说一句,建议修改的答案Array.prototype可以说是糟糕的建议。更改本机原型基本上是代码中的地雷。另一个实现可能与您的不同,因此它会破坏您的代码,或者您会破坏他们期望其他行为的代码。这包括是否/何时添加与您的本机实现发生冲突的本机实现。您可能会说“我知道我在使用什么,所以没问题”,目前这可能是真的,您是一个开发人员,但添加第二个开发人员,他们无法读懂您的想法。而且,您是几年后的第二个开发人员,当您忘记了然后将其他一些库(分析?,日志记录?,...)移植到您的页面上并忘记了您留在代码中的头脑时。

这不仅仅是理论。网上有无数关于人们陷入这些地雷的故事。

可以说,修改本机对象的原型只有一些安全用途。一种是在旧浏览器中填充现有的指定实现。在这种情况下,规范已定义,规范已在新浏览器中实现,您只想在旧浏览器中获得相同的行为。那是相当安全的。预修补(规范正在进行中但尚未发货)可以说是不安全的。发货前规格会发生变化。


Luc*_*Luc 17

概述

  • a.push(...b) - 有限、快速、现代的语法
  • a.push.apply(a, b) - 有限,快速
  • a = a.concat(b)无限制,如果a很大则慢
  • for (let i in b) { a.push(b[i]); }- 无限制,如果b很大则慢

每个片段都会修改a以扩展为b.

“有限”片段将每个数组元素作为参数传递,并且可以传递给函数的参数的最大数量是有限的。从该链接看来,a.push(...b)直到有大约 32k 个元素b(大小a无关紧要)之前,这似乎是可靠的。

相关MDN文档:传播语法。适用() .concat() .push()

速度考虑

每个方法是快,如果这两个ab小,所以在大多数Web应用程序,你会想使用push(...b),并用它做。

如果你处理的元素超过几千个,你想做什么取决于具体情况:

  • 你在一个大数组中添加了几个元素
    push(...b)非常快
  • 你在一个大数组中添加了许多元素
    concat比循环稍快
  • 您正在向一个小数组中添加许多元素
    concat比循环快得多

这让我很吃惊:我想a=a.concat(b)就能够做的很好的memcpyba不打扰做单独的扩展操作的a.push(...b)必须做的,从而始终是最快的。相反,a.push(...b)速度要快得多,尤其是当a它很大时。

在 Linux 上的 Firefox 88 中使用以下方法测量了不同方法的速度:

a = [];
for (let i = 0; i < Asize; i++){
  a.push(i);
}
b = [];
for (let i = 0; i < Bsize; i++){
  b.push({something: i});
}
t=performance.now();
// Code to test
console.log(performance.now() - t);
Run Code Online (Sandbox Code Playgroud)

参数和结果:

 ms | Asize | Bsize | code
----+-------+-------+------------------------------
 ~0 |  any  |  any  | a.push(...b)
 ~0 |  any  |  any  | a.push.apply(a, b)
480 |   10M |    50 | a = a.concat(b)
  0 |   10M |    50 | for (let i in b) a.push(b[i])
506 |   10M |  500k | a = a.concat(b)
882 |   10M |  500k | for (let i in b) a.push(b[i])
 11 |    10 |  500k | a = a.concat(b)
851 |    10 |  500k | for (let i in b) a.push(b[i])
Run Code Online (Sandbox Code Playgroud)

请注意, aBsize为 500 000 是我系统上所有方法接受的最大值,这就是为什么它小于Asize.

所有测试都运行多次,以查看结果是否异常或具有代表性。当然,快速方法在使用 的一次运行中几乎无法估量performance.now(),但是由于慢速方法是如此明显并且两种快速方法都以相同的原理工作,因此我们不必费心重复很多次来分裂头发。

concat如果任一数组很大,该方法总是很慢,但只有在必须执行大量函数调用并且不关心有多大时,循环才会很慢a。因此,循环类似于push(...b)push.apply用于小bs,但如果它变大则不会中断;然而,当你接近极限时,concat又快了一点。


Raf*_*ird 12

可以使用以下方法完成splice():

b.unshift(b.length)
b.unshift(a.length)
Array.prototype.splice.apply(a,b) 
b.shift() // Restore b
b.shift() // 
Run Code Online (Sandbox Code Playgroud)

但是,尽管它更加丑陋,但它并不快push.apply,至少在Firefox 3.0中没有.

  • 我发现了同样的事情,splice没有提供性能增强来推动每个项目直到大约10,000个元素阵列http://jsperf.com/splice-vs-push (9认同)
  • 如果数组 b 很大,则使用 splice.apply 或 push.apply 可能会由于堆栈溢出而失败。它们也比使用“for”或“forEach”循环慢 - 请参阅此 jsPerf:http://jsperf.com/array-extending-push-vs-concat/5 和我的回答 http://stackoverflow.com/问题/1374126/how-to-append-an-array-to-an-existing-javascript-array/17368101#17368101 (2认同)

Pan*_*ava 5

这个解决方案对我有用(使用 ECMAScript 6 的扩展运算符):

let array = ['my', 'solution', 'works'];
let newArray = [];
let newArray2 = [];
newArray.push(...array); // Adding to same array
newArray2.push([...array]); // Adding as child/leaf/sub-array
console.log(newArray);
console.log(newArray2);
Run Code Online (Sandbox Code Playgroud)