Skip to content

迭代器与生成器

迭代器 Iterator

概念

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

迭代和遍历的区别:迭代强调的是以此取数据,不保证取多少,遍历强调的是要把整个数据依次全部取出。

可迭代协议

迭代器:一个具有 next 方法的对象,next 方法返回下一个数据并且能指示是否迭代完成

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

对象和数组中的部分遍历方法是通过迭代器实现的

遍历

js 对象中支持以下方式的遍历

遍历数组

  1. for 循环

  2. forEach

  3. map

  4. reduce

  5. keys

  6. values

  7. for of

  8. ......

其中keys, values for of需要Iterator支持

遍历 Map/Set

  1. keys

  2. entries

  3. forEach

  4. ......

遍历 Object

  1. for in
  2. Object.keys(obj)得到对象每个属性的数组, 然后使用数组的遍历方法遍历每个key,就能获取每个key对应的value

Iterator 和 for of

IteratorES6提出的一个接口,为各种不同的数据结构提供统一的访问机制。

任何数据结构只要包含Iterator接口,就可以完成遍历操作。

Iterator 的作用

为各种数据结构,提供一个统一的、简便的访问接口。

ES6 提出了新的遍历命令for...of循环,Iterator接口主要供 for...of消费。

Iterator 的遍历过程

既然数组是支持for...of循环的,那数组肯定包含了Iterator接口,我们通过它来看看Iterator的遍历过程。

javascript
const arr = [1, 2, 3, 4, 5]
const iterator = arr[Symbol.iterator]()

iterator.next() // { value: 1, done: false }
iterator.next() // { value: 2, done: false }
iterator.next() // { value: 3, done: false }
iterator.next() // { value: 4, done: false }
iterator.next() // { value: 5, done: false }
iterator.next() // { value: undefined, done: true }
const arr = [1, 2, 3, 4, 5]
const iterator = arr[Symbol.iterator]()

iterator.next() // { value: 1, done: false }
iterator.next() // { value: 2, done: false }
iterator.next() // { value: 3, done: false }
iterator.next() // { value: 4, done: false }
iterator.next() // { value: 5, done: false }
iterator.next() // { value: undefined, done: true }

从图中我们能看出:

  1. Iterator 接口返回了一个有next方法的对象。

  2. 每调用一次next,依次返回了数组中的项,直到它指向数据结构的结束位置。

  3. 返回的结果是一个对象,对象中包含了当前值value和当前是否结束done

用 for of 遍历 Object

javascript
let obj = { a: 1, b: 2, c: 3 }
for (let value of obj) {
  console.log(value)
}
let obj = { a: 1, b: 2, c: 3 }
for (let value of obj) {
  console.log(value)
}

以上代码运行会报错,原因就是 Object 原型链上没有 iterator 方法,我们可以手动实现一个 iterator 方法让 Object 支持迭代器,这样就可以实现 Objectfor of

javascript
function objectIterator() {
  const keys = Object.keys(this)
  let index = 0
  return {
    // 实现了 next 方法,满足成为迭代器的条件,则该[Symbol.iterator]方法的返回值是一个迭代器
    next: () => {
      const done = index >= keys.length
      const value = done ? undefined : this[keys[index]]
      index++
      return {
        value,
        done
      }
    },
    // 迭代器提前关闭执行的操作
    return() {
      console.log('提前关闭!')
      return { done: true }
    }
  }
}

Object.prototype[Symbol.iterator] = objectIterator

let obj = { a: 1, b: 2, c: 3 }
for (let value of obj) {
  console.log(value)
}
// 1
// 2
// 3
function objectIterator() {
  const keys = Object.keys(this)
  let index = 0
  return {
    // 实现了 next 方法,满足成为迭代器的条件,则该[Symbol.iterator]方法的返回值是一个迭代器
    next: () => {
      const done = index >= keys.length
      const value = done ? undefined : this[keys[index]]
      index++
      return {
        value,
        done
      }
    },
    // 迭代器提前关闭执行的操作
    return() {
      console.log('提前关闭!')
      return { done: true }
    }
  }
}

Object.prototype[Symbol.iterator] = objectIterator

let obj = { a: 1, b: 2, c: 3 }
for (let value of obj) {
  console.log(value)
}
// 1
// 2
// 3

生成器 Generator

概念

生成器是一个通过构造函数Generator创建的对象,生成器既是一个迭代器(有 next 方法),同时又是一个可迭代对象(有知名符号),可以用于 for of 循环。

js
//伪代码,由JS引擎内部使用

