Generator“跳转”本质是状态机驱动的控制流切换与闭包捕获的词法环境协同作用:用整数枚举建模生命周期状态(如STATE_SUSPENDED_AT_3),每次next()依状态执行对应闭包代码块并更新状态;闭包自动冻结局部变量、this、new.target等上下文,确保yield求值时机、迭代器协议等细节精准复刻。

手写 Generator 编译器模拟器时,“跳转”本质不是指令跳转,而是控制流在暂停/恢复点之间的状态切换。所谓“完美复刻运行底座”,核心在于用状态机刻画执行阶段,用闭包捕获局部变量与执行上下文——二者结合,才能准确模拟 next() 调用时的断点续行行为。
状态机:显式建模 Generator 的生命周期阶段
Generator 函数执行过程可划分为明确的有限状态:初始(created)、运行中(executing)、暂停(suspended)、完成(completed)、出错(errored)。每个 yield 表达式都是一个潜在的暂停点,对应一个状态转移边。
- 状态不能靠
if-else堆砌,应统一用整数或符号枚举(如STATE_SUSPENDED_AT_3),每个数字绑定到具体yield语句的位置 - 每次调用
next(),先读当前状态,再执行该状态下对应的代码块(即“从哪继续”),最后更新状态(如遇到下一个yield就切到新暂停态) - 状态转移必须单向且确定:例如从
STATE_RUNNING→STATE_SUSPENDED_AT_5后,不能再倒回第 3 行;这和真实 V8 的字节码跳转表逻辑一致
闭包:冻结每帧的词法环境与控制上下文
Generator 每次暂停,必须完整保留当前函数帧的所有局部变量、参数、临时计算值——这不是靠堆栈复制,而是靠 JavaScript 闭包天然持有的词法环境(LexicalEnvironment)。
- 不要在状态机外维护全局变量池;每个状态处理函数都应是一个闭包,由外层“编译器生成函数”创建,自动捕获其作用域内所有活跃变量
- 例如:
function* foo(x) { let a = 1; yield a + x; return a * 2; }编译后,suspend_at_yield状态函数体内能直接访问x和a,无需手动传参或存取 context 对象 - 若需支持嵌套 Generator 或
try/catch,闭包还须捕获 try-block 的异常处理边界信息(可用标记位 + 错误重抛机制模拟)
跳转的本质:状态 + 闭包 + 控制权移交
所谓“跳转”,是三者协同的结果:状态决定执行哪一段闭包,闭包提供数据,而 next() 调用本身完成控制权从用户代码到 Generator 内部逻辑的移交。
- 首次
next():进入初始闭包,执行至第一个yield,保存状态,返回{value, done: false} - 后续
next(value):将value注入上一个yield表达式的求值结果(即赋给yield左侧的变量),然后按当前状态跳入对应闭包继续执行 - 遇到
return或抛错:更新状态为completed或errored,此后所有next()都返回{done: true}或抛出缓存错误
关键细节:避免常见失真
很多模拟器跑不通真实 Generator,往往栽在这些细节:
-
yield 表达式求值时机:真实引擎中,
yield expr是先求expr,再暂停;模拟器若把expr放在状态切换后求值,就会错乱 -
this 绑定与 new.target:Generator 函数被调用时的
this和new.target必须在首次闭包创建时捕获并冻结,不可每次next()重新推导 -
迭代器协议合规性:返回对象必须有
next、return、throw方法,且return()必须立即进入completed状态并清理资源

















