如何在不创建新数组的情况下用另一个数组扩展现有的JavaScript数组


970

似乎没有办法用另一个数组扩展现有的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)的情况下有效。

注意:不是如何向数组添加内容的重复项-这里的目标是将一个数组的全部内容添加到另一个数组中,并“就地”执行,即不复制扩展数组的所有元素。


5
来自@Toothbrush对答案的评论:a.push(...b)。它的概念与最高答案相似,但已针对ES6更新。
tscizzle

Answers:


1497

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

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

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

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

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

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

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


3
我认为这是您最好的选择。任何其他事情都会涉及到apply()的迭代或其他作用
Peter Bailey

44
如果“ b”(要扩展的数组)很大(根据我的测试,Chrome中的大约150000个条目),则此答案将失败。您应该使用for循环,甚至最好在“ b”上使用内置的“ forEach”函数。见我的答案:stackoverflow.com/questions/1374126/…–
jcdude

35
@Deqing:数组的push方法可以接受任意数量的参数,然后将其推入数组的后面。因此a.push('x', 'y', 'z'),有效通话将扩展a3个元素。apply是任何采用数组并使用其元素的函数的方法,就好像它们都是作为函数的位置元素而明确给出的一样。因此a.push.apply(a, ['x', 'y', 'z']),还将数组扩展3个元素。此外,apply将上下文作为第一个参数(我们a再次传递到该位置以附加到a)。
DzinX

注意:在这种情况下,第一个返回值不是[1,2,3,4,5],而是5,而之后== [1,2,3,4,5]。

1
另一个不太令人困惑(且时间不长)的调用将是:[].push.apply(a, b)
niry

259

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


对于那些仅搜索“ JavaScript array extension”并到达此处的用户,您可以很好地使用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];

(它也会复制。)


43
问题清楚地表明:“是否不创建新数组?”
威尔特

10
@Wilt:的确如此,答案说明了为什么:)这是很长一段时间以来首次出现在Google上的。另外,其他解决方案也很丑陋,需要一些意识形态和美观的解决方案。尽管如今,arr.push(...arr2)对于这个特定问题,它是一种更新,更好和技术上正确的答案。
odinho-Velmont

7
我听你的,但我搜索“的JavaScript追加数组,而无需创建一个新的数组”,那么它是伤心地看到,一个60倍upvoted答案是高达没有回答的实际问题;)我没有downvote,因为您在回答中明确了。
Wilt

202

您应该使用基于循环的技术。此页面上基于使用的其他答案.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当我们要追加的数组很大时(测试表明对我来说,大的在Chrome中大约大于150,000,在Firefox中大约大于500,000 ),DzinX的答案(使用push.apply)和其他基于方法的方法都会失败。您可以在此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);
}

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


1
ps以测试other_array是否确实是一个数组,请选择此处描述的选项之一:stackoverflow.com/questions/767486/…–
jcdude

6
好答案。我不知道这个问题。我在这里引用了您的答案:stackoverflow.com/a/4156156/96100
Tim Down

2
.push.apply实际上比.forEachv8 快得多,最快的仍然是内联循环。
本杰明·格林鲍姆

1
@BenjaminGruenbaum-您能发布指向显示结果的链接吗?据我从此评论结尾处链接的jsperf收集的结果来看,使用.forEach速度比.push.applyChrome / Chromium(自v25开始的所有版本)都快。我无法单独测试v8,但如果需要请链接结果。参见jsperf:jsperf.com/array-extending-push-vs-concat/5
jcdude 2014年

1
@jcdude您的jsperf无效。因为数组中的所有元素都是undefined,所以.forEach将跳过它们,使其成为最快的元素。.splice实际上是最快的?!jsperf.com/array-extending-push-vs-concat/18
EaterOfCode 2014年

152

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

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它不能很大(将其保留在大约100000个项目以下),因为根据jcdude的回答,调用堆栈会溢出。


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

使用时有一个方便,.concat那就是第二个参数一定不能是数组。这可以通过将扩展操作符与结合使用来实现concat,例如arr1.push(...[].concat(arrayOrSingleItem))
Steven Pribilinskiy

