From 7fb0fdb0fcc2c7bec71df3a8dfc0562cea29d81d Mon Sep 17 00:00:00 2001 From: qianguyihao Date: Sun, 28 May 2023 20:30:46 +0800 Subject: [PATCH] =?UTF-8?q?add:=20=E7=94=9F=E6=88=90=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 07-JavaScript进阶/03-迭代器和生成器.md | 192 ++++++++++++++++++++++++- 1 file changed, 190 insertions(+), 2 deletions(-) diff --git a/07-JavaScript进阶/03-迭代器和生成器.md b/07-JavaScript进阶/03-迭代器和生成器.md index 9342838..a3ef54b 100644 --- a/07-JavaScript进阶/03-迭代器和生成器.md +++ b/07-JavaScript进阶/03-迭代器和生成器.md @@ -495,7 +495,7 @@ const myObj2 = { return { done: true }; } }, - // 【重要】监听迭代器的中断 + // 【关键代码】监听迭代器的中断 return: () => { console.log('迭代器被中断了'); return { done: true }; @@ -510,7 +510,7 @@ for (const item of myObj2) { const [key, value] = item; console.log(key, value); if (value == 'qianguyihao') { - // 【重要】如果发现 value 为 qianguyihao,则中断迭代器,停止继续遍历 + // 【关键代码】如果发现 value 为 qianguyihao,则中断迭代器,停止继续遍历 break; } } @@ -525,6 +525,194 @@ name qianguyihao 根据打印结果可以看出,迭代器只遍历了 myObj2 对象的第一个元素,符合指定条件后,通过 break 语句中断了迭代器,停止了继续遍历;与此同时,迭代器中的 return() 函数监听到了迭代器的中断。对了,return() 函数中,还需要写 `return { done: true }`表示迭代器的使命已结束;如果不写这行则会报错:`Uncaught TypeError: Iterator result undefined is not an object`。 +## 生成器 + +### 概念 + +我们平时写的函数,基本是通过 return 返回值,或者发生异常,函数才会终止执行。这还不够灵活。 + +**生成器**是 ES6 中新增的一种特殊的函数,所以也称为“生成器函数”。它可以更灵活地控制函数什么时候执行, 什么时候暂停等等。 + +生成器函数使用 `function*` 语法编写。最初调用时,生成器函数不执行任何代码,而是返回一个称为 Generator 的迭代器。通过调用生成器的 next() 方法时,Generator 函数将执行,直到遇到 yield 关键字时暂停执行。 + +可以根据需要多次调用该函数,并且每次都返回一个新的 Generator,但每个 Generator 只能迭代一次。 + +### 生成器函数和普通函数的区别 + +- 生成器函数需要在 `function` 关键字后面加一个符号 `*`。 +- 生成器函数可以通过 `yield` 关键字控制函数的执行流程。 +- 生成器函数的返回值是一个生成器(Generator)。生成器是一种特殊的迭代器。 + +## 生成器函数拆解 + +### 定义一个生成器函数,以及生成器函数的执行时机 + +如果要定义一个生成器函数,我们需要在`function`单词的后面追加一个`*`符号。代码举例: + +```js +function* foo() { + console.log('1'); + console.log('2'); + console.log('3'); +} + +foo(); +``` + +上方代码中,`*`符号既可以写在 `function`单词的后面,即`function* foo()`;也可以写在`foo`单词的前面,即`function *foo()`。这两种写法都可以,第一种写法更常见。 + +但是上面的代码写完后,并不会有打印结果,因为**我们还需要调用生成器的 next()方法,生成器函数才会执行,直到遇到 yield 关键字后暂停执行**;**最后遇到 return关键字,或者遇到函数末尾时,结束执行**。代码举例: + +```js +// 通过 * 符号,定义一个生成器函数 +function* foo() { + console.log('1'); + yield; + + console.log('2'); + // 下面这行 console.log('a') 会跟 yield 一起执行 + yield console.log('a'); + + console.log('3'); +} + +const generator = foo(); // 返回一个生成器对象 +// 调用生成器的 next()方法,生成器才会执行,直到遇到 yield 后暂停执行 +generator.next(); // 这行代码执行后,打印结果是:1 +generator.next(); // 这行代码执行后,打印结果是:1 2 a +generator.next(); // 这行代码执行后,打印结果是:1 2 a 3 +``` + +仔细看注释,生成器 generator 每调用一次 next() ,foo()函数里的代码就往下执行一次,直到遇到 yield 后暂停。 + +### next() 方法的返回值 + +生成器既然是一种特殊的迭代器,那么它也有 next()方法,而且 next()方法里同样有 done 和 value 这两个属性。我们来看看这两个属性的**默认属性值**是什么: + +```js +// 通过 * 符号,定义一个生成器函数 +function* foo() { + console.log('阶段1'); + yield; + + console.log('阶段2'); + yield; + + console.log('阶段3'); + return; // 执行 return 之后,函数不再继续往下走了,生成器的 next()方法的 done 属性值为 true。 + + console.log('阶段4'); +} + +// 执行生成器函数,返回一个生成器对象 +const generator = foo(); +// 调用生成器的 next()方法,生成器才会执行,直到遇到 yield 后暂停执行;遇到 return关键字,或者遇到函数末尾时,结束执行。 +console.log(generator.next()); +console.log(generator.next()); +console.log(generator.next()); +``` + +打印结果: + +``` +阶段1 +{value: undefined, done: false} + +阶段2 +{value: undefined, done: false} + +阶段3 +{value: undefined, done: true} +``` + +上方代码的打印结果可以看出,生成器函数在遇到函数的末尾,或者遇到 return 之后,函数就不再继续往下走了,next()方法的 done 属性值为 true。 + +还可以看到,next()方法的 value 属性值默认为 undefined,**如果某些情况下我们希望 value属性有值**的话,可以通过 yield 关键字进行传递。代码举例: + +```js +// 通过 * 符号,定义一个生成器函数 +function* foo() { + console.log('阶段1'); + yield 'a'; // 【关键代码】yield 后面写的内容,就是传递给 next() 方法的 value 属性值 + + console.log('阶段2'); + yield 'b'; + + console.log('阶段3'); + return; // 这里的 return,相当于 return undefined + + console.log('阶段4'); +} + +// 执行生成器函数,返回一个生成器对象 +const generator = foo(); +// 调用生成器的 next()方法,生成器才会执行,直到遇到 yield 后暂停执行;遇到 return关键字,或者遇到函数末尾时,结束执行。 +console.log(generator.next()); // 打印生成器对象的 next()方法 +console.log(generator.next()); +console.log(generator.next()); +``` + +打印结果: + +``` +阶段1 +{value: 'a', done: false} + +阶段2 +{value: 'b', done: false} + +阶段3 +{value: undefined, done: true} +``` + +### next() 方法的参数 + +根据前面的代码实例得知,生成器函数是分多个阶段执行的。此时有一个诉求:如何给当前阶段传递参数呢? + +答案是:可以通过当前阶段 next() 方法的参数,给当前阶段传值。这个参数值会成为**上一个阶段** yield 语句的返回值。 + +代码举例: + +```js +// 通过 * 符号,定义一个生成器函数 +function* foo() { + console.log('阶段1'); + // 【关键代码】第二次调用 next()方法时,通过 res2 接收 next()方法的参数值 + const res2 = yield 'a'; + + console.log('阶段2:', res2); + // 第三次调用 next()方法时,通过 res3 接收 next()方法的参数值 + const res3 = yield 'b'; + + console.log('阶段3:', res3); + return; +} + +// 执行生成器函数,返回一个生成器对象 +const generator = foo(); +// 调用生成器的 next()方法,生成器才会执行,直到遇到 yield 后暂停执行;遇到 return关键字,或者遇到函数末尾时,结束执行。 +console.log(generator.next()); // 执行第一阶段 +console.log(generator.next('next2')); // 执行第二阶段,并传参 +console.log(generator.next('next3')); // 指定第三阶段 +``` + +打印结果: + +``` +阶段1 +{value: 'a', done: false} + +阶段2: next2 +{value: 'b', done: false} + +阶段3: ntext3 +{value: undefined, done: true} +``` + +在理解上方代码时需要注意的是,将 next2 这个属性值赋值给 res2,这个操作的执行时机是在**第二阶段的最开始**做的,不是在第一阶段的末尾做的。并且,这个属性值是通过第一阶段的 yield 返回值接收的。 + + +