likes
comments
collection
share

从本质、性能、基本实现上,ES5数组遍历 VS ES6数组遍历,强强对抗!

作者站长头像
站长
· 阅读数 25

读这篇文章,我需要花多长时间?

字数:3082 时间:10min

读这篇文章,我能学到什么?

🍪 es5数组与es6数组的比较,分析了各遍历的特点、性能、基本实现。 for...of、for、forEach、map、filter、some、every、reduce、reduceRight、for...in等。

🍪 面试题:

1.map与forEach本身能终止循环吗?

2.详细说说for循环如何污染全局变量?

3.for...in和for...of有什么区别?

4.在循环 for、for-in、forEach、for-of 、map中改变item的值,会发生什么?

5.在循环 for、for-in、forEach、for-of、map性能如何?

ES5数组 VS ES6数组

1 ES5数组遍历

  • forEach、map、filter、some、every传递的参数有:

    🍪 currentValue:必须,当前元素。

    🍪 index:可选。当前元素的索引值。

    🍪 arr:可选、当前元素所属的数组对象。

    🍪 thisValue:可选。传递给函数的值一般用this值,如果这个参数为空,undefined会传递给this值。

    // 比如:forEach:
    arr.forEach((currentValue,index,arr)=>{},thisValue)
    
  • reduce、reduceRight传递的参数有:

    🍪 total:上一次调用回调返回的值,或者提供的初始值。

    🍪 currentValue:必须,当前元素。

    🍪 index:可选。当前元素的索引值。

    🍪 arr:可选、当前元素所属的数组对象。

    🍪 initialValue:表示传递给函数的初始值(作为第一次调用callback的第一个参数)

    // 比如:reduce:
    arr.reduce((total,currentValue,index,arr)=>{},initialValue)
    

1.1 for

  • 基本实现:

     const arr = [0, 1, 3, 2]
    
    for (let i = 0; i < arr.length; i++) {
        console.log(arr[i]);
    }
    console.log(i)// undefined
    
    const arr = [0, 1, 3, 2]
    
    for (var i = 0; i < arr.length; i++) {
        console.log(arr[i]);
    }
    console.log(i)// 4
    

    上面的两个遍历有啥区别?细心的你就会发现,上面两个遍历就是let与var的区别。非常叻仔的你就会发现这是作用域的问题(块级作用域与全局作用域)。

    🍪 块级作用域:let声明的变量只能在当前作用域内有效,也就是上面的for代码块里有效,所以在for作用外调用的变量是没声明的。

    🍪 全局作用域:var定义的i是for函数的全局变量,每次递增不会重新声明,由于每次递增没有发现i声明,但是for的全局存在i,此时会通过作用域链去找i,找到的时候for循环已经执行完毕,退出循环了。

  • for性能消耗

    🍪 普通版for循环

     let data = new Array(100000).fill(0);
    // 开始for这个计时器的计时
    console.time('for');
    for (let i = 0; i < data.length; i++) {
    }
    // 结束for这个计时器的计时
    console.timeEnd('for');
    // 2.5ms
    

    🍪 优化版循环

     console.time('for');
    for (let i = 0, len = data.length; i < len; i++) {
    }
    console.timeEnd('for');
    // 2.17ms
    

