1251

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

我想实现以下目标:

>>> a = [1, 2]
[1, 2]
>>> b = [3, 4, 5]
[3, 4, 5]
>>> SOMETHING HERE
>>> a
[1, 2, 3, 4, 5]

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

注意:不是如何将某些内容附加到数组?-- 这里的目标是将一个数组的全部内容添加到另一个数组中,并“就地”完成,即不复制扩展数组的所有元素。

4

19 回答 19

1876

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

>>> a.push(...b)

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

>>> a.push.apply(a, b)

或者,如果您认为它更清楚:

>>> Array.prototype.push.apply(a,b)

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

于 2009-09-03T15:27:55.563 回答
316

2018 年更新更好的答案是我的一个较新的答案a.push(...b)。不要再支持这个了,因为它从来没有真正回答过这个问题,但它是 2015 年围绕谷歌首次点击的黑客:)


对于那些简单地搜索“JavaScript 数组扩展”并到达这里的人,您可以很好地使用Array.concat.

var a = [1, 2, 3];
a = a.concat([5, 4, 3]);

Concat 将返回一个新数组的副本,因为线程启动器不想要。但是您可能不在乎(当然对于大多数用途来说,这都可以)。


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

const a = [1, 2, 3];
const b = [...a, 5, 4, 3];

(它也复制。)

于 2015-07-20T16:15:17.563 回答
229

您应该使用基于循环的技术。此页面上基于 using 的其他答案.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);
}

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

var a = [1,2,3];
var b = [5,4,3];
a.extend(b);

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

当使用大数组作为第二个参数调用 'Function.prototype.apply' 时,由于超出调用堆栈大小而发生错误。(MDN 有一个关于使用Function.prototype.apply超出调用堆栈大小的危险的注释- 请参阅标题为“应用和内置函数”的部分。)

要与此页面上的其他答案进行速度比较,请查看此 jsperf(感谢 EaterOfCode)。基于循环的实现在速度上与 using 相似Array.push.apply,但往往比Array.slice.apply.

有趣的是,如果您要附加的数组是稀疏的,则上述forEach基于方法可以利用稀疏性并优于.apply基于方法;如果您想自己测试,请查看这个 jsperf 。

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

Array.prototype.extend = function (array) {
    array.forEach(this.push, this);
}

因为这会产生垃圾结果!为什么?因为Array.prototype.forEach为它调用的函数提供了三个参数——它们是:(element_value, element_index, source_array)。forEach如果您使用“forEach(this.push, this)”,所有这些都将被推送到您的第一个数组中!

于 2013-06-28T15:16:22.177 回答
175

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

arr1.push(...arr2);

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);

在 ES6 中,这变成了:

var arr1 = [0, 1, 2];
var arr2 = [3, 4, 5];
arr1.push(...arr2);

请注意,这arr2不能很大(保持在大约 100 000 个项目以下),因为根据 jcdude 的回答,调用堆栈溢出。

于 2016-02-05T11:27:59.383 回答
48

首先说几句关于apply()JavaScript 的内容,以帮助理解我们为什么使用它:

apply()方法调用具有给定this值的函数,并以数组形式提供参数。

Push 期望将项目列表添加到数组中。但是,该apply()方法将函数调用的预期参数作为一个数组。这使我们可以使用内置方法轻松地push将一个数组的元素放入另一个数组。push()

想象一下你有这些数组:

var a = [1, 2, 3, 4];
var b = [5, 6, 7];

并简单地这样做:

Array.prototype.push.apply(a, b);

结果将是:

a = [1, 2, 3, 4, 5, 6, 7];

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

a.push(...b); //a = [1, 2, 3, 4, 5, 6, 7]; 

更短更好,但目前并非所有浏览器都完全支持。

此外,如果您想所有内容从 array移动ba,在此过程中清空b,您可以这样做:

while(b.length) {
  a.push(b.shift());
} 

结果如下:

a = [1, 2, 3, 4, 5, 6, 7];
b = [];
于 2017-05-25T13:05:05.270 回答
42

概述

  • 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 文档:传播语法.apply()、. concat() 、. push( )

速度注意事项

每种方法都很快,a而且b都很小,因此在大多数 Web 应用程序中,您都希望使用push(...b)并完成它。

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

  • 您正在向大数组中添加一些元素
    push(...b)非常快
  • 你正在向一个大数组中添加许多元素
    concat比循环稍快
  • 您将许多元素添加到一个小数组中
    concat比循环快得多
  • 通常只向任何大小的数组添加少量元素
    → 循环的速度与用于少量添加的有限方法一样快,但即使在添加许多元素时它不是最高效的,也永远不会抛出异常
  • 您正在编写一个包装函数以始终获得最大性能
    →您需要动态检查输入的长度并选择正确的方法,也许在循环中调用push(...b_part)(使用 big 的切片)。b

这让我感到惊讶:我认为a=a.concat(b)可以很好地 memcpy on on 而b无需a像必须做的那样进行单独的扩展操作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);

参数和结果:

 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])

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

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

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

于 2021-05-28T12:02:12.867 回答
37

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

例子:

a = [1, 2];
b = [3, 4, 5];
$.merge(a,b);

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

于 2013-12-24T15:51:12.790 回答
19

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

Array.prototype.append = function(array)
{
    this.push.apply(this, array)
}

并像这样使用它

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

a.append(b)
于 2013-01-07T10:39:00.560 回答
14

可以使用splice()

