前几天学习 for…of 和 iterator 对象的时候接触到了 generator 函数,generator函数返回的是一个 iterator 对象,而 通过[Symbol.iterator] 接口返回的 iterator 对象,就是 for…of 遍历时的规则,所以,今天来学习一下这个 generator 函数

复习:iterator对象
迭代器对象,结构为:
1 | { |
如果想要让某个对象能够使用 for...of 进行遍历
1 | var obj = { |
generator 函数
generator 函数返回的就是一个 iterator 对象,也就是类似于
1 | { |
的一个对象
基本语法
1 | function *gen() { |
在函数 function 之后使用 * 声明这是一个 generator 函数,其实就是一种可以暂停执行的函数,函数内部的 yield 就是暂停点,每次都等到 yield 后面的部分返回值之后,函数才会继续向下执行
Generator 函数返回的就是一个 遍历器对象,它的暂停执行,也是通过 遍历对象的next() 实现的,遍历器对象的next方法的运行逻辑如下。
(1)遇到yield表达式,就暂停执行后面的操作,并将紧跟在yield后面的那个表达式的值,作为返回的对象的value属性值。
(2)下一次调用next方法时,再继续往下执行,直到遇到下一个yield表达式。
(3)如果没有再遇到新的yield表达式,就一直运行到函数结束,直到return语句为止,并将return语句后面的表达式的值,作为返回的对象的value属性值。
(4)如果该函数没有return语句,则返回的对象的value属性值为undefined。
yield 后面的代码,只有在调用 next ,执行到这一行的时候,才会开始执行(手动的惰性求值)
yield*
指后面跟着的也是一个 generator 函数的执行结果(即一个 iterator 对象),这一步的执行相当于 把后面的generator函数平铺开来,对其进行遍历
1 | function* concat(iter1, iter2) { |
next/return/throw
next()、throw()、return()这三个方法本质上是同一件事,可以放在一起理解。它们的作用都是让 Generator 函数恢复执行,并且使用不同的语句替换yield表达式。
next()是将yield表达式替换成一个值。
1 | const g = function* (x, y) { |
上面代码中,第二个next(1)方法就相当于将yield表达式替换成一个值1。如果next方法没有参数,就相当于替换成undefined。
throw()是将yield表达式替换成一个throw语句。
1 | gen.throw(new Error('出错了')); // Uncaught Error: 出错了 |
return()是将yield表达式替换成一个return语句。
1 | gen.return(2); // Object {value: 2, done: true} |
generator 函数的返回值
generator 函数的返回值是一个 指针对象(也就是一个 iterator 对象)
generator 中的this
普通 generator 函数中的 this 是不生效的,即使使用 new 来创建,generator 返回的也是 iterator 对象,每次调用 iter.next() 时,generator函数中的this并不是指向 iter,而是指向其上级作用域链中的 this 对象
1 | var obj = { |
上面代码中,虽然执行 next 的对象是 iter,但是执行时其中的 this 指向的是 obj,也就是其上级作用域链中的 this
而且不能执行 new
1 | function* F() { |
generator 异步
1 | var fetch = require('node-fetch'); |
上面代码中,首先执行 Generator 函数,获取遍历器对象,然后使用next方法(第二行),执行异步任务的第一阶段。由于Fetch模块返回的是一个 Promise 对象,因此要用then方法调用下一个next方法。
那么,如何让函数自执行,也就是自己在上一次yield返回数据之后,调用 next 从而进行接下来代码的执行呢
既然 generator 函数的初衷是为了把执行权暂时出让,这里又为什么要探究自执行的机制呢?
在这里,generator 函数更大的作用是为了在 generator 函数内部,可以用 同步的方式来写异步的代码。
因为generator 函数能够出让执行权的时机都是每次 yield 执行结束,返回数据的时间点,而在yield 后面的函数执行过程中是没有办法被打断的,我们让 generator 函数自执行,那么在这个 generator 函数内部,我们可以保证,每次 yield 后的函数不会被打断,而且在这个函数执行的过程中这个generator函数会处于等待状态,而且 yield 一旦返回数据,函数就可以立即执行接下来的同步代码
协程的概念,在异步的时候把执行权交出给这个异步函数,异步函数一旦执行完毕,就把执行权收回来继续执行同步代码
自执行函数的实现——thunk 函数
什么是 thunk 函数
如果某个函数调用中包括一个回调函数,也就是说,在处理完这个函数的主流程之后,将调用这个回调函数进行一些本函数之外数据或状态的变更,那么这样的函数可以被包装成一个 thunk 函数
1 | function thunk(fn) { |
call 和 apply 的区别,apply 的调用参数为数组,而 call 则直接传入即可
thunk 函数的作用,就是延迟 callback 的传入时机
generator 与 thunk 函数
那么,generator 函数又与 thunk 函数有什么关系呢?——使用 Thunk 函数,可以自执行 generator 函数
1 | function run(g) { |
调用:
1 | var g = function* (){ |
自执行函数的实现——Promise
使用Promise 实现,则需要 generator 函数中每个yield 后面跟着的都必须是一个 Promise 对象
1 | function run(g) { |