1.2 forEach

  • 从Array.forEach源码角度去理解forEach

    function myForEach(arr, callback) {
                let T, k;
                if (arr === null) {
                    throw new TypeError('this is null or not defined');
                }
                // 用于处理若传入的arr为非数组的情况
                const obj = Object(arr);
                const len = obj.length >>> 0;// 无符号右移:十进制转化为二进制
                if (typeof callback !== 'function') {
                    throw new TypeError(`${callback} is not a function`);
                }
                // 保证函数参数不止一个
                if (arguments.length > 1) {
                    T = callback;
                }
                k = 0;
                while (k < len) {
                    // 如果指定属性在指定的对象或其原型链中,则in运算符返回true
                    // 用于过滤未初始化值
                    if (k in obj) {
                        const kValue = obj[k];
                        // kValue, k, obj对应着forEach回调函数3个参数,数组当前项、当前索引、数组对象本身
                        // call:将callback的this指向其自己的内部
                        callback.call(T, kValue, k, obj);
                    }
                    k++;
                }
                return undefined;
            }
            const arr = [1, 2, 3];
            myForEach(arr, (item) => {
                console.log(item);
            })
    
  • forEach特点

    🍪 forEach方法不会改变原数组,也没有返回值。

    🍪 forEach无法使用break,continue跳出循环,使用return时,效果和在for循环中使用continue一致。

    🍪 forEach方法无法遍历对象仅仅适用于数组的遍历。

    🍪 forEach() 对于空数组是不会执行回调函数的

  • forEach的基本实现

    arr.forEach((currentValue,index,arr)=>{},thisValue)
    
    const arr = [2, 3, 5, 1, 6];
    arr.forEach((item, index, array) => {
    // array也就是arr
        console.log(index, item, array);
    })
    
  • forEach性能消耗

    console.time('forEach');
    data.forEach(item => {
    });
    console.timeEnd('forEach');
    // 9.18ms
    

1.3 map

  • 从Array.map源码角度去理解

    Array.prototype.myMap = function (callback) {
                var T, A, k;
                if (this == null) {
                    throw new TypeError('this is null or not defined');
                }
                var O = Object(this);
                var len = O.length >>> 0;
                if (typeof callback !== 'function') {
                    throw new TypeError(callback + ' is not a function');
                }
                if (arguments.length > 1) {
                    T = arguments[1];
                }
                // 创建数组对象
                A = new Array(len);
                k = 0;
                while (k < len) {
                    var kValue, mappedValue;
                    if (k in O) {
                        mappedValue = callback.call(T, kValue, k, O);
                        A[k] = mappedValue;
                    }
                    k++;
                }
                return A;
            };
    
     const arrMap = [
                { name: '孙悟空', sex: 0, org: '' },
                { name: '猪八戒', sex: 0, org: '' },
                { name: '唐僧', sex: 0, org: '' },
                { name: '沙僧', sex: 0, org: '' }];
    arrMap.myMap((item) => {
                console.log(item);
                return item;
            });
    
  • map的特点

    🍪 map方法不会对空数组进行检测。

    🍪 map方法遍历数组是返回一个新数组,不会改变原数组

    🍪 map方法有返回值,可以return出来,map的回调函数中支持return返回值。

    🍪 map方法无法遍历对象,仅适用于数组的遍历。

  • map的基本实现

    let arr = [2, 3, 5, 1, 6];
    let newArr = arr.map((item, index, array) => item = item * 2);
    console.log(arr, newArr);
    //  [2, 3, 5, 1, 6],[4, 6, 10, 2, 12]
    
  • map性能消耗

    console.time('map');
    data.map(item => {
    });
    console.timeEnd('map');
    // 20.6ms
    

1.4 filter

  • filter的特点

    🍪 filter方法会返回一个新的数组,不会改变原数组。

    🍪 filter方法不会对空数组进行检测。

    🍪 filter方法适用于检测数组。

  • filter的基本实现

    let arr = [2, 3, 5, 1, 6];
    let newArr = arr.filter((item, index, array) => item>3);
    console.log(arr, newArr);
    //  [2, 3, 5, 1, 6],[5,6]
    
  • filter的特质

    🍪 特别地,filter方法可以用来移除数组中的undefined、null、NAN等值

    let arr = [1, undefined, 2, null, 3, false, '', 4, 0];
    let newArr = arr.filter(Boolean);
    console.log(newArr);// [1,2,3,4]
    
  • filter性能消耗

    console.time('filter');
    data.filter(item => item < 0);
    console.timeEnd('filter');
    // 16.2ms
    