b.unshift(b.length)
b.unshift(a.length)
Array.prototype.splice.apply(a,b) 
b.shift() // Restore b
b.shift() // 

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

于 2009-09-17T12:51:18.930 回答
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);

于 2018-10-05T18:33:45.543 回答
1

结合答案...

Array.prototype.extend = function(array) {
    if (array.length < 150000) {
        this.push.apply(this, array)
    } else {
        for (var i = 0, len = array.length; i < len; ++i) {
            this.push(array[i]);
        };
    }  
}
于 2013-09-11T10:10:59.263 回答
1

您可以为扩展创建一个 polyfill,如下所示。它将添加到数组中;就地并返回自身,以便您可以链接其他方法。

if (Array.prototype.extend === undefined) {
  Array.prototype.extend = function(other) {
    this.push.apply(this, arguments.length > 1 ? arguments : other);
    return this;
  };
}

function print() {
  document.body.innerHTML += [].map.call(arguments, function(item) {
    return typeof item === 'object' ? JSON.stringify(item) : item;
  }).join(' ') + '\n';
}
document.body.innerHTML = '';

var a = [1, 2, 3];
var b = [4, 5, 6];

print('Concat');
print('(1)', a.concat(b));
print('(2)', a.concat(b));
print('(3)', a.concat(4, 5, 6));

print('\nExtend');
print('(1)', a.extend(b));
print('(2)', a.extend(b));
print('(3)', a.extend(4, 5, 6));
body {
  font-family: monospace;
  white-space: pre;
}

于 2015-09-17T13:02:43.037 回答
1

我正在添加这个答案,因为尽管问题说明得很清楚without creating a new array,但几乎每个答案都忽略了它。

现代 JavaScript 可以很好地处理数组等可迭代对象。这使得实现一个基于此的版本成为可能concat,并在逻辑上跨越其参数的数组数据。

下面的示例使用了具有此类逻辑的iter-ops库:

import {pipe, concat} from 'iter-ops';

const i = pipe(
    originalArray,
    concat(array2, array3, array4, ...)
); //=> Iterable

for(const a of i) {
    console.log(a); // joint values from all arrays
}

上面,没有创建新数组。运算符concat将遍历原始数组,然后按指定顺序自动继续进入array2, then array3, 依此类推。

就性能和内存使用而言,这是连接数组的最有效方式。

如果最终决定将其转换为实际的物理数组,则可以通过标准扩展运算符来实现:

const fullArray = [...i]; // pulls all values from iterable, into a new array
于 2021-12-26T13:52:41.097 回答
0

合并两个以上数组的另一种解决方案

var a = [1, 2],
    b = [3, 4, 5],
    c = [6, 7];

// Merge the contents of multiple arrays together into the first array
var mergeArrays = function() {
 var i, len = arguments.length;
 if (len > 1) {
  for (i = 1; i < len; i++) {
    arguments[0].push.apply(arguments[0], arguments[i]);
  }
 }
};

然后调用并打印为:

mergeArrays(a, b, c);
console.log(a)

输出将是:Array [1, 2, 3, 4, 5, 6, 7]

于 2016-07-22T18:20:36.953 回答
-1

答案超级简单。

>>> a = [1, 2]
[1, 2]
>>> b = [3, 4, 5]
[3, 4, 5]
>>> SOMETHING HERE
(The following code will combine the two arrays.)

a = a.concat(b);

>>> a
[1, 2, 3, 4, 5]

Concat 的行为与 JavaScript 字符串连接非常相似。它将返回您在调用函数的数组末尾放入 concat 函数的参数的组合。关键是您必须将返回的值分配给变量,否则它会丢失。所以例如

a.concat(b);  <--- This does absolutely nothing since it is just returning the combined arrays, but it doesn't do anything with it.
于 2015-11-19T16:27:09.650 回答
-2

对于 > 150,000 条记录,请使用Array.extend而不是。Array.push

if (!Array.prototype.extend) {
  Array.prototype.extend = function(arr) {
    if (!Array.isArray(arr)) {
      return this;
    }

    for (let record of arr) {
      this.push(record);
    }

    return this;
  };
}
于 2019-01-07T01:31:16.013 回答
-3

您可以通过在该push()方法的帮助下简单地向数组添加新元素来做到这一点。

let colors = ["Red", "Blue", "Orange"];
console.log('Array before push: ' + colors);
// append new value to the array
colors.push("Green");
console.log('Array after push : ' + colors);

用于将元素附加到数组开头的另一种方法是 unshift() 函数,它添加并返回新长度。它接受多个参数,附加现有元素的索引,最后返回数组的新长度:

let colors = ["Red", "Blue", "Orange"];
console.log('Array before unshift: ' + colors);
// append new value to the array
colors.unshift("Black", "Green");
console.log('Array after unshift : ' + colors);

还有其他方法。你可以在这里查看它们。

于 2020-07-02T10:33:45.607 回答
-3

另一个选项,如果你安装了 lodash:

 import { merge } from 'lodash';

 var arr1 = merge(arr1, arr2);
于 2021-07-20T17:25:21.640 回答
-7

超级简单,不依赖传播运算符或应用,如果这是一个问题。

b.map(x => a.push(x));

在对此进行了一些性能测试之后,它非常慢,但回答了关于不创建新数组的问题。Concat 明显更快,即使是 jQuery 也很$.merge()赞。

https://jsperf.com/merge-arrays19b/1

于 2019-02-24T23:48:59.430 回答