ES6 中的 for of 循环与迭代器接口

78 阅读3分钟

for of 循环和 for in 循环的区别

for of 循环是 ES6 中新增的一种循环方式,相比于 for in 循环,for of 循环每次的循环结果是 value,而 for in 循环每次循环的结果是 key。

  1. for in 循环
    for in 循环每次得到的结果是当前循环对象的 key 值,而且会将对象原型上的属性也遍历出来,所以一般我们会使用 Object.hasOwnProperty 来判断该属性是否是原型上的属性。
const obj = {
  name: 'Li',
  age: 19,
  address: [
    'chain',
    'guangdong',
    'shenzhen',
  ]
}
const arr = [1, 2, 3, 4]

Array.prototype['jj'] = 1111

console.log('for in 循环:')
for (const key in obj) {
  if (Object.hasOwnProperty.call(obj, key)) {
    console.log('key: ' + key + ' value: ' + obj[key])
  }
}
console.log()
for (const key in arr) {
  // 这里没有使用 Object.hasOwnProperty 判断属性是否属于原型上的属性
  // 所以输出的结果中有属性 'jj'
  console.log('key: ' + key + ' value: ' + arr[key])
}

结果:
image.png

  1. for of 循环
    for of 循环每次循环的结果是当前循环对象的 value,且不会将原型上的属性值给循环出来。而且 for of 不能用于对象上。
const obj = {
  name: 'Li',
  age: 19,
  address: [
    'chain',
    'guangdong',
    'shenzhen',
  ]
}
const arr = [1, 2, 3, 4]

// 给数组元素添加原型
Array.prototype['jj'] = 1111

console.log('for of 循环:')
for (const value of arr) {
  console.log('value: ' + value)
}
console.log()
for (const value of obj) {
  console.log('vaule: ' + value)
}

结果:
可以看到对数组的循环可以正确的得出结果且不会循环到原型上的数据。但是对象的循环则是直接报错!
报错的原因是: obj is not iterable,意思为 obj 这个对象是不可遍历的
image.png

什么是可遍历接口

可便利接口主要是提供给 for of 消费的,可遍历接口就是 Symbol.Iterator 属性,一个数据结构部署了 Symbol.iterator 属性,那么这个数据结构就可以被 for of 循环

我们来看看在浏览器中直接输出一个数组对象
@CV5FQG48NARQI8EZJ99G.png

可以看到数组的 Symbol.iterator 属性是一个方法,执行这个方法返回一个对象,我们称之为迭代器对象,这个对象中含有一个 next 的方法
E9P42GA{F9TIY)TAIPPA{L.png

使用迭代器对象循环的原理:

  1. 创建一个指针,用于指向当前数据结构的起始位置
  2. 第一次调用 next 方法,指针指向数据结构中的第一位成员
  3. 不断的调用 next 方法,指针不断的往后移动一位,直到最后一个成员
  4. 每一次调用 next 的方法,返回的结果是一个对象,包含 value(当前循环得到的值) 和 done(循环是否完成) 两个属性

给该数组添加几个值,我们来试一下使用迭代器对数组做循环是什么操作过程
image.png

先重新获取数组的迭代器对象,再一次调用 next 方法对数组循环
image.png

可以看到 next 每次返回的值中包含两个属性,value 表示当前循环得到的值,done 表示循环是否完成。

结论:只有部署了 Symbol.iterator 属性或者说 Symbol.iterator 接口的数据结构才能使用 for of 循环。for of 底层的原理是先获取迭代器对象,然后不断的调用 next 方法得到循环结果,通过 next 返回结果中 done 属性判断循环是否结束。

原生部署了 Symbol.iterator 迭代器接口的数据结构有

  • Array
  • Arguments
  • Set
  • Map
  • String
  • TypedArray
  • NodeList

自定义迭代接口

我们可以自己实现迭代器接口,让一些不能使用 for of 循环的数据结构也可以使用 for of 循环遍历

  1. 给 Object 对象部署 Symbol.iterator 接口
// 给 obj 部署 Symbol.Iterator 的接口
const obj =  {
  name: 'yiyi',
  age: 13,
  address: [ 'chain', 'guangdong', 'shenzhen' ],
  // 部署 Symbol.iterator 属性
  [Symbol.iterator]() {
    let index = -1
    const indexArr = Object.keys(this)
    console.log('迭代器方法执行辣! \n')
    
    return {
      next: () => {
        index ++
        return {
          value: this[indexArr[index]],
          done: index >= indexArr.length
        }
      }
    }
  }
}

for (const value of Obj) {
  console.log(value)
}

运行结果:
image.png