Where is JavaScript?

JavaScript出坑之提升数组操作性能

前端开发中会经常涉及到数组的操作需求,去重、过滤、替换、数据查询处理等,这些操作会直接间接地依赖于数组的原生方法。不同的业务需求和环境特点下,我们应该怎么选择处理方案,本文就笔者开发过程中遇到的一些案例带大家一一讨论。

前端开发中会经常涉及到数组的操作需求,去重、过滤、替换、数据查询处理等,这些操作会直接间接地依赖于数组的原生方法。为了满足这些需求,ES 规范里逐代加入了各种各样的 API 供开发者选用,特别是 ES6 中加入了mapfiltersomereducefindfindIndex等方法。不同的业务需求和环境特点下,我们应该怎么选择处理方案,本文就笔者开发过程中遇到的一些案例带大家一一讨论。

测试环境

  • MacBook Pro (Retina, 13-inch, Early 2015) - 2.7 GHz Intel Core i5 - 8 GB 1867 MHz DDR3
  • Google Chrome 71.0.3578.98(正式版本)(64 位)

循环

为了对比不同方案直接的差异,需要构造一个相对容量大点的数组。同时为了模拟后续案例中的业务场景数据,数组元素由对象构成,包含 id 字段。

              
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
// uuid generator function uuid() { function s4() { return Math.floor((1 + Math.random()) * 0x10000) .toString(16) .substring(1); } return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4(); } // generate arr with length len function generateRandomArray (len) { let arr = [] let i = len while(i >= 0){ arr.push({ id: uuid(), createdAt: new Date().getTime() }) i-- } return arr } const list = generateRandomArray(1000000)
  • for循环
    JavaScript 中 for 循环有若干种写法
              
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
let a // 方法一 console.time('method1') for (let i = 0; i < list.length; i++) { a = list[i] } console.timeEnd('method1') // 方法二 console.time('method2') for (let i = 0, len = list.length; i < len; i++) { a = list[i] } console.timeEnd('method2') // 方法三 console.time('method3') for (let i = 0, item; item = list[i]; i++) { a = item } console.timeEnd('method3') // 方法四 console.time('method4') for (let i = list.length; i--;) { a = list[i] } console.timeEnd('method4')

测试结果

              
  • 1
  • 2
  • 3
  • 4
method1: 14.324951171875ms method2: 4.864013671875ms method3: 8.18310546875ms method4: 5.711181640625ms

总体上相差不大,方法1和方法3在每一步遍历中多了一些取值操作,耗时上略有体现。

  • ES6 数组循环方法
    ES6 中的数组循环方法有 forEach, mapfor of
              
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
// forEach console.time('method5') list.forEach(item => { a = item }) console.timeEnd('method5') // map console.time('method6') list.map(item => { a = item }) console.timeEnd('method6') // for...of console.time('method7') for (let item of list) { a = item } console.timeEnd('method7')

测试结果

              
  • 1
  • 2
  • 3
method5: 17.27001953125ms method6: 168.003173828125ms method7: 64.89111328125ms

由上面的数据可以很明显的看出,forEachmapfor of 这些 ES6 的语法并没有传统的 for 循环或者 while 循环快,特别是 map 方法。但是由于 map 有返回值,无需额外调用新数组的 push 方法,所以在执行浅拷贝任务上,内存占用很低。而 for of 语法在内存占用上也有一定的优势。顺便提一下:for 循环 while 循环 for of 循环是可以通过 break return 关键字跳出的,而 forEach map 这种循环是无法跳出的。

数组增量修改

数组增量修改的业务场景大致为 -
已经拿到 N 条数据的数组,需要将 M 条数据添加进数组。M 条数据中包含对已有数据(在 N 条数据中)的更新,以及一些全新增加的数据。

常规的想法是对 M 条数据做一次遍历,遍历中若查询存在,则修改数组相应索引下的值,否则,就 push 到数组末尾。

为了模拟该场景,利用 generateRandomArray 函数构造 100000 条数据,在随机抽取部分数据模拟需要更新的数据。

              
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
const NList = generateRandomArray(100000) // 随机抽取函数 function getArrItem(arr, num) { var temp_array = new Array(); for (var index in arr) { temp_array.push(arr[index]); } var return_array = new Array(); for (var i = 0; i < num; i++) { if (temp_array.length > 0) { var arrIndex = Math.floor(Math.random() * temp_array.length); return_array[i] = temp_array[arrIndex]; temp_array.splice(arrIndex, 1); } else { break; } } return return_array; } const MList = getArrItem(NList, 500).concat(generateRandomArray(500)) console.time('method1') MList.forEach(i => { let index = NList.findIndex(item => item.id === i.id) if (index !== -1) { NList[index] = i } else { NList.push(i) } }) console.timeEnd('method1')

测试结果

              
  • 1
method1: 2372.586181640625ms

这个效率确实有点坑,仔细想想,上述过程MList每次遍历中,都要 NList.findIndex 相当于对 NList 做一次循环,累积下来的耗时不少。那么能想到什么方法优化呢,首先一般我们都会想到建立哈希映射。

              
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
console.time('method2') let hashMap = {} NList.forEach((i, index) => { hashMap[i.id] = { index, value: i } }) MList.forEach(i => { if (hashMap[i.id]) { NList[hashMap[i.id].index] = i } else { NList.push(i) } }) console.timeEnd('method2')

测试结果

              
  • 1
method2: 194.77880859375ms

这个效率提升是显而易见的。

本文于 2017-3-2  发布在  编程  分类下, 当前已被围观 21 次

并被添加「 JavaScript 工作 前端 」标签

本站使用「 署名 4.0 国际」创作共享协议