1.5 some、every

  • 特点

    🍪两个方法都不会改变数组,会返回一个布尔值

    🍪两个方法都不会对空数组进行检测

    🍪两个方法都仅适用于检测数组

  • 基本实现

    🍪 some:数组中只要有一个元素符合判断条件的,返回true,否则false

    🍪 every:数组中只有每个元素都符合判断条件的就返回true,否则false

    const arr = [1,2,45,-1,0];
    arr.some(item=>item<0);// true;
    arr.every(item=>item<0);// false;
    

1.6 reduce、reduceRight

  • 特点

    🍪 两个方法都不会改变数组。

    🍪 两个方法如果添加初始值,就会改变原数组,会将这个初始值放在数组的最后一位。

    🍪 两个方法对于空数组是不会执行回调函数的。

  • 基本实现

    🍪 reduce:正序遍历,接收一个函数作为累加器,数组中的每个值从左到右开始累加,最终计算为一个值。

    let arr = [1, 2, 3, 4]
    let sum = arr.reduce((prev, cur, index, arr) => {
        return prev + cur
    }, 5)
    console.log(arr, sum);// [1,2,3,4] 15
    

    🍪 reduceRight:与reduce用法一样,但它逆序遍历,数组中每个值从右到左开始累加,最终计算为一个值。

1.7 for...in

  • 特点

    🍪 for...in方法不仅会遍历当前的对象所有的可枚举属性,还会遍历其原型链上的属性

  • 基本实现

    const obj = { a:1,b:2,c:3}
    for(let i in obj){
      console.log('键名',i);
      console.log('键值',obj[i]);
    }
    
  • for in性能消耗

    console.time('for in');
    for (item in data) {
    
    }
    console.timeEnd('for in');
    // 178ms
    

2 ES6遍历

2.1 find、findIndex

  • find:通过函数内判断的数组的第一个元素的值。

    let arr = [2,1,3,5,4];
    arr.find(item=>item>2);// 3
    
  • findIndex:找到数组中符合条件的第一个元素位置。

    let arr = [2,1,3,5,4];
    arr.findIndex(item=>item>2);// 索引为2
    

2.2 for...of

  • for of特点

    🍪 for of方法只会遍历当前对象的属性不会遍历其原型链上的属性

    🍪 for of方法适用遍历数组、类数组、字符串、map、set等有迭代器对象的集合。

    🍪 for of方法不支持遍历普通对象,因为其没有迭代器对象。如果想要遍历一个对象的属性,可以用for in

    🍪 可以使用break、continue、return来中断循环遍历。

    🍪 对于for of的循环,可以由breakcontinereturn终止,这种情况下,迭代器关闭。

  • for of基本实现

    let arr = [    
            {id:1, value:'hello'},    
            {id:2, value:'world'},    
            {id:3, value:'JavaScript'}
    ]
    for (let item of arr) {  
            console.log(item)
    }
    // 输出结果:{id:1, value:'hello'}  {id:2, value:'world'} {id:3, value:'JavaScript'}
    
  • for of性能消耗

    console.time('for of');
    for (item of data) {
    
    }
    console.timeEnd('for of');
    // 20ms
    

2.3 keys()、values()、entries()

  • keys() 返回数组的索引值(对象键名)。
  • values() 返回数组的元素(对象键值)。
  • entries() 返回数组的键值对。

Object.keys()方法返回的数组中的值都是字符串,也就是说不是字符串的key值会转化为字符串 结果数组中的属性值都是对象本身可枚举的属性,不包括继承来的属性。

const arr = [
            { name: '孙悟空', sex: 0, org: '' },
            { name: '猪八戒', sex: 0, org: '' },
            { name: '唐僧', sex: 0, org: '' },
            { name: '沙僧', sex: 0, org: '' }];
for (let item of arr.keys()) {
    console.log(item);// 0 1 2 3
}
for (let item of arr.values()) {
    console.log(item);// 整个数组对象
}
for (let item of arr.entries()) {
    console.log(item);// [数组每个对象的索引,数组中的每个对象]
}

