如何在不创建新数组的情况下用另一个数组扩展现有 JavaScript 数组
Posted
技术标签:
【中文标题】如何在不创建新数组的情况下用另一个数组扩展现有 JavaScript 数组【英文标题】:How to extend an existing JavaScript array with another array, without creating a new array 【发布时间】:2010-11-25 08:07:30 【问题描述】:似乎没有办法用另一个数组扩展现有 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?的重复——这里的目标是将一个数组的全部内容添加到其他,并“就地”完成,即不复制扩展数组的所有元素。
【问题讨论】:
来自@Toothbrush 对答案的评论:a.push(...b)
。它在概念上与最佳答案相似,但针对 ES6 进行了更新。
>>> a.push(...b)
【参考方案1】:
.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
足够短,则应使用另一个答案中描述的标准基于循环的技术。
【讨论】:
我认为这是你最好的选择。其他任何事情都将涉及迭代或 apply() 的其他操作 如果“b”(要扩展的数组)很大(根据我的测试,Chrome 中大约有 150000 个条目),则此答案将失败。您应该使用 for 循环,或者更好地使用“b”上的内置“forEach”函数。看我的回答:***.com/questions/1374126/… @Deqing:数组的push
方法可以接受任意数量的参数,然后将其推到数组的后面。所以a.push('x', 'y', 'z')
是一个有效的调用,它将扩展a
3 个元素。 apply
是任何函数的方法,它接受一个数组并使用它的元素,就好像它们都被明确地作为函数的位置元素一样。所以a.push.apply(a, ['x', 'y', 'z'])
也会将数组扩展 3 个元素。此外,apply
将上下文作为第一个参数(我们再次传递 a
以附加到 a
)。
注意:在这种情况下,第一个返回值不是 [1,2,3,4,5] 而是 5,而 a == [1,2,3,4,5] 之后。
另一个稍微不那么令人困惑(并且不会更长)的调用是:[].push.apply(a, b)
。【参考方案2】:
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];
(它也复制。)
【讨论】:
问题很明确:“不创建新数组?” @Wilt:这是真的,答案说明了原因 :) 这在 Google 上首次出现了很长时间。其他解决方案也很丑陋,想要一些有思想的和好的东西。虽然现在arr.push(...arr2)
更新更好,并且在技术上是对这个特定问题的正确答案。
我听到了,但是我搜索了 "javascript append array without create a new array",然后很遗憾看到一个 60 次投票的答案很高没有回答实际问题;)我没有投反对票,因为您在回答中明确表示。
@Wilt 和我搜索了“js extend array with array”并到了这里,所以谢谢你:)【参考方案3】:
您应该使用基于循环的技术。此页面上基于使用 .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
!
【讨论】:
附注要测试 other_array 是否真的是一个数组,请选择此处描述的选项之一:***.com/questions/767486/… 好答案。我没有意识到这个问题。我在这里引用了你的答案:***.com/a/4156156/96100.push.apply
实际上比 v8 中的 .forEach
快得多,最快的仍然是内联循环。
@BenjaminGruenbaum - 你能发布一个链接到一些结果吗?据我从本评论末尾链接的 jsperf 收集的结果中可以看出,在 Chrome/Chromium 中使用 .forEach
比 .push.apply
更快(在 v25 之后的所有版本中)。我无法单独测试 v8,但如果您有,请链接您的结果。见jsperf:jsperf.com/array-extending-push-vs-concat/5
@jcdude 您的 jsperf 无效。因为数组中的所有元素都是undefined
,所以.forEach
将跳过它们,使其最快。 .splice
居然是最快的?! jsperf.com/array-extending-push-vs-concat/18【参考方案4】:
这些天我觉得最优雅的是:
arr1.push(...arr2);
MDN article on the spread operator 在 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 的回答,调用堆栈会溢出。
【讨论】:
只是对我刚刚犯的错误的警告。 ES6 版本非常接近arr1.push(arr2)
。这可能是像arr1 = []; arr2=['a', 'b', 'd']; arr1.push(arr2)
这样的问题,结果是一个数组数组arr1 == [['a','b','d']]
,而不是两个数组组合。这是一个容易犯的错误。出于这个原因,我更喜欢下面的第二个答案。 ***.com/a/31521404/4808079
使用.concat
有一个方便之处,就是第二个参数不能是数组。这可以通过将扩展运算符与concat
结合来实现,例如arr1.push(...[].concat(arrayOrSingleItem))
这是现代优雅的 Javascript,适用于目标数组不为空的情况。
这个速度够快吗?【参考方案5】:
首先介绍一下 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());
,对吗?【参考方案6】:
如果要使用jQuery,有$.merge()
例子:
a = [1, 2];
b = [3, 4, 5];
$.merge(a,b);
结果:a = [1, 2, 3, 4, 5]
【讨论】:
如何找到jQuery.merge()
的实现(在源代码中)?
花了我 10 分钟 -- jQuery 是开源的,源代码发布在 Github 上。我搜索了“合并”,并进一步找到了 core.js 文件中合并函数的定义,请参阅:github.com/jquery/jquery/blob/master/src/core.js#L331【参考方案7】:
概述
a.push(...b)
- 有限、快速、现代的语法
a.push.apply(a, b)
- 有限,快速
a = a.concat(b)
无限制,如果 a
很大,则速度较慢
for (let i in b) a.push(b[i]);
- 无限制,如果 b
很大,则速度较慢
每个 sn-p 修改 a
以扩展为 b
。
“有限”的 sn-ps 将每个数组元素作为参数传递,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
比循环快得多
这让我很惊讶:我认为a=a.concat(b)
能够很好地完成b
到a
的memcpy,而无需像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
s,循环类似于 push(...b)
或 push.apply
,但如果它变大则不会中断;然而,当你接近极限时,concat
又快了一点。
【讨论】:
【参考方案8】:我喜欢上面描述的a.push.apply(a, b)
方法,如果你愿意,你可以随时创建这样的库函数:
Array.prototype.append = function(array)
this.push.apply(this, array)
并像这样使用它
a = [1,2]
b = [3,4]
a.append(b)
【讨论】:
不应使用 push.apply 方法,因为如果您的“array”参数是一个大数组(例如,Chrome 中 > ~150000 个条目),它可能会导致堆栈溢出(并因此失败)。您应该使用“array.forEach” - 请参阅我的回答:***.com/a/17368101/1280629 在 ES6 中,只需使用a.push(...b)
【参考方案9】:
可以使用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 中没有。
【讨论】:
我发现了同样的事情,splice 直到大约 10,000 个元素数组才提供推送每个项目的性能增强jsperf.com/splice-vs-push +1 感谢您添加此内容和性能比较,为我节省了测试此方法的工作量。 如果数组 b 很大,使用 splice.apply 或 push.apply 可能会因堆栈溢出而失败。它们也比使用“for”或“forEach”循环慢 - 请参阅这个 jsPerf:jsperf.com/array-extending-push-vs-concat/5 和我的答案 ***.com/questions/1374126/…【参考方案10】:此解决方案适用于我(使用 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);
【讨论】:
【参考方案11】:结合答案...
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]);
;
【讨论】:
不,没有必要这样做。使用 forEach 的 for 循环比使用 push.apply 更快,并且无论要扩展的数组的长度是多少都可以工作。看看我修改后的答案:***.com/a/17368101/1280629 无论如何,你怎么知道 150000 是适用于所有浏览器的正确数字?这是软糖。 哈哈。我的答案无法辨认,但似乎是当时发现的其他人的一些组合 - 即摘要。不用担心【参考方案12】:您可以像下面那样为扩展创建一个 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;
【讨论】:
【参考方案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]);
;
然后调用并打印为:
mergeArrays(a, b, c);
console.log(a)
输出将是:Array [1, 2, 3, 4, 5, 6, 7]
【讨论】:
【参考方案14】:答案超级简单。
>>> 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.
【讨论】:
正如the docs forArray.prototype.concat()
at MDN 中明确指出的那样,这将返回一个新数组,它不会像OP 明确要求的那样附加到现有数组。【参考方案15】:
对于 > 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;
;
【讨论】:
【参考方案16】:您可以通过在 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);
还有其他方法。您可以查看here。
【讨论】:
这会向数组添加一个值。它不会根据问题和 Python 的 listextend
方法将数组(就地)添加到数组中。【参考方案17】:
另一个选项,如果你安装了 lodash:
import merge from 'lodash';
var arr1 = merge(arr1, arr2);
【讨论】:
我认为这不适用于这样的数组。请提供一个工作 sn-p 的链接。【参考方案18】:超级简单,不依赖传播运算符或应用,如果这是一个问题。
b.map(x => a.push(x));
在对此进行了一些性能测试后,它非常慢,但回答了关于不创建新数组的问题。 Concat 明显更快,即使是 jQuery 的 $.merge()
也很赞。
https://jsperf.com/merge-arrays19b/1
【讨论】:
如果你不使用返回值,你应该使用.forEach
而不是.map
。它更好地传达了代码的意图。
@david - 每个人都有。我更喜欢.map
的语法,但你也可以使用b.forEach(function(x) a.push(x) )
。事实上,我将它添加到 jsperf 测试中,它比 map 快。还是超级慢。
这不是偏好的情况。这些“功能”方法中的每一个都具有与之相关的特定含义。在这里调用.map
看起来很奇怪。这就像打电话给b.filter(x => a.push(x))
。它有效,但它暗示某些事情正在发生,而实际上并没有发生,从而使读者感到困惑。
@elPastor 这应该值得你花时间,因为像我这样的其他人会坐着挠他的脖子,想知道他看不到的代码中还发生了什么。在大多数普通情况下,重要的是意图的清晰度,而不是性能。请尊重阅读您代码的人的时间,因为当您只是一个人时,他们可能很多。谢谢。
@elPastor 我确切地知道.map()
做了什么,这就是问题所在。这些知识会让我觉得你需要结果数组,否则使用.forEach()
让读者对回调函数的副作用保持谨慎是一件好事。严格来说,您的解决方案会创建一个额外的自然数数组(push
的结果),而问题是:“不创建新数组”。以上是关于如何在不创建新数组的情况下用另一个数组扩展现有 JavaScript 数组的主要内容,如果未能解决你的问题,请参考以下文章