深入生成器函数的底层原理
分析执行上下文
生成器函数本质上还是一个_函数_,所以它的执行离不开 执行上下文
function* generator() {console.log("status1");yield "hello";console.log("status2");yield "world";}
let gen = generator();
let one = gen.next();
1. 我们试着分析在执行gen = generator()
这句代码前的执行上下文状态:
此时执行上下文栈(后面统称ECS) 中的_栈顶_是 全局执行上下文
该上下文中, 全局对象
(window) 上保存了 generator
函数对象。
gen , one 是 let
声明所以在 词法环境中_未进行初始化_
2. 当执行 gen = generator()
时 , 创建 generator
函数的执行上下文:
此时,ECS的_栈顶_是 生成器函数 generator
的执行上下文:
从这个时刻开始,就体现出 function *
与 function
的_区别_:
当 generator
的执行上下文生成好以后,
_不会_和常规的函数一样,开始执行代码。
而是会_创建_一个 指向 generator
执行上下文的 迭代器
并_返回该对象_
注意:
- 这里并_没有执行_生成器函数内部的代码
- 生成好执行上下文后,创建了一个指向该上下文环境的对象(被称作_迭代器_)然后返回
和正常的函数一样,生成器函数的上下文被弹出栈顶 (这也是不堵塞挂起的原因)
但由于上下文环境生成的对象_没有失去引用_,因此被保留了下来(和_闭包_一个原理)
执行next 方法
执行 next 方法时,也很_特殊_:
- 不会_创建 next 方法的上下文,而是将迭代器中保存的_生成器函数的上下文_重新激活_压入栈顶,并从上次挂起的
yeild
位置开始_继续_执行代码。* 从这里可以看出,这就是 生成器函数_不会堵塞_执行的原因,因为_只有执行next
函数时_,生成器函数的上下文才会被压入栈顶。每次执行到yeild
关键字就让next
返回结果,并_弹出栈顶挂起_,_等待_下次激活。> 你是否还记得 next() 方法的参数?
我们可以在执行 next 方法时传入参数。
该参数很奇怪,会作为_上一次yeild语句的返回值_。
刚开始我会觉得这种设计很奇怪,但是现在看来这是一种很_正常_的逻辑:
我们执行 next 时,将生成器函数从_挂起_状态_恢复_到执行状态。
next方法的_参数_,为这次的_恢复执行_提供了一个_信息_,该信息保存在 上次挂起(这次恢复) 的语句,也就是作为上次yeild语句的返回值
我们通过yeild语句从生成器中_得到_返回值,再使用迭代器的 next 方法把值_传回_生成器,实现了双向通信。
普通函数每次调用都会_创建_一个新的执行上下文,而生成器函数_不同_,自始至终都是使用_一个上下文_,而这个上下文对象由于_被迭代器引用_,所以_不会_被当作垃圾从内存中回收掉。
普通函数的执行上下文只有调用时才新创建,相比之下,生成器函数的上下文会_暂时挂起并在将来恢复_。
总结:
- 执行生成器函数后,不执行其中的代码,而是_返回一个保存了其上下文对象的迭代器。* 每次调用迭代器的
next
方法,不会_创建next
方法的上下文,而是_将迭代器中保存的生成器的上下文重新激活压入栈顶。* next 方法的参数作为_上次挂起的语句的返回值。
最后
最近找到一个VUE的文档,它将VUE的各个知识点进行了总结,整理成了《Vue 开发必须知道的36个技巧》。内容比较详实,对各个知识点的讲解也十分到位。
有需要的小伙伴,可以点击下方卡片领取,无偿分享