zoukankan      html  css  js  c++  java
  • 转载: 翻译 | JavaScript 小技巧之数组合并

    原文链接:https://davidwalsh.name/combining-js-arrays

    原译文链接:http://www.ituring.com.cn/article/497290

    这是一篇介绍 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 是一个由 a 和 b 两个数组合并而成的全新的数组,而 a 和 b 则不受影响。相当简单,对吧?

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

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

    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 )

    但是呢,公主与王子并没有从此过上幸福无虞的生活。在两种情况下,将 a 或 b 传给 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(..)

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

  • 相关阅读:
    QML的一些基础的区分
    qml的一个文章----可以看出状态、动画的使用
    凡是人性的,都是如下的
    全国经纬度,具体到县
    web-nodkit 入门
    一个文章-转年收入50万美元的软件工程师做的是什么类型的工作
    qml 封装技巧-利用数据来进行适配
    windbg内核诊断方式--转载
    Windbg程序调试--转载
    编写你自己的单点登录(SSO)服务
  • 原文地址:https://www.cnblogs.com/zhangmingzhao/p/7696483.html
Copyright © 2011-2022 走看看