ES6 – 迭代器Iterator

为什么要有Iterator

  • 它是一种接口机制,为各种不同的数据结构提供统一的访问机制
  • 让不支持遍历的数据结构“可遍历”,主要供for ... of消费

基础使用

它是一种接口,这个接口要求return一个对象,并且这个对象要有next函数,而且next函数需要return一个对象,这个对象要有value属性和down属性

例1:组的遍历(数组本身就可以遍历)

function makeIterator(arr) {
  let nextIndex = 0
  return {
    next() {
      return nextIndex < arr.length ? {
        value: arr[nextIndex++],
        down: false
      } : {
        value: false,
        down: true
      }
    }
  }
}
makeIterator(['a', 'b', 'c'])
let it = makeIterator(['a', 'b', 'c'])
console.log(it.next());
console.log(it.next());
console.log(it.next());
console.log(it.next());

从上面的代码可以看出:结构和Generator很像,因为Generator自带了next功能

ES6 - 迭代器Iterator

例2: 遍历不可遍历的对象

如果遍历不可遍历的对象会怎样

let courses = {
  allCourse: {
    frontend:['ES6','Vue','React','小程序'],
    backend: ['Java','Go','Python','Spring boot'],
    webapp: ['Android','ios']
  }
}
for(let item of courses) {
  console.log(item);
}

ES6 - 迭代器Iterator

迭代不可迭代实例的尝试无效。
为了可迭代,非数组对象必须具有 [Symbol.iterator]() 方法。

如果让当前的对象拥有Symbol.iterator方法是不是就可以变成可迭代(可遍历)的了呢?

数组中有 Symbol.iterator 吗?

既然一个对象如果要是可迭代的,就需要有Symbol.iterator方法
,那么我们知道数组是可迭代的,我们不妨看下它是否有这个方法呢?

let arr = ['a','b','c']
console.log(arr);

ES6 - 迭代器Iterator

因此只要对象下有Symbol.iterator属性就表示它是可遍历的或者说可迭代的,那我们是不是也可以通过next()来输出里面的值呢?

let arr = ['a','b','c']
console.log(arr);
let it = arr[Symbol.iterator]()
console.log(it.next())
console.log(it.next())
console.log(it.next())
console.log(it.next())

ES6 - 迭代器Iterator

也就说对于数组这种可迭代的结构,它自带了Symbol.iterator,也就是说它遵循了Iterator协议

Map中有Symbol.iterator 吗?

let map = new Map()
map.set('name','es6')
map.set('age','18')
map.set('school','Beida')
console.log(map);

ES6 - 迭代器Iterator

我们发现Map结构的数据也带了Symbol.iterator,说明它也是可遍历或者说可迭代的对象

既然说可迭代的对象,那我们也可以使用next()遍历里面的属性

let map = new Map()
map.set('name','es6')
map.set('age','18')
map.set('school','Beida')
let it = map[Symbol.iterator]()
console.log(it.next());
console.log(it.next());
console.log(it.next());
console.log(it.next());

ES6 - 迭代器Iterator

因此,Map也具有Iterrator这种接口的数据结构

我们之前学过哪些具备Iterator接口的数据结构呢?

  • Array
  • Map
  • Set
  • String
  • 函数的arguments对象
  • NodeList对象

两个协议

  • 可迭代协议:当前对象上是否有[Symbol.iterator]属性,如果有就表示是可迭代的,使用for ... of 进行遍历,反之则表示不可迭代
  • 迭代器协议:当前的迭代器必须符合下面这种结构:
return {
    next() {
        return {
            done,
            value
        }
    }
}

让我们套用上面两个协议,让不可遍历的对象变成可迭代吧!

let courses = {
  allCourse: {
    frontend:['ES6','Vue','React','小程序'],
    backend: ['Java','Go','Python','Spring boot'],
    webapp: ['Android','ios']
  }
}

将上面不可以迭代的对象,按照上面两个协议进行改写:

let courses = {
  allCourse: {
    frontend:['ES6','Vue','React','小程序'],
    backend: ['Java','Go','Python','Spring boot'],
    webapp: ['Android','ios']
  }
}

courses[Symbol.iterator] = function() {
  let allCourse = this.allCourse
  let keys = Reflect.ownKeys(allCourse)
  let values = []
  return {
    next() {
      // 保证values是空数组
      if(!values.length){
        // 保证 allCourse 中还有属性
        if(keys.length) {
          // 每次只取第一个属性,这样能保证每次取的都不一样
          values = allCourse[keys[0]]
          // 取完就删除第一个属性,删除数组第一个元素,并返回删除的结果
          keys.shift()
        }
      }
      return {
        // vlues中没有值的时候说明allCourse的属性被取完了
        done: !values.length,
        // 每次next的只取第一个
        value: values.shift()
      }
    }
  }
}
for(let item of courses) {
  console.log(item);
}

ES6 - 迭代器Iterator

由于当前既遵循了可迭代协议,又遵循了迭代器协议,因此我们就将不可迭代的对象变成可迭代的对象

这样好像很麻烦啊!那么我们为什么不先取frontend,再取backend,再取webapp最后将数组拼接上呢?

我们不妨设想这样一个场景: 如果一个项目是多人合作的,碰到一个类似上面的对象,如果团队下面很多人都需要在不同的模块或业务中遍历这个对象中的值,那么岂不是我们每个人都要实现一遍,一个个取值,然后拼接到一起

如果我们把这个对象做成可迭代的对象,那么其他人如果想要遍历这个对象只需要使用for... of遍历即可,这样岂不是方便自己也方便他人?

使用Generator的方式实现对象的Iterator

之前学的Generator也是返回一个对象,这个对象也是有next方法,并且next方法的返回值也是valuedone,因此我们可以使用Iterator这个迭代器中使用Generator这样我们就不需要自己去定义next方法

Generator函数有两个特点:

  1. function后加型号*
  2. 里面用到yield表达式
let courses = {
  allCourse: {
    frontend: ['ES6', 'Vue', 'React', '小程序'],
    backend: ['Java', 'Go', 'Python', 'Spring boot'],
    webapp: ['Android', 'ios']
  }
}

courses[Symbol.iterator] = function * () {
  let allCourse = this.allCourse
  let keys = Reflect.ownKeys(allCourse)
  let values = []
  while (true) {
    // 如果values是空的并且allCourse中有属性那么就存
    if (!values.length) {
      if (keys.length) {
        values = allCourse[keys[0]]
        keys.shift()
        yield values.shift()
      } else {
        return false
      }
      // 如果values不是空的就输出被删除的第一个元素
    } else {
      yield values.shift()
    }
  }
}

for(let item of courses) {
  console.log(item);
}

由于Generator中自己实现了next方法,已经具备了valuedone,因此就不需要我们自己重写next方法,也不需要返回具有valuedone的对象

ES6 - 迭代器Iterator

因此,当我们想用Iterator接口重写对象,让不可遍历的对象变成可遍历的对象的时候,我们就要考虑下是要自己实现一个next方法,还是借助Generator帮助我们取实现一个next

总结:为什么说Iterrator是一个接口机制?

对任何的数据结构,虽然当前不可以循环遍历,但是只要它符合可迭代协议和迭代器协议,那么它就可以变成可迭代的,我们就可以使用for ... of的方式对这个结构进行迭代(遍历)。这样任何数据结构都变成可遍历的,可遍历的概念就变得更加的广义了

原文链接:https://juejin.cn/post/7235091963312996413 作者:仗剑走天涯_

(0)
上一篇 2023年5月21日 上午10:36
下一篇 2023年5月21日 上午10:47

相关推荐

发表回复

登录后才能评论