【细读JS忍者秘籍】深入生成器函数的底层原理


theme: channing-cyan

深入生成器函数的底层原理

分析执行上下文

生成器函数本质上还是一个函数,所以它的执行离不开 执行上下文

function* generator() {
    console.log("status1");
    yield "hello";
    console.log("status2");
    yield "world";
  }

let gen = generator();
let one = gen.next();

1. 我们试着分析在执行gen = generator() 这句代码前的执行上下文状态:

image.png

此时执行上下文栈(后面统称ECS) 中的栈顶全局执行上下文

该上下文中, 全局对象(window) 上保存了 generator 函数对象。

gen , one 是 let 声明所以在 词法环境中未进行初始化

2. 当执行 gen = generator() 时 , 创建 generator 函数的执行上下文:

image.png

此时,ECS的栈顶生成器函数 generator 的执行上下文

从这个时刻开始,就体现出 function *function区别

generator 的执行上下文生成好以后,

不会和常规的函数一样,开始执行代码。

而是会创建一个 指向 generator 执行上下文的 迭代器返回该对象

注意:

  • 这里并没有执行生成器函数内部的代码
  • 生成好执行上下文后,创建了一个指向该上下文环境的对象(被称作迭代器)然后返回

和正常的函数一样,生成器函数的上下文被弹出栈顶 (这也是不堵塞挂起的原因)

但由于上下文环境生成的对象没有失去引用,因此被保留了下来(和闭包一个原理)

image.png

执行next 方法

执行 next 方法时,也很特殊

  • 不会创建 next 方法的上下文,而是将迭代器中保存的生成器函数的上下文重新激活压入栈顶,并从上次挂起的 yeild 位置开始继续执行代码。

  • 从这里可以看出,这就是 生成器函数不会堵塞执行的原因,因为只有执行 next 函数时,生成器函数的上下文才会被压入栈顶。每次执行到 yeild 关键字就让next返回结果,并弹出栈顶挂起等待下次激活。

你是否还记得 next() 方法的参数?

我们可以在执行 next 方法时传入参数。

该参数很奇怪,会作为上一次yeild语句的返回值

刚开始我会觉得这种设计很奇怪,但是现在看来这是一种很正常的逻辑:

我们执行 next 时,将生成器函数从挂起状态恢复到执行状态。

next方法的参数,为这次的恢复执行提供了一个信息,该信息保存在 上次挂起(这次恢复) 的语句,也就是作为上次yeild语句的返回值

我们通过yeild语句从生成器中得到返回值,再使用迭代器的 next 方法把值传回生成器,实现了双向通信。

普通函数每次调用都会创建一个新的执行上下文,而生成器函数不同,自始至终都是使用一个上下文,而这个上下文对象由于被迭代器引用,所以不会被当作垃圾从内存中回收掉。

普通函数的执行上下文只有调用时才新创建,相比之下,生成器函数的上下文会暂时挂起并在将来恢复

总结:

  • 执行生成器函数后,不执行其中的代码,而是返回一个保存了其上下文对象的迭代器。

  • 每次调用迭代器的 next 方法,不会创建 next 方法的上下文,而是将迭代器中保存的生成器的上下文重新激活压入栈顶。

  • next 方法的参数作为上次挂起的语句的返回值。

© 版权声明
THE END
喜欢就支持一下吧
点赞12 分享
相关推荐
  • 暂无相关文章
  • 评论 抢沙发
    头像
    欢迎您留下宝贵的见解!
    提交
    头像

    昵称

    取消
    昵称表情代码图片

      暂无评论内容