JS:生成器函数和迭代器以前的关系

生成器函数

生成器函数就是在普通函数的 function 关键字后面加个星号(*javascript

function* generator() {
    yield 1
    yield 2
    yield 3
}
复制代码

调用生成器函数,返回的就是一个迭代器(iterator)。html

const iterator = generator()

iterator.next() // {value: 1, done: false}
iterator.next() // {value: 2, done: false}
iterator.next() // {value: 3, done: false}
iterator.next() // {value: undefined, done: true}
复制代码

迭代器就是部署了 next 方法一个对象,每次调用就会返回一个包含 valuedone 属性的对象:value 表示当前遍历值,done 则表示遍历时候结束。java

for-of 循环

for..of 循环是用来遍历可迭代对象(iterable)的。语法以下:数组

for (variable of iterable) {
    // statements
}
复制代码

所谓的可迭代对象就是部署了 [Symbol.iteraor] 属性的对象,这个属性是个方法。调用这个方法就能获得迭代器,所以 [Symbol.iteraor] 还被称为“迭代器生成函数”。ide

const arr = ['a', 'b', 'c']

// (1) 使用 for-of 循环
for (const element of arr) {
  console.log(element)
}

// (2) 手动循环
let iterator = arr[Symbol.iterator]()
while (true) {
  let result = iterator.next()
  if (result.done) break
  console.log(result.value)
}
复制代码

(1) 处的 for-of 循环,内部的执行逻辑如 (2) 处代码反映的这样。最终的输出结果为 a -> b -> c函数

for-of 与生成器函数

你可能不知道的是:调用生成器函数的结果也能被 for-of 循环遍历。ui

仍是以上面的 generator() 为例:this

for (const element of generator()) {
  console.log(element)
}

// 1 -> 2 -> 3
复制代码

果真能够。但我不知道看后的你们有没有疑问,我却是有。spa

generator() 的结果不是个迭代器吗,为何也能被 for-of 调用?莫非 for-of 不只对可迭代对象,也能直接对迭代器遍历?code

咱们来试试。

for (const element of arr[Symbol.iterator]()) {
  console.log(element)
}
// a -> b -> c
复制代码

arr[Symbol.iterator]() 返回的是个迭代器,看到确实能够被 for-of 遍历。那么就能说明 for-of 也能对迭代器作遍历吗?

for-of 只能遍历可迭代对象

咱们来看下,arr[Symbol.iterator]() 的结构吧。

image.png

  • 支持 next 方法说明是个迭代器对象。
  • 同时原型链上继承了一个 [Symbol.iterator] 属性……啊,原来仍是个可迭代对象啊。

数组的 arr[Symbol.iterator]() 的返回对象:既是迭代器,也是可迭代对象。这就有点问题了,由于这里起做用的可能仍是 [Symbol.iterator] 属性(按 for-of 的语法来讲,是这样的)。

为了充分证实,我们再用普通对象改装成的可迭代对象,证实一下:

let range = {}
range[Symbol.iterator] = function() {
  return {
    current: 1,
    last: 3,
    
    next() {
      if (this.current <= this.last) {
        return { done: false, value: this.current++ }
      } else {
        return { done: true }
      }
    }
  }
}
复制代码

咱们先用 for-of 遍历一下:

for (let item of range) { console.log(item) } // 1 -> 2 -> 3
复制代码

OK,没问题。下面进入关键一步了,对 range[Symbol.iterator]() 的结果遍历。

for (let item of range[Symbol.iterator]()) { console.log(item) }
// TypeError: range[Symbol.iterator] is not a function or its return value is not iterable
复制代码

报错啦!range[Symbol.iterator] 返回的要是个可迭代对象才行

便是说 arr[Symbol.iterator]() 之因此可以遍历,不是由于它是个迭代器,而是由于它是个可迭代对象。

若是再进一步,咱们还能发现一个有趣的地方:

let iterator = arr[Symbol.iterator]()

iterator[Symbol.iterator]() === iterator // true
复制代码

arr[Symbol.iterator]() 是个可迭代对象,咱们对它调用 [Symbol.iterator],发现结果与自身相等!这也是为何前面会给咱们形成 for-of 也能遍历迭代器假象的缘由。

生成器函数返回值仍是个可迭代对象

再回到前面生成器函数的例子:

function* generator() {
    yield 1
    yield 2
    yield 3
}

for (const element of generator()) {
  console.log(element)
}
// 1 -> 2 -> 3
复制代码

generator() 的返回结果是个迭代器对象,但这个对象能被 for-of 循环,绝对是由于它同时仍是个可迭代对象!

咱们验证下:

image.png

果真是的。一样的:

let iterator = generator()

iterator[Symbol.iterator]() === iterator // true
复制代码

啊哈,同为可迭代对象的迭代器,调用 [Symbol.iterator] 后,结果与自身相等(再摸一遍脑门)——这就是为何,前面咱们会有 for-of 也能遍历迭代器幻象的缘由!

使用生成器函数建立可迭代对象

有一个有趣的用例,咱们改下下上面的 range 对象:

const range = {
    *[Symbol.iterator]() {
        yield 1;
        yield 2;
        yield 3;
    }
}

for (let value of range) { 
    console.log(value)
}
// 1 -> 2 -> 3
复制代码

这里使用的是“生成器函数的返回值是迭代器”这一特色。

总结

  • 调用生成器函数返回的结果,是个迭代器对象(具备 next 方法)。同时,
  • 这个迭代器仍是个可迭代对象。这就是为何
  • generator() 的结果能够被 for-of 遍历的缘由!

参考连接

(正文完)


广告时间(长期有效)

我有一位好朋友开了一间猫舍,在此帮她宣传一下。如今猫舍里养的都是布偶猫。若是你也是个爱猫人士而且有须要的话,不妨扫一扫她的【闲鱼】二维码。不买也没关系,看看也行。

(完)

相关文章
相关标签/搜索