for in 和for of 循环机制你理解透测了么

在面试题题中经常遇到面试官,问for..in 和for of 的区别,也许你只是只要它是两种遍历方式,for...in 可以遍历对象,for... of 不能遍历对象。除了这些的区别,就想不到什么东西了。今天就给大家盘点下,for...in. 和for... of 循环机制的优缺点,原理以及项目中的使用

for in 循环机制

1. for in 循环会优先迭代数字属性。

 let obj ={
            name:'wkm',
            age:20,
            [Symbol('AA')]:100,
            0:10,
            1:20
        }
        for (const key in obj) {
            console.log("key",key);
        }
         /*  输出结果
         key 0
         key 1
         key name
         key age
        */
复制代码

2. 无法迭代Symbol 类型的属性 (看上面循环没有输出Symbol 的属性)

3. 会遍历私有的和公有的可枚举属性

 
       Object.prototype.AAA = 200;
        Object.prototype[10] = 300
        let obj ={
            name:'wkm',
            age:20,
            [Symbol('AA')]:100,
            0:10,
            1:20
        }
        for (const key in obj) {
            console.log("key",key);
        }
        /* 
         key 0
         key 1
         key name
         key age
         key 10
         key AAA
        */
复制代码

看上面结果,因为他遍历私有的和公有的可枚举属性,比如说项目中有个类,而且类的原型上有方法那么,用for in 去遍历它,原本不用它原型上的方法但是也会遍历到。浪费性能

基于上面的问题我们可以用obj.hasOwnProperty(key) 避免跌倒公有的属性

  Object.prototype.AAA = 200;
        Object.prototype[10] = 300
        let obj ={
            name:'wkm',
            age:20,
            [Symbol('AA')]:100,
            0:10,
            1:20
        }
        for (const key in obj) {
            if(!obj.hasOwnProperty(key)) break // 避免迭代公有属性
            console.log("key",key);
        }
        /* 
         key 0
         key 1
         key name
         key age
        */
复制代码

小结

从上面例子可以看出for in 循环的缺点 无法迭代Symbol 类型的属性, 会遍历私有的和公有的可枚举属性 尤其是会默认遍历公有的和私有的可枚举属性,对性能消耗会大。所以项目中尽量不用for in 循环,自己封装个遍历对象的方法。另外for in 除了可以遍历对象,还可遍历数组 和字符串,但是它不可以遍历 Set 和map 结构

封装循环对象的方法

  1. 会用到检测数据类型和检测是不是存对象的写法,下面先把工具函数封装一下
