合并JavaScript数组

This is a quickie simple post on JavaScript techniques. We're going to cover different methods for combining/merging two JS arrays, and the pros/cons of each approach.

这是有关JavaScript技术的简单快速文章。 我们将介绍合并/合并两个JS数组的不同方法,以及每种方法的优缺点。

Let's start with the scenario:

让我们从场景开始:

var a = [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ];
var b = [ "foo", "bar", "baz", "bam", "bun", "fun" ];


The simple concatenation of a and b would, obviously, be:

ab的简单串联显然是:

[
   1, 2, 3, 4, 5, 6, 7, 8, 9,
   "foo", "bar", "baz", "bam" "bun", "fun"
]


concat(..) (concat(..))

The most common approach is:

最常见的方法是:

var c = a.concat( b );

a; // [1,2,3,4,5,6,7,8,9]
b; // ["foo","bar","baz","bam","bun","fun"]

c; // [1,2,3,4,5,6,7,8,9,"foo","bar","baz","bam","bun","fun"]


As you can see, c is a whole new array that represents the combination of the two a and b arrays, leaving a and b untouched. Simple, right?

如您所见, c是一个全新的array ,表示两个ab数组的组合,而ab保持不变。 简单吧?

What if a is 10,000 items, and b is 10,000 items? c is now 20,000 items, which constitutes basically doubling the memory usage of a and b.

如果a是10,000个项目, b是10,000个项目怎么办? c现在是20,000个项目,基本上使ab的内存使用量增加了一倍。

"No problem!", you say. We just unset a and b so they are garbage collected, right? Problem solved!

你说:“没问题!” 我们只是取消ab设置,所以它们被垃圾回收了,对吗? 问题解决了!

a = b = null; // `a` and `b` can go away now


Meh. For only a couple of small arrays, this is fine. But for large arrays, or repeating this process regularly a lot of times, or working in memory-limited environments, it leaves a lot to be desired.

嗯 对于仅几个小array ,这很好。 但是对于大型array ,或者定期重复此过程很多次,或者在内存受限的环境中工作,这还有很多需要改进的地方。

循环插入 (Looped Insertion)

OK, let's just append one array's contents onto the other, using Array#push(..):

好的,让我们使用Array#push(..)将一个array的内容附加到另一个array上:

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

a; // [1,2,3,4,5,6,7,8,9,"foo","bar","baz","bam","bun","fun"]

b = null;


Now, a has the result of both the original a plus the contents of b.

现在, a的结果是原始a加上b的内容。

Better for memory, it would seem.

看起来更好地存储。

But what if a was small and b was comparitively really big? For both memory and speed reasons, you'd probably want to push the smaller a onto the front of b rather than the longer b onto the end of a. No problem, just replace push(..) with unshift(..) and loop in the opposite direction:

但是,如果a很小而b确实很大,该怎么办? 对于内存和速度的原因,你可能要推小a到前面b ,而不是更长b到年底a 。 没问题,只需用unshift(..)替换push(..) unshift(..)并以相反的方向循环:

// `a` into `b`:
for (var i=a.length-1; i >= 0; i--) {
    b.unshift( a[i] );
}

b; // [1,2,3,4,5,6,7,8,9,"foo","bar","baz","bam","bun","fun"]

a = null;


功能技巧 (Functional Tricks)

Unfortunately, for loops are ugly and harder to maintain. Can we do any better?

联合国寻求 tunately, for循环是丑陋,难以维护。 我们可以做得更好吗?

Here's our first attempt, using Array#reduce:

这是我们第一次尝试使用Array#reduce

// `b` onto `a`:
a = b.reduce( function(coll,item){
    coll.push( item );
    return coll;
}, a );

a; // [1,2,3,4,5,6,7,8,9,"foo","bar","baz","bam","bun","fun"]

// or `a` into `b`:
b = a.reduceRight( function(coll,item){
    coll.unshift( item );
    return coll;
}, b );

b; // [1,2,3,4,5,6,7,8,9,"foo","bar","baz","bam","bun","fun"]


