概述
JS对数组的循环从一开始的for, while到ES5的forEach,map在ES6中又新增了filter,includes等方法。越发展,JS对数组的操变得更加灵活,我们可以用几行代码更优雅的解决问题。本文单单从运行速度方面对各个数组方法进行比较,希望在之后的业务场景中,在数组的操作方式的选择上,能有个参考。
循环浅拷贝对比
先说结论,推荐map, while, for
浅拷贝中,涉及到的函数有
- for
- for in
- for of
- while
- forEach
- map
const sourceArray = new Array(10000000).fill(9)
const methodFor = () => {
console.time('methodFor')
const array = []
const length = sourceArray.length
for(let i = 0; i < length; i ++) {
array.push(sourceArray[i])
}
console.timeEnd('methodFor')
}
const methodForIn = () => {
console.time('methodForIn')
const array = []
for(let i in sourceArray) {
array.push(sourceArray[i])
}
console.timeEnd('methodForIn')
}
const methodForOf = () => {
console.time('methodForOf')
const array = []
for(let i of sourceArray) {
array.push(sourceArray[i])
}
console.timeEnd('methodForOf')
}
const methodWhile = () => {
console.time('methodWhile')
const array = []
let i = 0
while(i < sourceArray.length) {
array.push(sourceArray[i])
i ++
}
console.timeEnd('methodWhile')
}
const methodForEach = () => {
console.time('methodForEach')
const array = []
let reslut
sourceArray.forEach(i => {
array.push(i)
})
console.timeEnd('methodForEach')
}
const methodMap = () => {
console.time('methodMap')
let array = []
let reslut
array = sourceArray.map(i => i)
console.timeEnd('methodMap')
}
methodFor()
methodForIn()
methodForOf()
methodWhile()
methodForEach()
methodMap()
chrome环境(已排序)
methodMap: 150.634033203125ms
methodWhile: 117.323974609375ms
methodForOf: 228.681640625ms
methodForEach: 229.51416015625ms
methodFor: 249.433837890625ms
methodForIn: 3063.458740234375ms
node环境(已排序)
methodMap: 163.163ms
methodFor: 224.809ms
methodWhile: 229.365ms
methodForOf: 333.059ms
methodForEach: 372.001ms
methodForIn: 5820.964ms
从多次结果展示,map本是ES5中用来返回一个操作之后的数组,所以在先拷贝中表现优异,另外则可考虑while的使用。
另外可以发现,for in 慢的出奇。原因有两个。首先for in中是那数组中的索引去检索,let的i的类型是string类型的,所以在取sourceArray[i]的时候,会先进行类型转换,而类型转换则相对消耗性能。另外则是for in会遍历数组或者对象的属性,而且必须按特定顺序遍历,先遍历所有数字键,然后按照创建属性的顺序遍历剩下的,所以非常非常慢,最不推荐。
查找对比
查找对比分为两部分,一种是简单的判断数组中是否存在某元素,另一种则是需要特定找到某元素
仅判断是否存在
先说结论:推荐使用includes,indexOf
涉及到的方法有
- for
- for of
- while
- find
- some
- includes
- indexOf
const sourceArray = new Array(10000000).fill(9)
sourceArray[999999] = 1
const methodFor_length = () => {
console.time('methodFor_length')
const length = sourceArray.length
let reslut = false
for(let i = 0; i < length; i ++) {
if(sourceArray[i] === 1) {
reslut = true
break
}
}
console.timeEnd('methodFor_length')
}
const methodForOf = () => {
console.time('methodForOf')
let reslut = false
for(let i of sourceArray) {
if(sourceArray[i] === 1) {
reslut = true
break
}
}
console.timeEnd('methodForOf')
}
const methodWhile = () => {
console.time('methodWhile')
let i = 0
let reslut = false
while(i < sourceArray.length) {
if(sourceArray[i] === 1) {
reslut = true
break
}
i ++
}
console.timeEnd('methodWhile')
}
const methodForEach = () => {
console.time('methodForEach')
let reslut = false
sourceArray.forEach(i => {
if(i === 1) {
reslut = true
return
}
})
console.timeEnd('methodForEach')
}
const methodFind = () => {
console.time('methodFind')
const reslut = sourceArray.find(i => i === 1)
console.timeEnd('methodFind')
}
const methodIndexOf = () => {
console.time('methodIndexOf')
const reslut = sourceArray.indexOf(1) === -1
console.timeEnd('methodIndexOf')
}
const methodIncludes = () => {
console.time('methodIncludes')
const reslut = sourceArray.includes(1)
console.timeEnd('methodIncludes')
}
const methodSome = () => {
console.time('methodSome')
const reslut = sourceArray.some(i => i === 1)
console.timeEnd('methodSome')
}
methodFor_length()
methodForOf()
methodWhile()
methodForEach()
methodIncludes()
methodIndexOf()
methodFind()
methodSome()
chrome(已排序)
methodIncludes: 1.006103515625ms
methodIndexOf: 1.002197265625ms
methodWhile: 2.119873046875ms
methodFor_length: 2.72705078125ms
methodFind: 11.995849609375ms
methodSome: 11.60009765625ms
methodForEach: 116.1689453125ms
methodForOf: 145.723876953125ms
node环境(已排序)
methodIncludes: 1.132ms
methodIndexOf: 1.137ms
methodWhile: 2.359ms
methodFor_length: 3.448ms
methodFind: 11.441ms
methodSome: 11.330ms
methodForEach: 104.478ms
methodForOf: 122.109ms
从结果看,如果单是用于判断是否存在的话,includes与indexOf几乎完胜。另外从语义上来讲,includes似乎也更胜一筹。
由于forEach,map等无法跳出循环,会将所有对象都循环一遍,耗时很厉害。
find与some则差的不多,更多的应用场景是对数组对象中属性的判断,毕竟includes与indexOf不适合判断复杂数据类型。find返回的是数组中找到的第一个符合要求的值(或者是对象的引用),some则返回的是布尔值,所以结合业务场景,各取所需。
查找并返回具体值的对比
涉及到的方法有
- for
- for of
- while
- indexOf
- forEach
- filter
- find
- findIndex
const sourceArray = new Array(10000000).fill(9)
sourceArray[999999] = 1
const methodFor_length = () => {
console.time('methodFor_length')
let reslut
const length = sourceArray.length
for(let i = 0; i < length; i ++) {
if(sourceArray[i] === 1) {
reslut = sourceArray[i]
break
}
}
console.timeEnd('methodFor_length')
}
const methodForOf = () => {
console.time('methodForOf')
let reslut
for(let i of sourceArray) {
if(i === 1) {
reslut = i
break
}
}
console.timeEnd('methodForOf')
}
const methodWhile = () => {
console.time('methodWhile')
let reslut
let i = 0
while(i < sourceArray.length) {
if(sourceArray[i] === 1) {
reslut = sourceArray[i]
break
}
i ++
}
console.timeEnd('methodWhile')
}
const methodForEach = () => {
console.time('methodForEach')
let reslut
sourceArray.forEach(i => {
if(i === 1) {
reslut = i
return
}
})
console.timeEnd('methodForEach')
}
const methodFind = () => {
console.time('methodFind')
const reslut = sourceArray.find(i => i === 1)
console.timeEnd('methodFind')
}
const methodFindIndex = () => {
console.time('methodFindIndex')
const index = sourceArray.findIndex(i => i === 1)
let reslut = sourceArray[index]
console.timeEnd('methodFindIndex')
}
const methodIndexOf = () => {
console.time('methodIndexOf')
const index = sourceArray.indexOf(1)
let reslut = sourceArray[index]
console.timeEnd('methodIndexOf')
}
const methodFilter = () => {
console.time('methodFilter')
const reslut = sourceArray.filter(i => i === 1)
console.timeEnd('methodFilter')
}
methodFor_length()
methodForOf()
methodWhile()
methodForEach()
methodFind()
methodFindIndex()
methodIndexOf()
methodFilter()
chrome环境(已排序)
methodIndexOf: 1.01171875ms
methodWhile: 3.039794921875ms
methodFor_length: 3.203125ms
methodFindIndex: 11.323974609375ms
methodFind: 11.60498046875ms
methodForOf: 27.98193359375ms
methodFilter: 112.635009765625ms
methodForEach: 116.5859375ms
node环境(已排序)
methodIndexOf: 1.399ms
methodWhile: 3.489ms
methodFor_length: 4.273ms
methodFind: 11.101ms
methodFindIndex: 11.513ms
methodForOf: 24.731ms
methodForEach: 107.206ms
methodFilter: 108.067ms从数据上看,indexof与while, for 从性能上来说,速度非常突出。forEach依旧是最不推荐的。
总结
但从速度上来说,for, while, indexOf这些当数据量庞大的时候,速度优势非常很明显。但是从代码量上来讲旧方法定义更多的变量,容易造成冗余的代码。新特性所定义的方法,语义更强,书写更优雅。特别是返回的数据格式,更容易把控。所以从业务上来说,孰优孰劣则需要前端自己选择。
另外提一句,对于循环数组的顺序,
For循环
最后简单比较下for循环在我最早期接触代码的时候,就看到过文章对for循环的优化方式之一便是讲length提出或者将循环对象缓存起来。如下代码
const sourceArray = new Array(10000000).fill(9)
const methodFor = () => {
console.time('methodFor')
for(let i = 0; i < sourceArray.length; i ++) {
}
console.timeEnd('methodFor')
}
const methodFor_length = () => {
console.time('methodFor_length')
const length = sourceArray.length
for(let i = 0; i < length; i ++) {
}
console.timeEnd('methodFor_length')
}
const methodFor_item = () => {
console.time('methodFor_item')
for(let i = 0, item; item = sourceArray[i]; i++) {
}
console.timeEnd('methodFor_item')
}
methodFor()
methodFor_length()
methodFor_item()
chrome环境
methodFor: 6.444091796875ms
methodFor_length: 4.895751953125ms
methodFor_item: 9.989013671875ms
node环境
methodFor: 7.448ms
methodFor_length: 6.159ms
methodFor_item: 19.123ms
从实验结果看,在for循环中,将length缓存,明显的提高了运行速率。以为在循环中,没有对子元素读取,方法methodFor_item将item缓存起来,多了每次循环的缓存步骤,所以变慢了
如果我们在循环当中,对子元素进行了读取赋值操作,则结果不同
const sourceArray = new Array(10000000).fill(9)
const methodFor = () => {
console.time('methodFor')
const arr = []
for(let i = 0; i < sourceArray.length; i ++) {
arr.push(sourceArray[i])
}
console.timeEnd('methodFor')
}
const methodFor_length = () => {
console.time('methodFor_length')
const length = sourceArray.length
const arr = []
for(let i = 0; i < length; i ++) {
arr.push(sourceArray[i])
}
console.timeEnd('methodFor_length')
}
const methodFor_item = () => {
console.time('methodFor_item')
const arr = []
for(let i = 0, item; item = sourceArray[i]; i++) {
arr.push(item)
}
console.timeEnd('methodFor_item')
}
methodFor()
methodFor_length()
methodFor_item()
chrome环境
methodFor: 138.826171875ms
methodFor_length: 114.406982421875ms
methodFor_item: 116.247802734375ms
node环境下
methodFor: 225.091ms
methodFor_length: 207.214ms
methodFor_item: 226.828ms在chrome下,有缓存则明显提高运行速率。而在node环境下,缓存子元素似乎效果不大。
综上所述,将循环中的length缓存起来,对提高循环速率有蛮大影响,并且从阅读来说,也是有益的