JavaScript 生成器核心:深入理解 yield
在 JavaScript 中,yield 关键字是生成器(Generator)最核心的特性。它允许函数在执行过程中暂停,并在后续恢复,同时保留当前的作用域状态。这不仅仅是简单的返回,而是对执行流的精细控制。
暂停与恢复机制
生成器函数在遇到 yield 之前会正常执行。一旦碰到这个关键字,执行立即停止,但函数的局部变量和调用栈上下文会被完整保留。要恢复执行,必须通过生成器对象调用 next() 方法。
function* generatorFn() {
yield;
}
let generatorObject = generatorFn();
console.log(generatorObject.next()); // { done: false, value: undefined }
console.log(generatorObject.next()); // { done: true, value: undefined }
此时 yield 有点像函数的中间返回语句,但它不会终止函数。生成的值会出现在 next() 返回的对象里。如果通过 yield 退出当前步骤,生成器处于 done: false 状态;只有当函数自然结束或通过 return 关键字显式返回时,才会进入 done: true 状态。
返回值与完成状态
我们可以对比一下 yield 和 return 在生成器中的表现。yield 产出值并挂起,而 return 直接结束函数。
function* generatorFn() {
yield 'foo';
yield 'bar';
return 'baz';
}
let generatorObject = generatorFn();
console.log(generatorObject.next()); // { done: false, value: 'foo' }
console.log(generatorObject.next()); // { done: false, value: 'bar' }
console.log(generatorObject.next()); // { done: true, value: 'baz' }
注意最后一步,return 'baz' 不仅产出了值,还标记了生成器的完成状态。
实例隔离性
生成器函数内部的执行流程是针对每个生成器对象区分作用域的。这意味着同一个函数创建出的多个实例互不干扰。
function* generatorFn() {
yield 'foo';
yield 'bar';
return 'baz';
}
let generatorObject1 = generatorFn();
let generatorObject2 = generatorFn();
console.log(generatorObject1.next()); // { done: false, value: 'foo' }
console.log(generatorObject2.next()); // { done: false, value: 'foo' }
console.log(generatorObject2.next()); // { done: false, value: 'bar' }
console.log(generatorObject1.next()); // { done: false, value: 'bar' }
即使两个对象来自同一个函数定义,它们各自维护独立的执行指针和状态。
使用范围限制
yield 关键字只能在生成器函数内部直接使用。如果在普通函数、箭头函数或嵌套的非生成器函数中使用,会抛出语法错误。
// 有效
function* validGeneratorFn() {
yield;
}
// 无效:yield 不能用于普通函数
function* invalidGeneratorFnA() {
function a() {
yield;
}
}
// 无效:yield 不能用于箭头函数
function* invalidGeneratorFnB() {
const b = () => {
yield;
};
}
// 无效:yield 不能用于立即执行的箭头函数
function* invalidGeneratorFnC() {
(() => {
yield;
})();
}
在实际开发中,如果你发现代码报出类似 "Unexpected token" 的错误,且涉及 yield,请检查它是否被包裹在了非生成器函数内部。这是初学者最容易踩的坑之一。
掌握 yield 的执行控制逻辑,是理解异步编程、迭代协议以及更高级并发模型的基础。