Array#reduce(..) and Array#reduceRight(..) are nice, but they are a tad clunky. ES6 => arrow-functions will slim them down slightly, but it's still requiring a function-per-item call, which is unfortunate.

Array#reduce(..)Array#reduceRight(..)不错,但它们有点笨拙。 ES6 =>箭头功能会使它们稍微变小,但仍然需要按功能调用,这是不幸的。

What about:

关于什么:

// `b` onto `a`:
a.push.apply( a, b );

a; // [1,2,3,4,5,6,7,8,9,"foo","bar","baz","bam","bun","fun"]

// or `a` into `b`:
b.unshift.apply( b, a );

b; // [1,2,3,4,5,6,7,8,9,"foo","bar","baz","bam","bun","fun"]


That's a lot nicer, right!? Especially since the unshift(..) approach here doesn't need to worry about the reverse ordering as in the previous attempts. ES6's spread operator will be even nicer: a.push( ...b ) or b.unshift( ...a ).

好多了,对!! 特别是因为unshift(..)方法在这里不需要像以前的尝试那样担心逆序。 ES6的散布运算符会更好: a.push( ...b )b.unshift( ...a )

But, things aren't as rosy as they might seem. In both cases, passing either a or b to apply(..)'s second argument (or via the ... spread operator) means that the array is being spread out as arguments to the function.

但是,事情并没有看起来那么乐观。 在这两种情况下,将ab传递给apply(..)的第二个参数(或通过...传播算子)意味着该数组将作为函数的参数被传播。

The first major problem is that we're effectively doubling the size (temporarily, of course!) of the thing being appended by essentially copying its contents to the stack for the function call. Moreover, different JS engines have different implementation-dependent limitations to the number of arguments that can be passed.

第一个主要问题是,通过实质上将其内容复制到函数调用的堆栈中,我们正在有效地使附加的事物的大小加倍(当然是暂时的!)。 此外,不同的JS引擎对可传递的参数数量有不同的实现相关的限制。

So, if the array being added on has a million items in it, you'd almost certainly way exceed the size of the size of the stack allowed for that push(..) or unshift(..) call. Ugh. It'll work just fine for a few thousand elements, but you have to be careful not to exceed a reasonably safe limit.

因此,如果添加的array中包含一百万个项目,则几乎可以肯定会超过该push(..)unshift(..)调用所允许的堆栈大小。 啊。 它对于几千个元素都可以正常工作,但是您必须注意不要超过合理的安全限制。

Note: You can try the same thing with splice(..), but you'll have the same conclusions as with push(..) / unshift(..).

注意:您可以使用splice(..)尝试相同的操作,但是得出的结论与push(..) / unshift(..)

One option would be to use this approach, but batch up segments at the max safe size:

一种选择是使用此方法,但以最大安全大小批量处理段:

function combineInto(a,b) {
    var len = a.length;
    for (var i=0; i < len; i=i+5000) {
        b.unshift.apply( b, a.slice( i, i+5000 ) );
    }
}


Wait, we're going backwards in terms of readability (and perhaps even performance!). Let's quit before we give up all our gains so far.

等一下,我们在可读性(甚至性能!)方面倒退了。 让我们放弃,直到我们放弃目前为止的所有收益。

摘要 (Summary)

Array#concat(..) is the tried and true approach for combining two (or more!) arrays. But the hidden danger is that it's creating a new array instead of modifying one of the existing ones.

Array#concat(..)是组合两个(或更多!)数组的可靠方法。 但是隐藏的危险是它正在创建一个新数组,而不是修改现有数组之一。

There are options which modify-in-place, but they have various trade-offs.

有一些可以就地修改的选项,但是它们具有各种折衷。

Giving the various pros/cons, perhaps the best of all of the options (including others not shown) is the reduce(..) and reduceRight(..).

考虑到各种优点/缺点,也许所有选项中最好的(包括未显示的其他选项)是reduce(..)reduceRight(..)

Whatever you choose, it's probably a good idea to critically think about your array merging strategy rather than taking it for granted.

无论您选择什么,最好是认真考虑阵列合并策略,而不是理所当然地考虑。

翻译自: https://davidwalsh.name/combining-js-arrays

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值