资讯专栏INFORMATION COLUMN

JavaScript 小技巧之数组合并

张率功 / 2307人阅读

摘要:对于一些小数组来说,这样做当然没有问题。第一个主要问题在于,我们将要追加的数组的元素数量翻倍了当然是临时性的,因为实质上要将数组内容拷贝到函数调用栈上。所以,假如要追加的数组中有一百万个元素,那么几乎一定会超过函数和的调用栈限制的大小。

原文链接: https://davidwalsh.name/combi...

这是一篇介绍 JavaScript 技术的小短文。我们将会讲到组合/合并两个数组的不同策略,以及每一种方法的优缺点。

首先展示一下应用场景:

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

很显然,拼接后的结果是这个样子滴:

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

concat(..)

最常见的做法如下:

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"]

从上述代码可以看出,c 是一个由 ab 两个数组合并而成的全新的数组,而 ab 则不受影响。相当简单,对吧?

假如 ab 分别包含 10,000 元素呢?那么 c 中就会包含 20,000 个元素,占用的内存基本上让 ab 占用的内存翻倍。

“这没什么大不了的!”你微微一笑。我们可以把 ab 删除嘛,这样就可以将占据的内存回收了,这样总可以吧?危机解除!

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

哦。对于一些小数组来说,这样做当然没有问题。但是对于大数组来说,或者经常性地执行这样的操作,再或者在执行环境内存有限的情况下,这样做还远远不够。

循环插入

好吧,那使用 Array#push(..) 方法将一个数组的内容追加到另外一个数组呢:

// `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;

现在,a 中包含的是原本 a 中的元素外加 b 中的元素。

看起来对于内存的使用有效多了。

可是假如 a 比较小而 b 相对来说很大呢?出于内存利用以及执行速度的考量,你一定希望把小数组 a 插入到 b 的前面而不是把大数组 b 追加到 a 后面。没问题,只要用 unshift(..) 替换 push(..) 然后反方向遍历就可以了:

// `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;
使用函数小技巧

遗憾的是,for 循环不够优雅,也不容易维护。还有没有更好的办法呢?

下面是我们的第一次尝试,用的是 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(..)Array#reduceRight(..) 看起来不错,只是有点笨拙。ES6 中的 => 箭头表达式可以对其进行适当“瘦身”,但是依然需要对于每一个元素进行一次函数调用,这一点有些令人遗憾。

下面的方法怎么样呢:

// `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"]

看起来好多了,是吧?!尤其是这里的 unshift(..) 不再需要顾及遍历顺序的问题。ES6 中的展开运算符(spread operator)会更棒:a.push( ...b ) 或是 b.unshift( ...a )

但是呢,公主与王子并没有从此过上幸福无虞的生活。在两种情况下,将 ab 传给 apply(..) 第二个参数(或者通过 ... )展开运算符意味着数组需要展开为函数的参数。

第一个主要问题在于,我们将要追加的数组的元素数量翻倍了(当然是临时性的),因为实质上要将数组内容拷贝到函数调用栈上。另外,不同的 JS 引擎因实现方式的不同,对于可以传入函数的参数的数量限制也不尽相同。

所以,假如要追加的数组中有一百万个元素,那么几乎一定会超过函数 push(..)unshift(..) 的调用栈限制的大小。嗯!几千元素应该是没有问题的,不过需要小心设定一个合理的安全上限。

注意: 你可以尝试使用 splice(..),但是结论与 push(..) / unshift(..) 相同。

一个可行的方式是,依然采用上述方法,将数组划分为处于安全范围的片段,进行批处理:

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

且慢,接下来我们要回到可读性(或者还有执行效率)的老话题了。我们还是在抛弃当前所获得的所有有效方式之前就此打住吧。

总结

Array#concat(..) 是合并两个(甚至多个)数组的行之有效的方法。但是隐含的风险是,它直接创建了一个新的数组,而不是在原来数组的基础上进行修改。

在原来数组的基础上进行修改有多种可行的方式,但均有某种程度的妥协。

从不同方法(包括未在这里展示的方法)的优缺点来看,或许最好的方法就是 reduce(..)reduceRight(..)

无论选择采用哪种方法,都需要对数组合并策略进行批判性思考,而不是想当然。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/88956.html

相关文章

  • 12个javaScript技巧

    摘要:如果第一个参数返回的值为,那么第二个值将会认为是一个默认值。但这个对象不具有数组的函数功能,比如等。 转载自:http://www.w3cplus.com/javasc...英文出处:https://blog.jscrambler.com/1... 12个JavaScript技巧 在这篇文章中将给大家分享12个有关于JavaScript的小技巧。这些小技巧可能在你的实际工作中或许能帮助...

    AWang 评论0 收藏0
  • 12个非常实用的JavaScript技巧

    摘要:如果第一个参数返回的值为,那么第二个值将会认为是一个默认值。获取数组中最后一个元素用来获取和之间的数组元素。但这个对象不具有数组的函数功能,比如等。 使用!!操作符转换布尔值 有时候我们需要对一个变量查检其是否存在或者检查值是否有一个有效值,如果存在就返回true值。为了做这样的验证,我们可以使用!!操作符来实现是非常的方便与简单。对于变量可以使用!!variable做检测,只要变量的...

    jsbintask 评论0 收藏0
  • 七个 JavaScript 实用技巧

    摘要:接下来先介绍七个马上就能用起来的小技巧。老实讲,文章所说的小技巧大部分都是新增的语法特性,,或者说已经发布好些年头,这些特性大家可能已经非常熟识。,对象合并,不多说,大部分场景可以取代。 作者:@davidwalshblog原文:7 Useful JavaScript Tricks 和许多其他语言一样,JavaScript 也需要靠很多小技巧去完成各种不同的事情。有的可能早已经广为人...

    Lin_YT 评论0 收藏0
  • 7个javascript实用技巧

    摘要:每种编程语言都有一些黑魔法或者说小技巧,也不例外,大部分是借助或者浏览器新特性实现。下面介绍的个实用小技巧,相信其中有些你一定用过。当然不管语言如何变化,我们总能在编程中总结一些小技巧来精简代码。 showImg(https://segmentfault.com/img/remote/1460000018902642); 每种编程语言都有一些黑魔法或者说小技巧,JS也不例外,大部分是借...

    tinysun1234 评论0 收藏0
  • 原生javascript的一些好用的技巧1

    摘要:以下我经常用,又总是记不住的几个方法转成数组形式获取中的内容关键在页面标签加载完成事件关键在阻止浏览器事件默认行为关键,记忆方法阻止事件冒泡关键,记忆方法数组的几个常用方法是数组的每一项如的下标,每一个元素的顺序等同于一个单纯的循环可以对进 以下我经常用,又总是记不住的几个方法 document.querySelectorAll 转成数组形式; [].slice.call(docume...

    qujian 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<