javascript/ES6数组及对象的遍历方法汇总,性能分析和功能对比

蛰伏已久 蛰伏已久 2018-09-30

数组和对象的遍历,是编程中经常遇到的,之前一直都是使用for循环,其实还有很多方法,今天花时间整理一下

数组遍历

for循环

最常用的了,没什么可说的,通过es6的字符串repeat及split方法,生成一个长度为1千万数组,供我们测试,并记录循环开始和结束时间,以便我们测试循环的性能

    let str='a'.repeat(10000000)
    let arr=str.split('')   

    let starttime=new Date().getTime()

    for(let i=0;i<arr.length;i++){

    }


    let endtime=new Date().getTime();
    console.log('耗时:'+starttime+','+endtime+','+(endtime-starttime))

耗时:9毫秒左右

对上面进行简单的优化,将数组长度初始化时存储起来,避免每次通过.length读取

for(let i= 0,len=arr.length;i<len;i++){

}

耗时:8毫秒左右

网上还有一种通过判断数组值不为null的方式进行循环,说是效率更高,但是实测耗时要比上面的更长,

for(let i= 0;arr[i]!=null;i++){

}

耗时:18毫秒左右

Array.forEach

通过数组的forEach,不需要再定义一个变量 i 用来标识进度

forEach支持两个参数:

  • 第一个参数为回调函数,(value,index,list),value是遍历的值,index是遍历的索引,list是遍历的数组

  • 第二个参数是上下文,也就是回调函数中的this,注意此时不能用箭头函数的写法

let arr=['a','b','c']

arr.forEach(value=>console.log(value))    //1个参数是值

arr.forEach((value,index)=>console.log(index+':',value))   //2个参数是值和索引

arr.forEach((value,index,list)=>list[index]='d')       //第3个参数是遍历的数组,可以通过这种方式改变要遍历的数组内容

传入上下文

    let obj={
        test:'123'
    }
    let arr=['a','b','c']

    arr.forEach(function(value,index,list){
        console.log(this.test)   //打印3次 ‘123’
    },obj)

注意,forEach没有返回值,不改变原数组,除非你通过传入的list参数显式改变数组内容

性能测试:

let str='a'.repeat(10000000)
let arr=str.split('')

let starttime=new Date().getTime()


arr.forEach(function(value,index,list){

})





let endtime=new Date().getTime();
console.log('耗时:'+starttime+','+endtime+','+(endtime-starttime))

耗时:127毫秒左右

可以看出比普通的for循环,耗时更长

Array.map

Array.map与forEach类似,也是接受2个参数

  • 第一个参数为回调函数,(value,index,list),value是遍历的值,index是遍历的索引,list是遍历的数组

  • 第二个参数是上下文,也就是回调函数中的this,注意此时不能用箭头函数的写法

不同的是Array.map有返回值,返回值是一个数组,每一项为回调函数中return的值

let arr=['a','b','c']


let newarr=arr.map((value,index,list)=>'d')  //回调函数return 'd'

console.log(arr)    //不变["a", "b", "c"]
console.log(newarr) // ["d", "d", "d"]

性能测试

    let str='a'.repeat(10000000)
    let arr=str.split('')

    let starttime=new Date().getTime()


    let newarr=arr.map((value,index,list)=>{})


    let endtime=new Date().getTime();
    console.log('耗时:'+starttime+','+endtime+','+(endtime-starttime))

耗时:1589毫秒左右

相比forEach耗时大大增加

for in

通过for in可以获取遍历的索引,如下

let arr=['a','b','c']

for(let key in arr){
    console.log(arr[key])   //遍历到的是索引0,1,2
}

性能测试

    let str='a'.repeat(10000000)
    let arr=str.split('')

    let starttime=new Date().getTime()


    for(let key in arr){
  
    }


    let endtime=new Date().getTime();
    console.log('耗时:'+starttime+','+endtime+','+(endtime-starttime))

耗时:1773毫秒左右

比foreach性能低很多

for of(es6)