面试官会问什么?

1.map与forEach本身能终止循环吗?

它们本身是不能终止循环,而是通过抛出new throw error()try...catch去捕获这个错误才可以终止循环。

let list=[1,2,3,4,5,6];
try{
  list.map(item=>{
     if(item===3){
          throw new Error()
         }
     console.log(item)
  })
} catch {}
// 1 2

es6对这个缺陷做了一个改进就是for of的出现。

2.详细说说for循环如何污染全局变量?

3.for...in和for...of有什么区别?

  • 遍历数组时

    🍪 for...in获取到的是每一项的索引值。

    🍪 for...of获取到的是每一项的值。

    const arr = [
        {
            name: '孙悟空',
            age: 500,
            org: '花果山'
        }
    ]
    
    for (item in arr) {
        console.log(item)
    }
    // 0
    
    for (item of arr) {
        console.log(item) // {name: '孙悟空',age: 500,org: '花果山'}
    }
    
  • 遍历对象时

    🍪 for...in获取到的是每一项的key值。

    🍪 for...of不能用于遍历对象,会报错,对象数组可以。

    const obj = {
        name: '孙悟空',
        age: 500,
        org: '花果山'
    };
    for (item in obj) {
        console.log(item)
    }
    // name age org
    
    for (item of obj) {
        // 报错
        console.log(item)
    }
    

4.在循环 for、for-in、forEach、for-of 、map中改变item的值,会发生什么?

了解过js都知道迭代器吧,就是遍历的特性,对于item.next().value进行操作。

🍪 如果原来的值是引用类型,那么iterator.next().value 和 arr[i]表示的是同一个对象,所以可以改变原来的item。

🍪 如果原来的值是基础类型,那么iterator.next().value 和 arr[i]分别指向了一个基础类型的值,所以不会改变原来的item。

  • 改变item元素值

    const arr = [
                { name: '孙悟空', org: '花果山' },
                9,
                'swk',
                function f() {
                    console.log(3);
                },
                [9, 9, 9, 9],
                new Date()
            ];
    // 数组元素全变为'西天取经'
    for (let i = 0; i < arr.length; i++) {
        arr[i] = '西天取经';
    }
    
    // arr数组元素没有变
    arr.forEach((item) => {
        item = '西天取经';
    })
    
    // arr数组元素没有变
    arr.map((item) => {
        item = '西天取经';
    })
    
    // arr数组元素没有变
    for (let item in arr) {
        item = '西天取经';
    }
    
    // arr数组元素没有变
    for (let item of arr) {
        item = '西天取经';
    }
    
  • 改变item元素属性

    const arr = [
                { name: '孙悟空', org: '花果山' },
                9,
                'swk',
                function f() {
                    console.log(3);
                },
                [9, 9, 9, 9],
                new Date()
            ];
    // 数组元素如下图所示
    for (let i = 0; i < arr.length; i++) {
        arr[i].org = '西天取经';
    }
    // 数组元素如下图所示
    arr.forEach((item) => {
        item.org = '西天取经';
    })
    // 数组元素如下图所示
    arr.map((item) => {
        item.org = '西天取经';
    })
    
    // arr数组不变
    for (let item in arr) {
        item.org = '西天取经';
    }
    // 数组元素如下图所示
    for (let item of arr) {
        item.org = '西天取经';
    }
    

    从本质、性能、基本实现上,ES5数组遍历 VS ES6数组遍历,强强对抗!

5.for、for-in、forEach、for-of 、map性能比较如何?

统一执行上面的性能代码得出以下效果: 从本质、性能、基本实现上,ES5数组遍历 VS ES6数组遍历,强强对抗!

站在大佬的肩膀上,可以看的更远!

# 在循环 for、for-in、forEach、for-of 、map中改变item的值,会发生什么? |# JS数组循环的性能和效率分析(for、while、forEach、map、for of)