JS合并数组的几种方法及优劣比较


Posted in Javascript onSeptember 19, 2014

本文属于JavaScript的基础技能. 我们将学习结合/合并两个JS数组的各种常用方法,并比较各种方法的优缺点.

我们先来看看具体的场景:

var q = [ 5, 5, 1, 9, 9, 6, 4, 5, 8];

var b = [ "tie", "mao", "csdn", "ren", "fu", "fei" ];

很明显,数组 q 和 b 简单拼接的结果是:

[

    5, 5, 1, 9, 9, 6, 4, 5, 8, 

    "tie", "mao", "csdn", "ren", "fu", "fei"

]

concat(..)方法

最常见的用法如下:

var c = q.concat( b );
q; // [5,5,1,9,9,6,4,5,8]

b; // ["tie","mao","csdn","ren","fu","fei"];
c; // [5,5,1,9,9,6,4,5,8,"tie","mao","csdn","ren","fu","fei"]

如您所见, c 是一个全新的数组, 表示 q 和 b 这两个数组的组合, 但是 q 和 b 现在没用了是吧?

如果 q 数组有10000个元素, b 数组也有有10000个元素? 那么数组c现在就有20000个元素, 这种方式占用了2倍的内存.

“这没问题!”,你可能会觉得. 只要将 q 和 b 置空就行, 然后就会被垃圾回收,对吗?问题解决了!

q = b = null; // `q` and `b` 现在可以被垃圾回收了

额? 如果数组都很小,那自然没问题. 但对大型的数组,或需要多次重复处理时, 内存就被限制了, 它还需要进行优化.

循环插入

OK, 让我们把一个数组的内容加入到另一个中试试,使用 Array#push() 方法:

// 将数组 `b` 插入 `q`

for (var i=0; i < b.length; i++) {

    q.push( b[i] );

}
q; // [5,5,1,9,9,6,4,5,8,"tie","mao","csdn","ren","fu","fei"]
b = null;

现在, q中存放了两个原始数组的内容(q + b).

看样子对内存优化做的不错.

但如果 q 数组很小而 b 又很大呢? 出于内存和速度的考虑,这时想把较小的 q 插入到 b 前面. 没问题,只要用 unshift() 方法代替 push() 即可, 对应的也要从大到小进行循环遍历:

// `q` into `b`:

for (var i=q.length-1; i >= 0; i--) {

    b.unshift( q[i] );

}
b; // [5,5,1,9,9,6,4,5,8,"tie","mao","csdn","ren","fu","fei"]
q = null;

实用技巧

悲催的是,for循环很土并且难以维护. 我们能做得更好吗?
我们先试试 Array#reduce :

// `b` onto `q`:

q = b.reduce( function(coll,item){

    coll.push( item );

    return coll;

}, q );
q; // [5,5,1,9,9,6,4,5,8,"tie","mao","csdn","ren","fu","fei"]
// or `q` into `b`:

b = q.reduceRight( function(coll,item){

    coll.unshift( item );

    return coll;

}, b );
b; // [5,5,1,9,9,6,4,5,8,"tie","mao","csdn","ren","fu","fei"]

Array#reduce() 和 Array#reduceRight() 很高大上,但有点笨重,而且一般人也记不住.  JS规范6 中的 => 箭头函数(arrow-functions) 能让代码量大大减少, 但需要对每个数组元素执行函数调用, 也是很渣的手段.
那么下面的代码怎么样呢?

// `b` onto `q`:

q.push.apply( q, b );
q; // [5,5,1,9,9,6,4,5,8,"tie","mao","csdn","ren","fu","fei"]
// or `q` into `b`:

b.unshift.apply( b, q );
b; // [5,5,1,9,9,6,4,5,8,"tie","mao","csdn","ren","fu","fei"]

BIG更高了,是吧!? 特别是 unshift() 方法不需要像前面那样考虑相反的顺序. ES6 的展开运算符(spread operator, 加 ... 前缀)就更高端了: a.push( ...b ) 或者 b.unshift( ...a )

但是,事实上这种方法还是太乐观了. 在这两种情况下,不管是将 a 或 b 传递给 apply() 作为第二个参数(apply方式调用Function时第一个参数在内部变成this,即context,上下文,作用域), 还是使用 ... 展开运算符的方式, 实际上数组都会被打散成为函数的 arguments .
第一个主要的问题是,占用了双倍的内存(当然,是临时的!),因为需要将数组复制到函数栈之中. 此外,不同的JS引擎有不同的实现算法,可能会限制了函数可以传递的参数数量.

如果数组添加了一百万个元素, 那一定会超过函数栈所允许的大小, 不管是push() 或 unshift()调用. 这种方式只在几千个元素时可用,所以必须限制其不能超过一定范围.

注意: 你也可以试试 splice(), 肯定会发现他和 push(..)/unshift(..) 都是一样的限制.

一种选择是继续使用这种方法,但是采用分批次处理:

