本文深入解析ES6提供的Generator函数,阐述其作为状态机和遍历器生成函数的双重特性。详细讲解语法特征、分段执行机制、yield与return的区别,以及next方法传参对yield返回值的影响,帮助开发者掌握这一异步编程核心工具。
前言
Generator函数概述
Generator函数是ES6规范中引入的一种用于处理异步编程的机制,其语法行为与传统函数存在显著差异。
从多个维度理解Generator函数,首先可以将其视为一个状态机,其中封装了多个内部状态。
执行Generator函数会返回一个遍历器对象,这意味着Generator函数除了充当状态机外,还是一个遍历器对象的生成函数。通过返回的遍历器对象,可以依次遍历Generator函数内部的每一个状态。
接下来将详细介绍相关内容。
语法与形式特征
语法层面
从语法角度理解,Generator函数本质上是一个状态机,封装了多个内部状态。执行该函数会返回一个遍历器对象,即它同时具备状态机和遍历器对象生成函数的特性。返回的遍历器对象允许依次访问Generator函数内部的各个状态。
形式特征
Generator函数在形式上是一个普通函数,但具备两个显著特征。
第一,在function关键字与函数名之间必须添加一个星号(*);第二,函数体内部使用yield表达式来定义不同的内部状态(yield在英语中意为“产出”)。
调用机制与执行流程
调用方式
Generator函数的调用方式与普通函数相同,即在函数名后加一对圆括号。不同之处在于,调用Generator函数后,函数体并不会立即执行,而是返回一个指向内部状态的指针对象,即前文提到的遍历器对象(Iterator Object)。必须调用该遍历器对象的next方法,指针才会移向下一个状态。每次调用next方法,内部指针从函数头部或上一次暂停的位置开始执行,直到遇到下一个yield表达式(或return语句)为止。换言之,Generator函数的执行是分段的,yield表达式标志着暂停执行,而next方法用于恢复执行。
function* helloWorldGenerator() {
yield 'hello';
yield 'world';
return 'ending';
}
var hw = helloWorldGenerator();
hw.next()
// { value: 'hello', done: false }
hw.next()
// { value: 'world', done: false }
hw.next()
// { value: 'ending', done: true }
hw.next()
// { value: undefined, done: true }
调用Generator函数会返回一个遍历器对象,代表函数内部的指针。此后,每次调用遍历器对象的next方法,都会返回一个包含value和done两个属性的对象。value属性表示当前内部状态的值,即yield表达式后方表达式的值;done属性为布尔值,指示遍历是否结束。
yield表达式详解
yield表达式
yield表达式与return语句既有相似之处,也有区别。相似点在于,两者都能返回紧跟在语句后的表达式的值。区别在于,每次遇到yield,函数会暂停执行,下次从该位置继续向后执行,而return语句不具备位置记忆功能。一个函数中只能执行一次(或一个)return语句,但可以执行多次(或多个)yield表达式。普通函数只能返回一个值,因为只能执行一次return;而Generator函数可以返回一系列值,因为可以有任意多个yield。从另一个角度看,Generator生成了系列值,这也是其名称的由来(英语中generator意为“生成器”)。
语法注意事项:
1. yield表达式只能出现在Generator函数内部。
2. 如果yield表达式用于其他表达式中,必须将其置于圆括号内。
3. 当yield表达式用作函数参数或位于赋值表达式的右侧时,可以不加括号。
例如:
function* demo() {
foo(yield 'a', yield 'b'); // OK
let input = yield; // OK
}
next方法传参机制
next方法的参数
yield表达式本身没有返回值(例如let a = yield;会返回undefined),或者说总是返回undefined。next方法可以接受一个参数,该参数将被视为上一个yield表达式的返回值(注意,是表达式的返回值,例如let a = yield...,参数会成为a的值,并覆盖表达式之前的值)。
function* f() {
for(var i = 0; true; i++) {
var reset = yield i;
console.log(reset);
if(reset) { i = -1; }
}
}
var g = f();
g.next()

由于next方法的参数表示上一个yield表达式的返回值,因此在第一次调用next方法时,传递参数是无效的。V8引擎会直接忽略第一次调用next方法时的参数,只有从第二次调用next方法开始,参数才有效。从语义上讲,第一个next方法用于启动遍历器对象,因此无需携带参数。
总结
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对本站的支持。