似乎没有办法用另一个数组扩展现有的 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
的算法)。
注意:这不是 How to append something to an array? 的副本——这里的目标是将一个数组的全部内容添加到另一个数组中,并且做到“就地”,即不复制扩展数组的所有元素。
a.push(...b)
。它在概念上与最佳答案相似,但针对 ES6 进行了更新。
.push
方法可以采用多个参数。您可以使用 spread operator 将第二个数组的所有元素作为参数传递给 .push
:
>>> a.push(...b)
如果您的浏览器不支持 ECMAScript 6,您可以改用 .apply
:
>>> a.push.apply(a, b)
或者,如果您认为它更清楚:
>>> Array.prototype.push.apply(a,b)
请注意,如果数组 b
太长(问题从大约 100,000 个元素开始,具体取决于浏览器),所有这些解决方案都将失败并出现堆栈溢出错误。
如果您不能保证 b
足够短,您应该使用另一个答案中描述的标准基于循环的技术。
2018 年更新:A better answer is a newer one of mine:a.push(...b)
。不要再支持这个了,因为它从来没有真正回答过这个问题,但它是 2015 年首次点击 Google 的黑客 :)
对于那些简单地搜索“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];
(它也复制。)
arr.push(...arr2)
更新更好,并且是对这个特定问题的技术正确(tm)答案。
您应该使用基于循环的技术。此页面上基于使用 .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);
DzinX's answer(使用 push.apply)和其他基于 .apply
的方法在我们要附加的数组很大时会失败(测试表明,对我来说,Chrome 中大约有 > 150,000 个条目,而 Firefox 中则 > 500,000 个条目) .您可以在 this jsperf 中看到此错误。
当使用大数组作为第二个参数调用 'Function.prototype.apply' 时,由于超出调用堆栈大小而发生错误。 (MDN 有一个关于使用 Function.prototype.apply 超出调用堆栈大小的危险的注释 - 请参阅标题为“应用和内置函数”的部分。)
要与此页面上的其他答案进行速度比较,请查看 this jsperf(感谢 EaterOfCode)。基于循环的实现与使用 Array.push.apply
的速度相似,但往往比 Array.slice.apply
慢一些。
有趣的是,如果您要附加的数组是稀疏的,则上面基于 forEach
的方法可以利用稀疏性并优于基于 .apply
的方法;如果您想自己测试一下,请查看 this jsperf。
顺便说一句,不要被诱惑(就像我一样!)将 forEach 实现进一步缩短为:
Array.prototype.extend = function (array) {
array.forEach(this.push, this);
}
因为这会产生垃圾结果!为什么?因为 Array.prototype.forEach
为其调用的函数提供了三个参数 - 它们是:(element_value, element_index, source_array)。如果您使用“forEach(this.push, this)”,所有这些都将在 forEach
的每次迭代中被推送到您的第一个数组中!
.push.apply
实际上比 v8 中的 .forEach
快得多,最快的仍然是内联循环。
.forEach
比 .push.apply
更快(在 v25 之后的所有版本中)。我无法单独测试 v8,但如果您有,请链接您的结果。请参阅 jsperf:jsperf.com/array-extending-push-vs-concat/5
undefined
,所以 .forEach
将跳过它们,使其最快。 .splice
实际上是最快的?! jsperf.com/array-extending-push-vs-concat/18
我觉得这几天最优雅的是:
arr1.push(...arr2);
MDN article on the spread operator 在 ES2015 (ES6) 中提到了这种很好的含糖方式:
更好的推送示例:推送通常用于将数组推送到现有数组的末尾。在 ES5 中,这通常是这样完成的: var arr1 = [0, 1, 2]; var arr2 = [3, 4, 5]; // 将 arr2 中的所有项目附加到 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 的回答,调用堆栈溢出。
arr1.push(arr2)
。这可能是像 arr1 = []; arr2=['a', 'b', 'd']; arr1.push(arr2)
这样的问题,结果是数组数组 arr1 == [['a','b','d']]
而不是两个数组组合。这是一个容易犯的错误。出于这个原因,我更喜欢下面的第二个答案。 stackoverflow.com/a/31521404/4808079
.concat
有一个方便之处,即第二个参数不能是数组。这可以通过将扩展运算符与 concat
结合来实现,例如 arr1.push(...[].concat(arrayOrSingleItem))
概述
a.push(...b) - 有限、快速、现代的语法
a.push.apply(a, b) - 有限,快速
= a.concat(b) 无限制,如果 a 很大则慢
for (let i in b) { a.push(b[i]); } - 无限制,如果 b 很大则慢
每个片段都会修改 a
以使用 b
进行扩展。
“有限”片段将每个数组元素作为参数传递,the maximum number of arguments you can pass to a function is limited。从该链接来看,a.push(...b)
似乎是可靠的,直到 b
中有大约 32k 个元素(a
的大小无关紧要)。
相关 MDN 文档:spread syntax、.apply()、.concat()、.push()
速度注意事项
如果 a
和 b
都很小,则每种方法都很快,因此在大多数 Web 应用程序中,您将希望使用 push(...b)
并完成它。
如果您要处理的元素超过几千个,那么您想要做什么取决于具体情况:
您正在向大数组中添加一些元素 → push(...b) 非常快
您正在向大型数组中添加许多元素→ concat 比循环稍快
您正在向一个小数组中添加许多元素→ concat 比循环快得多
您通常只向任何大小的数组添加少量元素 → 循环的速度与用于少量添加的有限方法一样快,但即使在添加许多元素时它不是最高效的,也永远不会抛出异常
您正在编写一个包装函数以始终获得最大性能→您需要动态检查输入的长度并选择正确的方法,也许调用 push(...b_part) (带有大 b 的切片)一个循环。
这让我感到惊讶:我认为 a=a.concat(b)
能够很好地将 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])
请注意,500 000 的 Bsize
是我系统上所有方法接受的最大值,这就是它小于 Asize
的原因。
所有测试都运行了多次,以查看结果是异常值还是具有代表性。当然,使用 performance.now()
一次运行,快速方法几乎无法测量,但是由于慢速方法非常明显,并且两种快速方法的工作原理相同,所以我们不必费心重复它很多次分裂的头发。
如果任一数组很大,concat
方法总是很慢,但只有在必须执行大量函数调用并且不关心 a
有多大时,循环才会很慢。因此,对于小的 b
,循环类似于 push(...b)
或 push.apply
,但如果它变大则不会中断;但是,当您接近极限时,concat
又会快一点。
首先介绍一下 JavaScript 中的 apply()
以帮助理解我们使用它的原因:
apply() 方法使用给定的 this 值调用函数,并以数组形式提供参数。
Push 期望将一个 list 项添加到数组中。但是,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
中的所有内容移动到 a
,并在此过程中清空 b
,您可以这样做:
while(b.length) {
a.push(b.shift());
}
结果如下:
a = [1, 2, 3, 4, 5, 6, 7];
b = [];
while (b.length) { a.push(b.shift()); }
,对吗?
如果你想使用 jQuery,有 $.merge()
例子:
a = [1, 2];
b = [3, 4, 5];
$.merge(a,b);
结果:a = [1, 2, 3, 4, 5]
jQuery.merge()
的实现(在源代码中)?
我喜欢上面描述的 a.push.apply(a, b)
方法,如果您愿意,可以随时创建如下库函数:
Array.prototype.append = function(array)
{
this.push.apply(this, array)
}
并像这样使用它
a = [1,2]
b = [3,4]
a.append(b)
a.push(...b)
使用 splice()
可以做到这一点:
b.unshift(b.length)
b.unshift(a.length)
Array.prototype.splice.apply(a,b)
b.shift() // Restore b
b.shift() //
但尽管它更丑,但它并不比 push.apply
快,至少在 Firefox 3.0 中没有。
这个解决方案对我有用(使用 ECMAScript 6 的扩展运算符):
让数组= ['我的','解决方案','作品'];让 newArray = [];让 newArray2 = []; newArray.push(...array); // 添加到同一个数组 newArray2.push([...array]); // 添加为子/叶/子数组 console.log(newArray);控制台.log(newArray2);
我正在添加这个答案,因为尽管问题清楚地说明了 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
,然后是 array3
,依此类推。
就性能和内存使用而言,这是连接数组的最有效方式。
如果最终决定将其转换为实际的物理数组,则可以通过标准扩展运算符来实现:
const fullArray = [...i]; // pulls all values from iterable, into a new array
正如投票最多的答案所说,考虑到大小限制问题,a.push(...b)
可能是正确的答案。
另一方面,一些关于性能的答案似乎已经过时了。
以下这些数字适用于 2022-05-20
https://i.stack.imgur.com/c1nKN.png
https://i.stack.imgur.com/WMwGg.png
https://i.stack.imgur.com/Jqt90.png
来自here
似乎 push
在 2022 年是最快的。未来可能会改变。
忽略问题(生成新数组)的答案没有抓住重点。鉴于可能存在对同一数组的其他引用,许多代码可能需要/想要修改数组
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
那些其他引用可能在某个对象的深处,在闭包中捕获的东西等等......
作为一个可能很糟糕的设计,但作为一个插图,想象一下你有
const carts = [
{ userId: 123, cart: [item1, item2], },
{ userId: 456, cart: [item1, item2, item3], },
];
和一个函数
function getCartForUser(userId) {
return customers.find(c => c.userId === userId);
}
然后您想将商品添加到购物车
const cart = getCartForUser(userId);
if (cart) {
cart.concat(newItems); // FAIL 😢
cart.push(...newItems); // Success! 🤩
}
顺便说一句,建议修改 Array.prototype
的答案可以说是不好的建议。更改本机原型基本上是代码中的地雷。另一种实现可能与您的不同,因此它会破坏您的代码,或者您会破坏他们的代码,期望其他行为。这包括是否/何时添加了与您的冲突的本机实现。你可能会说“我知道我在使用什么,所以没问题”,这在目前可能是正确的,你是一个开发者,但添加第二个开发者,他们无法读懂你的想法。而且,当您忘记然后将其他一些库(分析?,日志记录?,...)移植到您的页面上并忘记您留在代码中的地主时,您是几年内的第二个开发人员。
这不仅仅是理论。网上有无数关于人们遇到这些地雷的故事。
可以说,修改原生对象的原型只有几个安全的用途。一种是在旧浏览器中填充现有的和指定的实现。在这种情况下,规范已定义,规范已在新浏览器中发布,您只想在旧浏览器中获得相同的行为。那是相当安全的。预修补(规范进行中但未发货)可以说是不安全的。规格在发货前发生变化。
结合答案...
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]);
};
}
}
您可以为扩展创建一个 polyfill,如下所示。它将添加到数组中;就地并返回自身,以便您可以链接其他方法。
if (Array.prototype.extend === undefined) { Array.prototype.extend = function(other) { this.push.apply(this, arguments.length > 1 ? arguments : other);返回这个; }; } 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];变量 b = [4, 5, 6];打印(“连接”); print('(1)', a.concat(b)); print('(2)', a.concat(b)); print('(3)', a.concat(4, 5, 6)); print('\n扩展'); print('(1)', a.extend(b)); print('(2)', a.extend(b)); print('(3)', a.extend(4, 5, 6));正文 {字体系列:等宽;空白:pre; }
合并两个以上数组的另一种解决方案
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]
答案超级简单。
>>> 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.
Array.prototype.concat()
at MDN 中明确指出的,这将返回一个 新数组,它不会像 OP 明确要求的那样附加到现有数组。
> 使用 Array.extend
而不是 Array.push
150,000 条记录。
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;
};
}
您可以通过在 push()
方法的帮助下简单地向数组添加新元素来做到这一点。
让颜色= [“红色”,“蓝色”,“橙色”]; console.log('推送前的数组:' + 颜色); // 将新值添加到数组 colors.push("Green"); console.log('推送后的数组:'+颜色);
用于将元素附加到数组开头的另一种方法是 unshift() 函数,它添加并返回新长度。它接受多个参数,附加现有元素的索引,最后返回数组的新长度:
让颜色= [“红色”,“蓝色”,“橙色”]; console.log('unshift 前的数组:' + 颜色); // 将新值添加到数组 colors.unshift("Black", "Green"); console.log('unshift 后的数组:' + 颜色);
还有其他方法。您可以查看它们here。
extend
方法将数组(就地)添加到数组中。
另一个选项,如果你安装了 lodash:
import { merge } from 'lodash';
var arr1 = merge(arr1, arr2);
超级简单,不依赖传播运算符或应用,如果这是一个问题。
b.map(x => a.push(x));
在对此进行了一些性能测试之后,它非常慢,但回答了关于不创建新数组的问题。 Concat 的速度明显更快,甚至 jQuery 的 $.merge()
也快了。
https://jsperf.com/merge-arrays19b/1
.forEach
而不是 .map
。它更好地传达了代码的意图。
.map
的语法,但您也可以使用 b.forEach(function(x) { a.push(x)} )
。事实上,我将它添加到 jsperf 测试中,它比 map 快。还是超级慢。
.map
看起来很奇怪。这就像调用 b.filter(x => a.push(x))
。它有效,但它暗示某些事情正在发生,而实际上并没有发生,从而使读者感到困惑。
.map()
做了什么,这就是问题所在。这些知识会让我认为您需要结果数组,否则使用 .forEach()
让读者对回调函数的副作用保持谨慎是一件好事。严格来说,您的解决方案会创建一个额外的自然数数组(push
的结果),而问题是:“不创建新数组”。
push
方法可以接受任意数量的参数,然后将它们推到数组的后面。所以a.push('x', 'y', 'z')
是一个有效的调用,它将a
扩展 3 个元素。apply
是任何函数的方法,它接受一个数组并使用它的元素,就好像它们都被显式地作为函数的位置元素一样。所以a.push.apply(a, ['x', 'y', 'z'])
也会将数组扩展 3 个元素。此外,apply
将上下文作为第一个参数(我们再次传递a
以追加到a
)。[].push.apply(a, b)
。