迭代器、生成器

42 阅读4分钟

迭代器

  1. 什么是迭代?

    从一个数据集合中按照一定的顺序,不断取出数据的过程。

  2. 迭代和遍历的区别?

    迭代强调的是依次取数据,不一定取多少;遍历强调的是把所有数据全部依次取出。

  3. 什么是迭代器?

    对迭代过程的封装,通常为对象。

  4. 迭代模式是一种设计模式,用于统一迭代过程,必须拥有以下两个功能:

    • 具有得到下一个数据的能力。

    • 具有判断是否还有后续数据的能力。

JS中的迭代器

js规定,如果一个对象具有next方法,且该方法返回一个特定结构的对象,则认为该对象是个迭代器。

该对象的结构如下:

const iterator = { 
    next() { 
        return { 
            value: xxx, //下一个数据的值
            done: boolean //是否迭代完成
        } 
    } 
}

示例:

const list = [1, 2, 3]
const iterator = {
   index: 0,
   next() {
     return {
       value: list[this.index++],
       done: this.index > list.length
     }
   }
}
console.log(iterator.next()) //{value: 1, done: false} 
console.log(iterator.next()) //{value: 2, done: false} 
console.log(iterator.next()) //{value: 3, done: false} 
console.log(iterator.next()) //{value: undefined, done: true}

//自动迭代 
let data = iterator.next() 
while(!data.done) { 
 data = iterator.next() 
}

与遍历不同之处:迭代器内部去处理数组,外部就不用再访问数组了,从而实现对数组使用的解耦。

可迭代协议 iterator protocol

ES6规定,如果一个对象具有知名符号属性 Symbol.iterator,并且属性值是一个迭代器创建函数,则该对象是可迭代的(iterable)。

//可迭代对象
const iteratorObj = {
   index: 0,
   addIndex() {
     this.index = this[Symbol.iterator]().next().value
   },
   [Symbol.iterator]() {
     return {
       next: () => ({
         value: this.index + 1,
         done: false //无限迭代  
       })
     }
   }
}
iteratorObj.addIndex()
iteratorObj.addIndex()
console.log(iteratorObj.index) //2

for-of循环

ES6新出的循环方法,专门遍历可迭代对象,是进行迭代的语法糖。

//迭代一个可迭代对象,实际上就是在不断调用该对象的 Symbol.iterator 方法的 next 方法。
const list = [1, 2, 3] 
for (const item of list) { 
    console.log(item) 
}
//输出: 
//1 
//2 
//3

展开运算符可作用于可迭代对象,可以轻松的将可迭代对象转换为数组:

const iteratorObj = {
  index: 0,
  [Symbol.iterator]() {
    return {
      next: () => ({
        value: ++this.index,
        done: this.index > 3
      })
    }
  }
}
console.log([...iteratorObj])
//输出:
//[1, 2, 3]

生成器

生成器基于迭代器而生,类似语法糖,为了更方便的书写迭代器,且拥有自己的更强大功能。

目前在 react 中重点使用了生成器。在ES7的 async/await 出来前,作为处理异步等待场景的替代方案。

  1. 生成器是通过构造函数 Generator 创建的对象,是个迭代器,也是可迭代对象。
  2. 创建生成器,必须使用生成器函数(Generator Function)。
  • 使用 * 关键字书写一个生成器函数:
//方式1
function* fn1() {}
//方式2
function* fn2() {}
//对象中书写
const obj = {
 *fn3() {}
}

//得到一个生成器
const generator = fn1()
console.log(generator.next()) //{value: undefined, done: true}
  • 注:不可与 async 关键字同时使用,箭头函数无法作为生成器使用。
  1. 生成器函数内部是为了给生成器的每次迭代提供数据,每次调用生成器的 next 方法,将导致生成器函数运行到下一个 yield 关键字的位置。
  • yield 关键字用来产生并传递一个迭代数据。

  • 生成器函数只是生成了一个可迭代对象,函数体内的代码不执行。

// yield 关键字只能在生成器函数内部使用
function* fn() {
  console.log("这段代码在调用fn函数时不会执行,对可迭代对象使用next方法时才执行")
  yield 123
  return 456 //return之后不会再执行迭代
  yield 789
}
const generator = fn()
console.log(generator.next())
//这段代码在调用fn函数时不会执行,对可迭代对象使用next方法时才执行
//{value: 123, done: false}
console.log(generator.next()) //{value: 456, done: true}
console.log(generator.next()) //{value: undefined, done: true}

////可调用生成器的 throw 方法直接结束全部迭代并抛出错误,且可传参
console.log(generator.throw(99)) //Uncaught 99
  1. 相关细节:
  • 函数 return 后就不会再运行了,done 的值为 true,迭代最终结果为 return 的值,可提前结束全部迭代。
  • 调用生成器的 next 方法时,可传递参数(生成器特有),参数会交给 yield 表达式的返回值。
  • 生成器拥有 retrun 和 throw 方法
  1. 怎么在一个生成器函数内调用其他生成器函数?
  • 需要在生成器内部的 yield 关键字后继续加上 * 标记
function* fn1() {
  yield "a"
  yield "b"
}
function* fn2() {
  yield* fn1()
  yield 1
  yield 2
}

const generator = fn2()
console.log(generator.next()) //{value: 'a', done: false}
console.log(generator.next()) //{value: 'b', done: false}
console.log(generator.next()) //{value: 1, done: false}
console.log(generator.next()) //{value: 2, done: false}
console.log(generator.next()) //{value: undefined, done: true}