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

How to extend an existing JavaScript array with another array, without creating a new array

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

我希望实现以下目标:

1
2
3
4
5
6
7
>>> 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的算法)有效工作的算法。

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


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

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

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

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

或者,如果你觉得更清楚的话:

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

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


update 2018:a better answer is a newer one of mine:a.push(...b).不要再对这个问题投反对票了,因为它从来没有真正回答过这个问题,但这是2015年谷歌第一次被点击的黑客行为:)

对于那些只搜索"javascript数组扩展"并到达这里的用户,您可以很好地使用Array.concat

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

concat将返回新数组的副本,因为线程启动程序不需要。但是你可能不在乎(当然对于大多数用途来说,这是可以的)。

还有一些很好的ecmascript 6糖,以spread operator的形式:

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

(它也复制。)


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

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

1
2
3
4
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);
}

然后可以执行以下操作:

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

Dzinx的回答(使用push.apply)和其他基于.apply的方法在我们附加的数组很大时失败(测试表明,对于我来说,Chrome的大值大于150000个条目,而firefox的大值大于500000个条目)。您可以在这个JSPerf中看到这个错误。

当以大数组作为第二个参数调用"function.prototype.apply"时,由于超出了调用堆栈大小,因此发生错误。(MDN注意到使用function.prototype.apply超过调用堆栈大小的危险-请参阅标题为"应用和内置函数"的部分。)

为了与本页上的其他答案进行速度比较,请查看这个JSperf(感谢eaterofcode)。基于循环的实现在速度上与使用Array.push.apply相似,但往往比Array.slice.apply慢一点。

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

顺便说一下,别被诱惑了(就像我以前那样!)要进一步缩短foreach实现,请执行以下操作:

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

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


我觉得现在最优雅的是:

1
arr1.push(...arr2);

MDN关于Spread Operator的文章在ES2015(ES6)中提到了这种甜蜜的方式:

A better push

Example: push is often used to push an array to the end of an existing
array. In ES5 this is often done as:

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

In ES6 with spread this becomes:

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

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


在javascript中,首先是几个关于apply()的单词,以帮助理解我们为什么要使用它:

The apply() method calls a function with a given this value, and
arguments provided as an array.

push需要向数组中添加一个项列表。但是,apply()方法将函数调用的预期参数作为数组。这使得我们可以使用内置的push()方法轻松地将一个数组的元素push转换为另一个数组。

假设您有这些数组:

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

只需这样做:

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

结果将是:

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

同样的事情也可以在ES6中使用如下的排列运算符("...):

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

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

另外,如果要将所有内容从数组b移动到a,在过程中清空b,可以这样做:

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

结果如下:

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


如果您想使用jquery,这里有$.merge()。

例子:

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

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


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

1
2
3
4
Array.prototype.append = function(array)
{
    this.push.apply(this, array)
}

像这样使用

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

a.append(b)


使用splice()可以做到:

1
2
3
4
5
b.unshift(b.length)
b.unshift(a.length)
Array.prototype.splice.apply(a,b)
b.shift() // Restore b
b.shift() //

但是,尽管它比push.apply更丑,至少在火狐3.0中不会更快。


此解决方案对我有效(使用ecmascript 6的spread运算符):

1
2
3
4
5
6
7
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);


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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(' ') + '
'
;
}
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('
Extend'
);
print('(1)', a.extend(b));
print('(2)', a.extend(b));
print('(3)', a.extend(4, 5, 6));
1
2
3
4
body {
  font-family: monospace;
  white-space: pre;
}


我能看到非常好的答案,这取决于你的数组大小,你的要求和目标。它可以用不同的方式来完成。

我建议使用javascript for循环:

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

for (i = 0; i < b.length; i++) {
    a.push(b[i]);
}

console.log(a)输出将

1
Array [ 1, 2, 3, 4, 5 ]

然后你就可以做你自己的功能了:

1
2
3
4
5
6
function extendArray(a, b){
    for (i = 0; i < b.length; i++) {
        a.push(b[i]);
    }
    return a;
}

console.log(extendArray(a, b));输出将

1
Array [ 1, 2, 3, 4, 5 ]


组合答案…

1
2
3
4
5
6
7
8
9
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]);
        };
    }  
}


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

1
2
3
4
5
6
7
8
9
10
11
12
13
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]);
  }
 }
};

然后调用并打印为:

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

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


对于>150000条记录,使用Array.extend而不是Array.push

1
2
3
4
5
6
7
8
9
10
11
12
13
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;
  };
}

答案非常简单。

1
2
3
4
5
6
7
8
9
10
11
>>> 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函数的参数的组合。关键是您必须将返回值赋给一个变量,否则它会丢失。例如

1
a.concat(b);  <--- This does absolutely nothing since it is just returning the combined arrays, but it doesn't do anything with it.


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

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

在对此进行了一些性能测试之后,它的速度非常慢,但回答了有关不创建新数组的问题。concat的速度明显更快,甚至jquery的$.merge()也会大惊小怪。

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