function combineInto(q,b) {

    var len = q.length;

    for (var i=0; i < len; i=i+5000) {

        // 一次处理5000条

        b.unshift.apply( b, q.slice( i, i+5000 ) );

    }

}

等等,我们损害了代码的可读性(甚至是性能!). 在我们放弃之前结束这个旅程吧.

总结

Array#concat() 是久经考验的方法, 用于组合两个(或多个)数组. 但他创建了一个新的数组,而不是修改现有的一个.

有很多变通的手法,但他们都有不同的优缺点,需要根据实际情况来选择.

上面列出了各种 优点/缺点,也许最好的(包括没有列出的)方法是 reduce(..) 和 reduceRight(..)

无论你选择什么,都应该批判性地思考你的数组合并策略,而不是把它当作理所当然的事情.

Javascript 相关文章推荐
基于jQuery的input输入框下拉提示层(自动邮箱后缀名)
Jun 14 Javascript
JQuery onload、ready概念介绍及使用方法
Apr 27 Javascript
Jquery动态改变图片IMG的src地址示例
Jun 25 Javascript
jquery操作下拉列表、文本框、复选框、单选框集合(收藏)
Jan 08 Javascript
Javascript和Java获取各种form表单信息的简单实例
Feb 14 Javascript
js单独获取一个checkbox看其是否被选中
Sep 22 Javascript
jQuery实现感应鼠标动画效果自动伸长的输入框实例
Feb 24 Javascript
原生JS实现图片轮播切换效果
Dec 15 Javascript
简述vue路由打开一个新的窗口的方法
Nov 29 Javascript
JavaScript变速动画函数封装添加任意多个属性
Apr 03 Javascript
详解微信小程序获取当前时间及日期的方法
Apr 28 Javascript
react项目从新建到部署的实现示例
Feb 19 Javascript
JS实现的用来对比两个用指定分隔符分割的字符串是否相同
Sep 19 #Javascript
js用Date对象的setDate()函数对日期进行加减操作
Sep 18 #Javascript
JS应用正则表达式转换大小写示例
Sep 18 #Javascript
使用不同的方法结合/合并两个JS数组
Sep 18 #Javascript
js实现按Ctrl+Enter发送效果
Sep 18 #Javascript
javascript搜索框点击文字消失失焦时文本出现
Sep 18 #Javascript
输入框过滤非数字的js代码
Sep 18 #Javascript
You might like
扩展你的 PHP 之入门篇
2006/12/04 PHP
php Try Catch异常测试
2009/03/01 PHP
解决Codeigniter不能上传rar和zip压缩包问题
2014/03/07 PHP
Smarty高级应用之缓存操作技巧分析
2016/05/14 PHP
PHP封装的验证码工具类定义与用法示例
2018/08/22 PHP
PHP切割汉字的常用方法实例总结
2019/04/27 PHP
php并发加锁问题分析与设计代码实例讲解
2021/02/26 PHP
用js实现控制内容的向上向下滚动效果
2007/06/26 Javascript
SinaEditor使用方法详解
2013/12/28 Javascript
分步解析JavaScript实现tab选项卡自动切换功能
2016/01/25 Javascript
Jquery调用iframe父页面中的元素及方法
2016/08/23 Javascript
JavaScript获取键盘按键的键码(参照表)
2017/01/10 Javascript
Vue单页式应用(Hash模式下)实现微信分享的实例
2017/07/21 Javascript
vuex 使用文档小结篇
2018/01/11 Javascript
react同构实践之实现自己的同构模板
2019/03/13 Javascript
vue中实现Monaco Editor自定义提示功能
2019/07/05 Javascript
jquery获取input输入框中的值
2019/11/13 jQuery
vue组件传值的实现方式小结【三种方式】
2020/02/05 Javascript
在vue中使用echarts(折线图的demo,markline用法)
2020/07/20 Javascript
ES6学习教程之Promise用法详解
2020/11/22 Javascript
[01:05:07]DOTA2-DPC中国联赛 正赛 DLG vs Dragon BO3 第一场2月1日
2021/03/11 DOTA
Python实现的数据结构与算法之基本搜索详解
2015/04/22 Python
Python OpenCV对本地视频文件进行分帧保存的实例
2019/01/08 Python
用sleep间隔进行python反爬虫的实例讲解
2020/11/30 Python
瑞典时尚耳机品牌:Urbanears
2017/07/26 全球购物
俄罗斯名牌服装网上商店:UNIQUE FABRIC
2019/07/25 全球购物
西部世纪.net笔试题面试题
2014/04/03 面试题
咖啡书吧创业计划书
2014/01/13 职场文书
迟到早退检讨书
2014/02/10 职场文书
责任书格式
2015/01/29 职场文书
幼儿园春季开学通知
2015/07/16 职场文书
职工趣味运动会开幕词
2016/03/04 职场文书
导游词书写之黄山
2019/08/06 职场文书
python中requests库+xpath+lxml简单使用
2021/04/29 Python
使用Vue3+Vant组件实现App搜索历史记录功能(示例代码)
2021/06/09 Vue.js
Flask response响应的具体使用
2021/07/15 Python