for in遍历到的是索引,而for of遍历到的是value

let arr=['a','b','c']

for(let value of arr){
    console.log(value)  //遍历到的是 'a','b','c'
}

性能测试

    let str='a'.repeat(10000000)
    let arr=str.split('')

    let starttime=new Date().getTime()


    for(let value of arr){

    }


    let endtime=new Date().getTime();
    console.log('耗时:'+starttime+','+endtime+','+(endtime-starttime))

耗时:200毫秒左右


总结:

  • for循环的效率最高

  • forEach 和for of可以直接遍历出数值,效率也还不错

  • for in 和map效率最低,不过map可以返回一个新数组,用于克隆数组比较方便

  • 慎用for in,除非必要

对象遍历

for in

for in虽说在上面遍历数组时,性能差了点,但是人家可以遍历对象啊哈哈,也算是扳回一局

let obj={
    name:'珊瑚学院',
    url:'shanhuxueyuan.com'
}

for(let key in obj){
    console.log(key+':'+obj[key])   //name:珊瑚学院    url:shanhuxueyuan.com
}

再复杂一点,看看for in对继承、不可枚举属性和对象的Symbol属性是否能够遍历

    let baseobj={
        type:'base',
        price:100
    }


    let obj={
        name:'珊瑚学院',
        url:'shanhuxueyuan.com',
        [Symbol()]:'symbol'      //定义了一个Symbol属性
    }

    Object.setPrototypeOf(obj,baseobj)   //通过setPrototypeOf实现继承

    Object.defineProperty(obj,'url',{
        enumerable:false   //设置url属性不可枚举
    })


    for(let key in obj){
        console.log(key+':'+obj[key])
        
        //name:珊瑚学院
        //type:base
        // price:100
    }

for in可以遍历自身和继承的对象的可枚举属性,但是不能遍历Symbol属性

Object.keys

Object.keys返回一个数组,数组中为对象的键,我们可以再通过数组的forEach进行遍历

let obj={
    name:'珊瑚学院',
    url:'shanhuxueyuan.com'
}

Object.keys(obj).forEach(key=>console.log(key+':'+obj[key]))

同样的看看对继承的遍历

let baseobj={
    type:'base',
    price:100
}



let obj={
    name:'珊瑚学院',
    url:'shanhuxueyuan.com',
    [Symbol()]:'symbol'
}

Object.setPrototypeOf(obj,baseobj)

Object.defineProperty(obj,'url',{
    enumerable:false   //不可枚举
})

Object.keys(obj).forEach(key=>console.log(key+':'+obj[key]))

//name:珊瑚学院

Object.keys只能遍历自身的可枚举属性,但是不能遍历Symbol属性,不能遍历继承对象的属性

Object.getOwnPropertyNames

直接看实例

let baseobj={
    type:'base',
    price:100
}



let obj={
    name:'珊瑚学院',
    url:'shanhuxueyuan.com',
    [Symbol()]:'symbol'
}

Object.setPrototypeOf(obj,baseobj)

Object.defineProperty(obj,'url',{
    enumerable:false   //不可枚举
})

Object.getOwnPropertyNames(obj).forEach(key=>console.log(key+':'+obj[key]))

//name:珊瑚学院
//url:shanhuxueyuan.com

Object.getOwnPropertyNames可以遍历自身的所有属性(不包括Symbol属性,但是包含不可枚举属性),不能遍历继承对象的属性

Reflect.ownKeys

let baseobj={
    type:'base',
    price:100
}



let obj={
    name:'珊瑚学院',
    url:'shanhuxueyuan.com',
    [Symbol()]:'symbol'
}

Object.setPrototypeOf(obj,baseobj)

Object.defineProperty(obj,'url',{
    enumerable:false   //不可枚举
})

Reflect.ownKeys(obj).forEach(key=>console.log(obj[key]))

//珊瑚学院
//shanhuxueyuan.com
//symbol

Reflect.ownKeys可以遍历自身的所有属性,包括不可枚举属性和Symbol属性


分享到

点赞(0)