for...in和for...of你到底懂了吗?

947 阅读5分钟
  • for..of能遍历对象吗?
  • for...in和for...of的区别是什么?
  • 什么是可枚举以及可迭代?

这篇笔记就是用来彻底搞清楚这几个问题,用于备忘。

for...in

官方解释:for...in以任意顺序遍历一个对象的可枚举属性; 注:

  • Symbol属性除外。
  • 既可以遍历对象自身的可枚举属性,又能遍历到对象原型上的可枚举属性(可使用getOwnPropertyNames()或hasOwnProperty()来确定属性是否是对象本身的属性)。
  • for...in是为遍历对象的属性而构建的,不建议与数组一起使用。数组可用forEach和for...of。

什么是可枚举属性?

可枚举属性是指那些内部“可枚举”标志设置为true的属性;其值会根据初始值的方式有所不同:

  1. 直接通过等号赋值的变量,其对应的标识enumerable默认为true;
let o = {'name':'xixinxixin'}
console.log(o.propertyIsEnumerable('name'))//true
  1. 通过Object.defineProperty等定义的属性,该标识默认为false。
let o = {}
Object.defineProperty(o,'name',{value:'xixinxixin'})
console.log(o.propertyIsEnumerable('name'))//false

for...of

for...of是ES6引入来遍历所有数据结构的统一方法,其中这里的所有数据结构具有可迭代数据结构。

可迭代

可迭代对象是指:

  • 对象内置或者自己实现了一个@@iterator方法,可通过常量Symbol.iterator访问该属性;
  • 可迭代对象是具有 Symbol.iterator 属性的对象。
  • 一个数据只要部署了Symbol.iterator,就具有了iterator接口,就可以使用for...of循环遍历它的成员。即for...of循环内部调用的数据结构为Symbol.iterator方法。

Symbol.iterator

Symbol.iterator为每一个对象定义了默认的迭代器。该迭代器可以被for...of循环使用。

  • 遍历器(Iterator)就是这样一种机制。它是一种借口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署了Iterator借口,就可以完成遍历操作(即依次处理该数据结构的所有成员)
  • Iterator的作用有三个:一是为各种数据结构,提供一个统一的、简便的访问接口;二是使得数据结构的成员能够按某种次序排列;三是ES6创造了一种新的遍历for...of循环、Iterator接口主要供for...of循环使用。 此外,[Symbol.iterator]是一个无参数的函数,返回给定对象的迭代器。那什么是迭代器对象?以及为什么会出现迭代器?👇

为什么会出现迭代器?

一般在使用JS编程时,你可能会写过或看到过如下类似的代码:

var colors = ["red", "green", "blue"];

for (var i = 0, len = colors.length; i < len; i++) {
    console.log(colors[i]);
}

在for循环中,需要一个变量i来保持指向集合中的某个值。如果当集合嵌套多层,比较复杂的时候,需要使用多个变量来遍历;嵌套越多或者对象越复杂那么维护的变量越多,出错的几率也就越大;而迭代器的出现就是来解决这类问题。

迭代器对象

  • 迭代器对象是具有特定迭代接口的对象;

  • 所有的迭代器对象都具有一个next方法,next方法返回一个结果对象;同时注意,next()方法必须是一个无参数函数,且返回一个拥有以下两个属性的对象:

    • done:如果迭代器可以产生序列的下一个值,则为false。如果迭代器已将序列迭代完毕,则为true。在这种情况下,value是可选的,如果他依然存在,即迭代结束之后的默认值。
    • value:迭代器返回的任何JavaScript值。done为true时可省略
  • 迭代器保持着一个指向集合中某个值的内部指针,每次调用next方法,都会返回一个应有的值。 注:next()方法必须返回一个对象,该对象具有两个属性:done和value,如果返回一个非对象值(false或undefined)则会抛出一个TypeError异常

let o={};//此时不是可迭代的
o[Symbol.iterator]=function(){
    return {
        next:function(){
            if(this.index<3){
            return {
                value:this.index++,
                done:false
            }
            }else{
            return {
                done:true
            }}
        },
        index:0
    }
}
//现在对象o是可遍历的,可以被for...of以及展开语法操作...
for(let key of o){
console.log(o)
}//logs:0,1,2

综上所述:一个返回特定迭代接口的对象,就是可迭代的对象;这个特定接口返回一个包含next方法的迭代器对象。

在前面我提到了在 for 循环中跟踪索引的问题。迭代器的出现是该问题解决方案的第一部分。而for-of循环是第二部分,因为它不需要完全跟踪集合中的索引,让你可以自由地专注于处理集合的内容。 for...of在可迭代对象上创建一个迭代循环,调用自定义迭代钩子,并为每个不同属性的值执行语句 。

let values = [1, 2, 3];

for (let num of values) {
    console.log(num);//1,2,3
}

通过上面例子,我们可以看到我们遍历数组时,无需关心索引,只需关注数组中的内容。

注:

  1. 可迭代对象(包括Array,Map,Set,String,TypedArray,arguments对象等等);
  2. 生成器创建的所有迭代器也是可迭代的,因为生成器默认分配 Symbol.iterator 属性。
  3. 普通创建的对象是不能被for...of迭代的,除非将对象构建成可迭代对象;

总结

  • 可迭代对象本身或原型链上实现了 [Symbol.iterable]方法,可以根据 typeof obj[Symbol.iterable] === "function"判断是否是可迭代对象
  • 迭代方法返回迭代器对象,一个带有 next 方法的对象,next 方法返回 done 和 value 构成的迭代信息
  • for...in和for...of的区别在于迭代的方式。前者以任意顺序遍历对象的可枚举属性。后者遍历可迭代对象定义要迭代的数据。