// 检测数据类型的方法封装
(function ({
    var getProto = Object.getPrototypeOf; // 获取实列的原型对象。
    var class2type = {};
    var toString = class2type.toString;
    var hasOwn = class2type.hasOwnProperty;
    var fnToString = hasOwn.toString;
    var ObjectFunctionString = fnToString.call(Object);

    [
        "Boolean",
        "Number",
        "String",
        "Symbol",
        "Function",
        "Array",
        "Date",
        "RegExp",
        "Object",
        "Error"
    ].forEach(function (name{
        class2type["[object " + name + "]"] = name.toLowerCase();
    });

    function toType(obj{
        if (obj == null) {
            return obj + "";
        }
        return typeof obj === "object" || typeof obj === "function" ?
            class2type[toString.call(obj)] || "object" :
            typeof obj;
    }
    // 判断是不是存对象。
    function isPlainObject(obj{
        var proto,
            Ctor,
            type = toType(obj);
        if (!obj || type !== "object") { // 如果类型检测不是对象直接返回。-+
            return false;
        }
        proto = getProto(obj); // 获取实列对象的原型对象。
        if (!proto) {
            return true;
        }
        Ctor = hasOwn.call(proto, "constructor") && proto.constructor;
        return typeof Ctor === "function" && fnToString.call(Ctor) === ObjectFunctionString;
    }

    window.toType = toType;
    window.isPlainObject = isPlainObject;
})();
复制代码
  1. 封装自己遍历对象的方法
 const eachObject = function eachObject(obj,callback){
            if(!isPlainObject(obj)) throw new TypeError('obj must be an plan object');
            // 保证是个function
            if(!(typeof callback =="function")) callback = Function.prototype;
            // 获取遍历对象的键名 数组,有Symbol 属性加上去 
            let keys = Object.getOwnPropertyNames(obj).concat(Object.getOwnPropertySymbols(obj)),
            i=0,
            len = keys.length,
            key,
            value,
            result;
            // 为啥用 for 不用forEach 因为forEach 循环不能终止
            for(;i<len;i++){
                key= keys[i];
                value = obj[key];
                result = callback(key,value)
                if(result === falsebreak;

            }
            return obj
        }
        // 测试
        let obj={a:1,b:2,c:3}
        eachObject(obj,(key,value)=>{
            console.log(key,value)
        })
复制代码

for of 循环机制

1.for 0f 循环的原理

for of 循环是基于Iterator(遍历器的) 只要拥有Iterator 机制的数据结构都能用for of 循环

1.1 遍历器机制

遍历器(Iterator)是一种接口机制,为各种不同的数据结构提供了统一的访问机制,任何数据结构只要部署了Iterator接口,就可以用for of 进行循环

1.2 遍历器机制的特点

  1. 拥有next 方法用于依次遍历数据结构成员
  2. 每一次遍历都返回一个对象{done:false, value:xxxx}
  3. Done:记录遍历是否完成
  4. value: 当前遍历的结果
根据上面手写个遍历器机制
 class Iterator {
            constructor(assemble) {
                this.assemble = assemble;
                this.index = 0;
            }
            // 有个next 方法
            next() {
                let { index, assemble } = this;
                if (index > assemble.length - 1) {
                    // 说明遍历完成
                    return {
                        donetrue,
                        valueundefined
                    }

                }
                // 遍历未来完成
                return {
                    donefalse,
                    value: assemble[this.index++]
                }
            }
        }

        let itor = new Iterator([102030]);
        console.log(itor.next())
        console.log(itor.next())
        console.log(itor.next())
        console.log(itor.next())
复制代码
通过修改原来的遍历器机制实现数据隔一个循环一次
 let arr = [10,20,30,40,50,60,70]
        arr[Symbol.iterator] = function(){
            let self = this;
            index = -2;
            return {
                next(){
                    if(index>self.length-1){
                        return {
                            done:true,
                            value:undefined
                        }

                    }
                    return {
                        done:false,
                        value:self[index+=2]

                    }
                }
            }
        }
        for(let item of arr){
        /* 
        循环过程: 
        1.首先获取[Symbol.iterator] 属性值函数 并将其执行拿到一个迭代器对象
        2. 每一次循环都执行一次 iterator.next()-> {done,value}
        3. 把value 的值给item 当done 为ture 时结束循环
         */
            console.log(item)
        }
复制代码

2. 对象不具有遍历器机制,想让它用for of 循环,给它原型上加上遍历器

 Object.prototype[Symbol.iterator] = function ({
            let obj = this,
                // 获取对象的键名,如果对象里有Symbol 属性拼接上, 
                keys = Object.getOwnPropertyNames(obj).concat(Object.getOwnPropertySymbols(obj))
            index = 0;
            return {
                next() {
                    if (index > keys.length - 1) {
                        return {
                            donetrue,
                            valueundefined
                        }
                    }
                    return {
                        donefalse,
                        value: obj[keys[index++]]
                    }
                }
            }
        }

        let obj = { a1b2c3 }

        for (let value of obj) {
            console.log(value)
        }
复制代码

3 for of 小结

  • 拥有Symbol.iterator属性的数据结构(都可以被遍历)
  • for of 能遍历 数组,部分类数组, String Set Map
  • 对象默认不具有遍历器机制,所以不能用for of 遍历 要想用for of 遍历,必须在对象原型上加遍历器

总结

for in 和for of 都是用来遍历的,for in 可以遍历 对象 字符串, 数组,但是由于for 会迭代原型上的可遍历属性,因此他的性能比较差,所以项目中迭代对象的方法做好要自己封装

for of 循环是只要数据结构的原型上Symbol.iterator 方法,都能迭代,数据结构原型上没有Symbol.iterator 方法的在原型上加上遍历器方法就可以了,迭代。

猜你喜欢

转载自juejin.im/post/7035854033424384007