var generator = new Generator()
generator.next() //它具有next方法
var iterator = generator[Symbol.iterator] //它也是一个可迭代对象
for (const item of generator) {
  //由于它是一个可迭代对象,因此也可以使用for of循环
}
//伪代码,由JS引擎内部使用

var generator = new Generator()
generator.next() //它具有next方法
var iterator = generator[Symbol.iterator] //它也是一个可迭代对象
for (const item of generator) {
  //由于它是一个可迭代对象,因此也可以使用for of循环
}

生成器函数的书写

js
// 使用这种语法定义的函数,执行后返回一个生成器
function* method {}
// 使用这种语法定义的函数,执行后返回一个生成器
function* method {}

运行过程

生成器函数的运行过程如下

  1. 第一次调用:生成了生成器,并没有调用内部逻辑

  2. 后续调用:每次调用生成器的next方法,将导致生成器函数运行到下一个 yield 关键字的位置。yield 关键字的执行表示产生了一条迭代数据。

javascript
function* generator() {
  let a = yield 1
  let b = yield 2
  let c = yield 3
  console.log(`a = ${a}, b = ${b}, c = ${c}`)
}

let gen = generator() // "Generator { }"
console.log(gen.next(10)) // {value: 1, done: false}
console.log(gen.next(9)) // {value: 2, done: false}
console.log(gen.next()) // {value: 3, done: false}
console.log(gen.next(8))
// a = 9, b = undefined, c = 8
// {value: undefined, done: true}
function* generator() {
  let a = yield 1
  let b = yield 2
  let c = yield 3
  console.log(`a = ${a}, b = ${b}, c = ${c}`)
}

let gen = generator() // "Generator { }"
console.log(gen.next(10)) // {value: 1, done: false}
console.log(gen.next(9)) // {value: 2, done: false}
console.log(gen.next()) // {value: 3, done: false}
console.log(gen.next(8))
// a = 9, b = undefined, c = 8
// {value: undefined, done: true}

从上述代码可以看出:

next()的参数会传给上一条执行的yield语句左边的变量。 所以,第一次调用gen.next(10),参数 10 并没有传递给任何变量。

第二次调用gen.next(9)时,参数 9 传递给了上一条执行的yield语句左边的变量a

第三次调用gen.next()时,没有传递参数,所以变量b的值为undefined

第四次调用gen.next(8)时,虽然返回值中done: true,但也不影响参数的传递

此外,next()函数的返回值,与传递的参数无关,只与yield右边返回的值以及生成器是否结束有关。

yield 关键字和 yield*表达式

yield 关键字用来暂停和恢复一个生成器函数

yield* 表达式用于委托给另一个 generator 或可迭代对象

生成器与 async/await 的实现

js
async function getResult() {
  await new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(1)
      console.log(1)
    }, 1000)
  })
  console.log(2)
}
async function getResult() {
  await new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(1)
      console.log(1)
    }, 1000)
  })
  console.log(2)
}
js
function* getResult() {
  yield new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(1)
      console.log(1)
    }, 1000)
  })
  console.log(2)
}
const gen = getResult()
function co(g) {
  g.next().value.then(() => {
    co(g)
  })
}
co(gen)
function* getResult() {
  yield new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(1)
      console.log(1)
    }, 1000)
  })
  console.log(2)
}
const gen = getResult()
function co(g) {
  g.next().value.then(() => {
    co(g)
  })
}
co(gen)

通过生成器对比两个字符串是否相等

js
function* walk(str) {
  let part = ''
  for (let i = 0; i < str.length; i++) {
    part += str[i]
    yield part
  }
}

function compare(s1, s2) {
  let genS1 = walk(s1)
  let genS2 = walk(s2)
  while (true) {
    const n1 = genS1.next()
    const n2 = genS2.next()
    if (n1.value !== n2.value) {
      return false
    }

    if (n1.done && n2.done) {
      return true
    }
  }
}

const s1 = '1234567777'
const s2 = '1234567777'

console.log(compare(s1, s2))
function* walk(str) {
  let part = ''
  for (let i = 0; i < str.length; i++) {
    part += str[i]
    yield part
  }
}

function compare(s1, s2) {
  let genS1 = walk(s1)
  let genS2 = walk(s2)
  while (true) {
    const n1 = genS1.next()
    const n2 = genS2.next()
    if (n1.value !== n2.value) {
      return false
    }

    if (n1.done && n2.done) {
      return true
    }
  }
}

const s1 = '1234567777'
const s2 = '1234567777'

console.log(compare(s1, s2))