对于目标数组不为空的情况,这是一种现代,优雅的Javascript。
timbo

这样够快吗?
Roel

@RoelDelosReyes:是的。
odinho-Velmont,

46

首先apply()在JavaScript中讲几句话,以帮助我们理解为什么要使用它:

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

推期望项目列表添加到数组。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]; 

更短,更好,但目前尚未在所有浏览器中完全支持。

另外,如果您希望所有内容从数组b移到ab在此过程中清空,则可以执行以下操作:

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

结果将如下所示:

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

1
还是while (b.length) { a.push(b.shift()); },对吗?
LarsW


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)

6
如果您的“数组”参数是一个大数组(例如,Chrome中的〜150000个条目),则不应该使用push.apply方法,因为它会导致堆栈溢出(并因此失败)。您应该使用“ array.forEach”-请参阅我的答案:stackoverflow.com/a/17368101/1280629
jcdude

12

可以使用splice()

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

但是,尽管它比较难看,但它的速度并不比push.applyFirefox 快,至少在Firefox 3.0中没有。


9
我发现了同样的事情,在大约10,000个元素数组之前,拼接不会提供推动每个项目的性能增强jsperf.com/splice-vs-push
Drew

1
+1感谢您添加此功能和性能比较,从而节省了我测试此方法的工作。
jjrv

1
如果数组b大,则由于堆栈溢出而无法使用splice.apply或push.apply。它们也比使用“ for”或“ forEach”循环慢-请参阅此jsPerf:jsperf.com/array-extending-push-vs-concat/5和我的回答 stackoverflow.com/questions/1374126/...
jcdude

4

此解决方案对我有用(使用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);


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


0

结合答案...

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

9
不,没有必要这样做。使用forEach的for循环比使用push.apply更快,并且无论要扩展的数组长度是多少都可以工作。看看我经过修改的答案:stackoverflow.com/a/17368101/1280629 无论如何,您如何知道150000是适用于所有浏览器的正确数字?这是软糖。
jcdude

大声笑。我的答案无法辨认,但似乎是当时找到的其他答案的组合-即摘要。不用担心
zCoder

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]


-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.

2
正如在MDN 的文档中Array.prototype.concat()明确指出的那样,这将返回一个新的Array,它不会像OP明确要求的那样追加到现有数组中。
威尔特

-2

使用Array.extend的,而不是Array.push为> 15万分的记录。

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

-6

超级简单,如果有问题,就不用依靠散布算子或套用。

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

在对此进行了一些性能测试之后,它的运行速度非常慢,但是回答了有关不创建新阵列的问题。Concat的速度要快得多,甚至jQuery $.merge()也可以使它更快。

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


4
如果不使用返回值,则应该使用.forEach而不是.map。它更好地传达了代码的意图。
大卫,

@david-每个人都有。我更喜欢synax,.map但您也可以这样做b.forEach(function(x) { a.push(x)} )。实际上,我在jsperf测试中添加了它,它比map更快。还是超级慢。
elPastor

3
这不是优先选择的情况。这些“功能性”方法各自具有特定的含义。打电话.map到这里看起来很奇怪。这就像打电话b.filter(x => a.push(x))。它可以工作,但是却暗示着某些事情正在发生,这使读者感到困惑。
大卫,

2
@elPastor应该值得您花时间,因为像我这样的其他人会坐下来挠挠脖子​​,想知道他看不到的代码中还有什么发生。在大多数普通情况下,重要的是意图的明确性,而不是性能。请尊重那些阅读您的代码的人的时间,因为当您只是一个人时,他们可能会很多。谢谢。
Dmytro Y.

1
@elPastor我确切地知道是什么.map(),那是问题所在。这些知识使我认为您需要结果数组,否则使用它.forEach()来使读者对回调函数的副作用保持谨慎将是一件好事。严格来说,您的解决方案会创建一个额外的自然数数组(结果为push),而问题是:“未创建新数组”。
Dmytro Y.
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.