各位观众,各位来宾,掌声欢迎!咳咳,大家好,我是今天的主讲人,江湖人称“代码老油条”。今天咱们聊聊 JavaScript 里一个挺有意思的家伙—— yield
。别看它名字挺玄乎,其实用起来简单得很,学会了能让你的代码更优雅、更灵活,还能装个小小的逼,何乐而不为呢?
开场白:Generator 函数是个啥?
在深入 yield
之前,咱们先得搞清楚它的老巢—— Generator 函数。简单来说,Generator 函数就是个“能暂停和恢复执行”的函数。普通的函数,一旦开始执行,就得一口气跑到结束。但 Generator 函数不一样,它可以在执行过程中“喘口气”,把控制权交出去,等需要的时候再回来接着执行。
定义 Generator 函数,只需要在 function
关键字后面加个 *
就行了。
function* myGenerator() {
console.log("Generator 函数开始执行");
yield 1;
console.log("第一次 yield 之后");
yield 2;
console.log("第二次 yield 之后");
yield 3;
console.log("Generator 函数执行完毕");
}
const generatorObject = myGenerator();
console.log(generatorObject.next()); // 输出: Generator 函数开始执行 { value: 1, done: false }
console.log(generatorObject.next()); // 输出: 第一次 yield 之后 { value: 2, done: false }
console.log(generatorObject.next()); // 输出: 第二次 yield 之后 { value: 3, done: false }
console.log(generatorObject.next()); // 输出: Generator 函数执行完毕 { value: undefined, done: true }
看到没?myGenerator()
并没有立即执行里面的代码,而是返回了一个 Generator 对象。我们需要通过调用 next()
方法来一步一步地执行它。
yield
:暂停的魔法棒
现在主角登场了!yield
关键字的作用就是让 Generator 函数暂停执行,并且可以“吐”出一个值。这个值会作为 next()
方法返回对象的 value
属性。当 Generator 函数执行到 yield
语句时,它会:
- 暂停执行。
- 把
yield
后面表达式的值作为value
属性返回。 - 等待下一次
next()
调用。
直到我们再次调用 next()
方法,Generator 函数才会从上次 yield
的地方继续执行。当 Generator 函数执行完毕或者遇到 return
语句时,next()
方法返回对象的 done
属性会变成 true
。
next()
方法:唤醒沉睡的 Generator
next()
方法是控制 Generator 函数执行的遥控器。每次调用 next()
,Generator 函数就会从上次暂停的地方恢复执行,直到遇到下一个 yield
或者执行完毕。
next()
方法还可以接受一个参数,这个参数会作为上一个 yield
表达式的返回值。这个特性非常有用,可以实现 Generator 函数和调用者之间的双向通信。
function* myGenerator() {
const value = yield "第一次提问:你叫什么名字?";
console.log("你回答说:", value);
yield "第二次提问:你多大了?";
}
const generatorObject = myGenerator();
console.log(generatorObject.next()); // 输出: { value: '第一次提问:你叫什么名字?', done: false }
console.log(generatorObject.next("张三")); // 输出: 你回答说: 张三 { value: '第二次提问:你多大了?', done: false }
console.log(generatorObject.next()); // 输出: { value: undefined, done: true }
在这个例子中,next("张三")
将字符串 "张三" 传递给了上一个 yield
表达式,并赋值给变量 value
。
*`yield`:委派你的 Generator**
yield*
关键字可以将一个 Generator 函数的执行权委派给另一个 Generator 函数。这有点像函数调用,但区别在于 yield*
会把被委派的 Generator 函数的 yield
值依次返回,直到被委派的 Generator 函数执行完毕。
function* generator1() {
yield 1;
yield 2;
}
function* generator2() {
yield* generator1();
yield 3;
yield 4;
}
const generatorObject = generator2();
console.log(generatorObject.next()); // 输出: { value: 1, done: false }
console.log(generatorObject.next()); // 输出: { value: 2, done: false }
console.log(generatorObject.next()); // 输出: { value: 3, done: false }
console.log(generatorObject.next()); // 输出: { value: 4, done: false }
console.log(generatorObject.next()); // 输出: { value: undefined, done: true }
yield* generator1()
会把 generator1()
的 yield
值 1 和 2 依次返回,然后才会继续执行 generator2()
剩下的部分。
实际应用场景:Generator 的十八般武艺
Generator 函数和 yield
关键字的应用场景非常广泛,下面列举几个常用的:
-
异步编程:告别回调地狱
Generator 函数可以和 Promise 结合,优雅地处理异步操作,避免回调地狱。
function getData(url) { return new Promise((resolve, reject) => { setTimeout(() => { const data = `从 ${url} 获取的数据`; resolve(data); }, 1000); }); } function* fetchData() { try { const data1 = yield getData("url1"); console.log("第一个数据:", data1); const data2 = yield getData("url2"); console.log("第二个数据:", data2); const data3 = yield getData("url3"); console.log("第三个数据:", data3); } catch (error) { console.error("发生错误:", error); } } function run(generator) { const iterator = generator(); function handleNext(result) { if (result.done) { return; } const promise = result.value; promise.then( (data) => { handleNext(iterator.next(data)); }, (error) => { handleNext(iterator.throw(error)); } ); } handleNext(iterator.next()); } run(fetchData);
在这个例子中,
fetchData()
函数使用yield
暂停执行,等待getData()
返回的 Promise 对象 resolve。run()
函数负责驱动 Generator 函数的执行,并处理 Promise 的 resolve 和 reject。 -
迭代器:自定义你的数据遍历方式
Generator 函数可以轻松地创建迭代器,自定义数据的遍历方式。
function* myIterator(array) { for (let i = 0; i < array.length; i++) { yield array[i]; } } const myArray = [1, 2, 3, 4, 5]; const iterator = myIterator(myArray); console.log(iterator.next()); // 输出: { value: 1, done: false } console.log(iterator.next()); // 输出: { value: 2, done: false } console.log(iterator.next()); // 输出: { value: 3, done: false } console.log(iterator.next()); // 输出: { value: 4, done: false } console.log(iterator.next()); // 输出: { value: 5, done: false } console.log(iterator.next()); // 输出: { value: undefined, done: true } // 使用 for...of 循环遍历 for (const value of myIterator(myArray)) { console.log(value); // 输出: 1 2 3 4 5 }
myIterator()
函数创建了一个迭代器,可以依次返回数组中的每个元素。使用for...of
循环可以方便地遍历这个迭代器。 -
状态机:简化你的状态管理
Generator 函数可以用来实现状态机,简化状态管理。
function* trafficLight() { while (true) { yield "red"; yield "yellow"; yield "green"; } } const light = trafficLight(); console.log(light.next().value); // 输出: red console.log(light.next().value); // 输出: yellow console.log(light.next().value); // 输出: green console.log(light.next().value); // 输出: red
trafficLight()
函数模拟了一个交通灯的状态,使用yield
依次返回不同的状态。 -
协同程序:实现并发效果
Generator 函数可以用来实现协同程序,模拟并发效果。
function* task1() { console.log("Task 1 开始"); yield; console.log("Task 1 结束"); } function* task2() { console.log("Task 2 开始"); yield; console.log("Task 2 结束"); } const t1 = task1(); const t2 = task2(); t1.next(); // Task 1 开始 t2.next(); // Task 2 开始 t1.next(); // Task 1 结束 t2.next(); // Task 2 结束
在这个例子中,
task1()
和task2()
两个 Generator 函数交替执行,模拟了并发效果。
Generator 函数 vs. 普通函数:对比分析
为了更清晰地理解 Generator 函数的特点,我们将其与普通函数进行对比:
特性 | 普通函数 | Generator 函数 |
---|---|---|
定义方式 | function myFunction() {} |
function* myGenerator() {} |
执行方式 | 立即执行 | 返回 Generator 对象,需要调用 next() 方法执行 |
暂停与恢复 | 不支持 | 支持,使用 yield 关键字暂停,next() 方法恢复 |
返回值 | 使用 return 语句返回 |
使用 yield 关键字返回,next() 方法返回对象包含 value 和 done 属性 |
适用场景 | 同步操作,简单的逻辑处理 | 异步操作,迭代器,状态机,协同程序等 |
使用 Generator 函数的注意事项
- Generator 函数的执行需要依赖
next()
方法,需要仔细控制执行流程。 - Generator 函数可以和 Promise 结合,但需要注意错误处理。
- 过度使用 Generator 函数可能会导致代码可读性下降,需要权衡利弊。
总结:yield
的魅力
yield
关键字是 Generator 函数的核心,它赋予了函数暂停和恢复执行的能力,使得我们可以编写更灵活、更优雅的代码。虽然 Generator 函数的学习曲线稍微陡峭,但一旦掌握,你就能在异步编程、迭代器、状态机等领域大展身手。
希望今天的讲座能帮助大家更好地理解 yield
关键字和 Generator 函数。记住,编程的乐趣在于不断学习和探索,勇于尝试新的技术,才能成为真正的代码大师!
感谢大家的聆听,下课!