下面是一个 for 循环的例子,会在每次循环中输出当前的 index ,这段代码很也是简单的生成了 0-5 这些数字
for (let i = 0; i <= 5; i++) {
console.log(i);
}
// 输出 0 1 2 3 4 5
我们再来看看利用生成器函数是怎么实现的
function* generatorForLoop(num) {
for (let i = 0; i <= num; i ++) {
yield console.log(i);
}
}
const gen = generatorForLoop(5);
gen.next(); // 0
gen.next(); // 1
gen.next(); // 2
gen.next(); // 3
gen.next(); // 4
gen.next(); // 5
我们可以看到,只有调用 next 方法,才会向下执行,而不会一次产生所有值。这就是一个最简单的生成器了。在某些场景下,这种特性就成为了它的杀手锏
1. 函数声明
生成器的形式是一个函数,函数名称前面加一个星号 * 表示它是一个生成器。
// 函数声明
function * generator () {}
// 函数表达式
let generator = function *() {}
在定义一个生成器时,星号的位置在函数名前,但是位置没有明确的要求,不需要考虑挨着谁,都可以
只要是可以定义函数的地方,就可以定义生成器。
需要特别注意的是:箭头函数不能用来定义生成器
2. yield 表达式
函数体内部使用yield表达式,定义不同的内部状态,我们来看一段代码
function* helloWorld() {
yield ‘hello’;
yield ‘world’;
return ‘ending’;
}
在上面的代码中定义了一个生成器函数 helloWorld ,内部有两个 yield 表达式,三个状态:hello,world 和 return 语句
作为生成器的核心,单纯这么解释可能还是不能明白 yield 的作用以及它的使用方法
下面我们来展开说说 yield 关键字
首先它和 return 关键字有些许的类似,return 语句会在完成函数调用后返回值,但是在 return 语句之后无法进行任何操作

可以看到在编译器中第一个 return 语句之后的代码变灰了,说明了没有生效。但是yield的工作方式却不同,我们再来看看 yield 是如何工作的

注意:yield 关键字只能在生成器函数内部使用,其他地方使用会抛出错误
首先生成器函数会返回一个遍历器对象,只有通过调用 next 方法才会遍历下一个状态,而 yield 就是一个暂停的标志
在上面的代码中,首先声明了一个生成器函数,利用 myR 变量接收生成器函数的返回值,也就是上面所说的遍历器对象,此时遍历器对象处于暂停状态。
当调用 next 方法时,开始执行,遇到 yield 表达式,就暂停后面的操作,将 yield 后面的表达式的值,作为返回的对象的 value 值,因此第一个 myR.next() 中的 value 值为 8
再次调用 next 方法时,再继续向下执行,遇到 yield 再停止,后续操作一致
需要注意的是,yield 表达式后面的表达式,只有当调用next方法,内部指针指向该语句时才会执行
function* gen() {
yield 123 + 456;
}
就例如上面的代码中,yield后面的表达式 123 + 456 ,不会立即求值,只会在 next 方法将指针移到这一句时,才会求值。
因此可以理解为 return 是结束, yield 是停止
3. 一定需要 yield 语句吗?
其实在生成器函数中也可以没有yield表达式,但是生成器的特性还在,那么它就变成了一个单纯的暂缓执行函数,只有在调用该函数的遍历器对象的 next 方法才会执行
function* hello() {
console.log(‘现在执行’);
}
// 生成遍历器对象
let generator = hello()
setTimeout(() => {
// 开始执行
generator.next()
}, 2000)
4. 注意
yield 表达式如果用在另一个表达式中,必须放在圆括号里
console.log('Hello' + (yield 123)); // OK
- 1
yield 表达式用作函数参数可以不加括号
foo(yield 'a')
- 1
在阮一峰老师的 ES6 书籍上有着对生成器函数这样的理解
Generator函数有多种理解角度。语法上,首先可以把它理解成,Generator 函数是一个状态机,封装了多个内部状态。
书上说,Generator 函数是状态机,这是什么意思呢,状态机又怎么理解呢?
这个和 JavaScript 的状态模式有些许关联
状态模式:当一个对象的内部状态发生改变时,会导致其行为的改变,这看起来像是改变了对象
看到这些定义的时候,显然每个字都知道是什么意思,合起来却不知所云
先不要慌,我们先来看看状态模式是个什么东西,写个状态机就明白了
我们用一个洗衣机的例子,按一下电源键就打开,再按一下就关闭,我们先来实现这个
let switches = (function () {
let state = “off”;
return function () {
if (state === “off”) {
console.log(“打开洗衣机”);
state = “on”;
} else if (state === “on”) {
console.log(“关闭洗衣机”);
state = “off”;
}
}
})();
在上面的代码中,通过一个立即执行函数,返回一个函数,将状态 state 保存在函数内部,每次按下电源键调用 switches 函数即可。
这样看起来很完美,下面我们改变一下需求,洗衣机上有一个调整模式的按钮,每按一下换一个模式,假设有快速、洗涤、漂洗、拖水怎么实现
同样的我们还是可以采用 if-else 语句实现
let switches = (function () {
let state = “快速”;
return function () {
if (state === “快速”) {
console.log(“洗涤模式”);
state = “洗涤”;
} else if (state === “洗涤”) {
console.log(“漂洗模式”);
state = “漂洗”;
} else if (state === “漂洗”) {
console.log(“脱水模式”);
state = “脱水”;
} else if (state === “脱水”) {
console.log(“快速模式”);
state = “快速”;
}
}
})();
越来越复杂了,当模式再增多时,if-else 语句会越来越多,代码会难以阅读,你可能会说可以采用 switch-case 语句来实现,当然也可以,但是治标不治本。我们可不可以不采用判断语句实现呢。回到我们刚开始的定义
状态模式:当一个对象的内部状态发生改变时,会导致其行为的改变,这看起来像是改变了对象
咦,想想,洗衣机不正是需要实现状态改变,行为改变吗?那这正可以采用状态模式来实现呀,这里我们就直接引出我们的 generator 函数,通过控制状态来改变它的行为
利用原型来实现的方法太过于复杂和冗余了,就不展示了
const fast = function () {
console.log(“快速模式”);
}
const wash = function () {
console.log(“洗涤模式”);
}
const rinse = function () {
console.log(“漂洗模式”);
}
const dehydration = function () {
console.log(“脱水模式”);
}
function* models() {
let i = 0,
fn, len = arguments.length;
while (true) {
fn = arguments[i++]
yield fn()
if (i === len) {
i = 0;
}
}
}
const exe = models(fast, wash, rinse, dehydration); //按照模式顺序排放
在上面的代码中我们只需要在每次按下时调用 next 方法即可切换下一个状态

