当我们使用for...of循环Object的时候发生了什么?

412 阅读3分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动

for...of

我们都知道for...of是ES6中添加的一个循环迭代的方法,用于遍历数组等,节省我们的代码工作量

let arr = [1, 2, 3, 4, 5, 6]
​
let obj = {
    a: '1',
    b: '2',
    c: '3'
}
​
​
for (let val of arr) {
    console.log(val)
}
​
// 1,2,3,4,5,6for (let val of obj) {
    console.log(val)
}
//TypeError: obj is not iterable

从上面我们可以看到,当使用for...of遍历Object的时候会报错,告诉我们obj不是一个iterable,什么是iterable,翻译过来叫做可迭代的,我们来看for...ofMDN上的定义

for...of语句可迭代对象(包括 ArrayMapSetStringTypedArrayarguments 对象等等)上创建一个迭代循环,调用自定义迭代钩子,并为每个不同属性的值执行语句

现在我们知道了,因为Object不是可迭代对象,所以for...of对它不好用,不能进行正常的遍历

那么为什么数组这一类可以,对象不行呢?它们之间差了什么导致Object不是可迭代对象?

console.dir(arr)
​
console.dir(obj)

通过打印发现,arr相比obj多了一个Symbol.iterable的方法,这个就是迭代的标识,有这个方法的就是可迭代对象,没有的就是不可迭代对象。

Symbol.iterable也可以叫做迭代器,那么我们能否根据for...of迭代数组的原理让obj也能够被迭代呢?手写一个迭代器,说干就干!

let obj = {
    a: '1',
    b: '2',
    c: '3'
}
​
//首先给obj的原型添加一个Symbol.iterator方法
obj[Symbol.iterator] = function () {
    //迭代器首先要有一个迭代协议,是怎么进行这个迭代进行规定,下面就是规定迭代obj的values值
    let values = Object.values(obj)
    //获取obj的所有values
    let index = 0
    //注意这里是固定写法,return返回的是一个对象,对象里面需要有一个next函数,next函数里面再返回一个对象
    return {
        next() {
            if (index >= values.length) {
                //当index大于等于values.length时,循环结束
                return {
                    done: true
                    //done代表循环,true为循环结束,false为循环未结束,继续进行
                }
            } else {
                return {
                    value: values[index++],
                    //value表示循环返回的值
                    done: false
                }
            }
        }
    }
}
//这时再使用for...of就能正常打印出来obj的values的值
for (let value of obj) {
    console.log(value)
}
// '1','2','3'

通过上面代码我们简单实现了一个迭代器,那为什么for...of就可以打印出来呢,其实我们还可以再细致的拆分

let values = obj[Symbol.iterator]
​
console.log(values.next())   //{done:false,value:1}
console.log(values.next())   //{done:false,value:2}
console.log(values.next())   //{done:false,value:3}
console.log(values.next())   //{done:true}

从上面的打印我们可以看到,每次执行values.next()的结果都不一样,一直到循环结束,其实for...of就是帮助我们做了这样一个事情,只不过吧里面的value赋给for...of里面的val

这个迭代器是我们自己手写的,迭代协议也是我们自己定义的,刚才我们拿到的是values值,下面定义一个keys的

let obj = {
    a: '1',
    b: '2',
    c: '3'
}
​
//首先给obj的原型添加一个Symbol.iterator方法
obj[Symbol.iterator] = function () {
    //迭代器首先要有一个迭代协议,是怎么进行这个迭代进行规定,下面就是规定迭代obj的keys值
    let keys = Object.keys(obj)
    //获取obj的所有keys
    let index = 0
    //注意这里是固定写法,return返回的是一个对象,对象里面需要有一个next函数,next函数里面再返回一个对象
    return {
        next() {
            if (index >= keys.length) {
                //当index大于等于values.length时,循环结束
                return {
                    done: true
                    //done代表循环,true为循环结束,false为循环未结束,继续进行
                }
            } else {
                return {
                    value: keys[index++],
                    //value表示循环返回的值
                    done: false
                }
            }
        }
    }
}
//这时再使用for...of就能正常打印出来obj的values的值
for (let value of obj) {
    console.log(value)
}
// a,b,c

如果想要得到keys和values,可以直接让values返回一个对象

return {
    value:{
        key:keys[index],
        value:obj[keys[index++]]
    }
}
​
for (let value of obj) {
    console.log(value)
}
// {key:'a',value:'1'}
// {key:'b',value:'2'}
// {key:'c',value:'3'}