说了这么多 generator 为什么说是状态机呢?我的理解是:当调用 Generator 函数获取一个迭代器时,状态机处于初态。迭代器调用 next 方法后,向下一个状态跳转,然后执行该状态的代码。当遇到 return 或最后一个 yield 时,进入终态。同时采用 Generator 实现的状态机是最佳的结构。
生成器的另一强大之处在于内建消息输入输出能力,而这一能力仰仗于 yield 和 next 方法
yield 表达式本身没有返回值,或者说总是返回 undefined 。 next 方法可以带一个参数,该参数就会被当作上一个 yield 表达式的返回值。
从语义上讲,第一个 next 方法用来启动遍历器对象,所以不用带有参数。
来看一个例子
function* foo(x) {
let y = x * (yield)
return y
}
const it = foo(6)
it.next()
let res = it.next(7)
console.log(res.value) // 42
在上面的代码中,调用 foo 函数返回一个遍历器对象 it ,并将 6 作为参数传递给 x ,调用遍历器对象的 next 方法,启动遍历器对象,并且运行到第一个 yield 位置停止,
再次调用 next 方法传入参数 7 ,作为上一个 yield 表达式的返回值也就是 x 的乘项 (yield) 的值,运行到下一个 yield 或 return 结束
下面开始作死
在上面的例子中,如果不传递参数会这么样呢?
在第二次运行 next 方法的时候不带参数,导致了 y 的值等于 6 * undefined 也就是 NaN 所以返回的对象的 value 属性也是 NaN

我们再变一下
在原先的例子中,我们说第一个 next 是用来启动遍历器对象,那么如果传入参数会怎么样?
其实这样传递参数是无效的,因为我们说 next 方法的参数表示上一个 yield 表达式的返回值。
V8 引擎直接忽略第一次使用
next方法时的参数
在上一篇中我们知道,一个对象的 Symbol.iterator 方法,等于该对象的遍历器生成函数,调用该函数会返回一个遍历器对象
在这一篇我们知道,生成器函数就是遍历器生成函数,那么是不是有什么想法了呢?
我们可以把生成器赋值给对象的 Symbol.iterator 属性,实现 iterator 接口
let myIterable = {};
myIterable[Symbol.iterator] = function* () {
yield 1;
yield 2;
yield 3;
}
[…myIterable] // [1, 2, 3]
生成器函数返回的遍历器对象,都有 next 方法,以及可选的 return 方法和 throw 方法
我们先来看 return 方法
return
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。


既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:前端)
最后的最后
面试题千万不要死记,一定要自己理解,用自己的方式表达出来,在这里预祝各位成功拿下自己心仪的offer。
需要完整面试题的朋友可以点击蓝色字体免费获取




cW8JQRz-1712100151260)]
[外链图片转存中…(img-cMAsS4or-1712100151260)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!
[外链图片转存中…(img-NcSFH917-1712100151261)]
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:前端)
最后的最后
面试题千万不要死记,一定要自己理解,用自己的方式表达出来,在这里预祝各位成功拿下自己心仪的offer。
需要完整面试题的朋友可以点击蓝色字体免费获取
[外链图片转存中…(img-NPfEHBKY-1712100151261)]
[外链图片转存中…(img-32OOREF2-1712100151261)]
[外链图片转存中…(img-Jpf1OTEL-1712100151262)]
[外链图片转存中…(img-9LH52pnA-1712100151262)]
本文详细介绍了JavaScript中的生成器函数,包括其工作原理(yield表达式和暂停执行),以及如何通过yield实现状态机和消息传递。文章还探讨了生成器与Iterator接口的关系,并提供了生成器的基本用法示例和注意事项。
1760

被折叠的 条评论
为什么被折叠?



