Generator
调用 Generator 函数,返回一个 遍历器对象,代表 Generator 函数的内部指针 以后,每次调用遍历器对象的 next方法,就会返回一个有着value和done两个属性的对象。value属性表示当前的内部状态的值,是yield表达式后面那个表达式的值;done属性是一个布尔值,表示是否遍历结束。
yield
generator函数返回的是一个遍历器对象,只有通过next方法来逐一访问每一个状态,所以其内部提供了一个暂停执行函数的机制,yield就是这个机制。
(1)执行遍历器对象的next方法时,函数内部遇到未执行的yield时,会暂停后边的执行,同时会把当前遇到的yield后边的语句作为返回对象的value值。
(2)如果执行next方法过程中再也没有遇到yield,而是遇到return则函数执行完成,并把return后边表达式的值作为返回对象的value值,done属性变为true。
ps:yield表达式后面的表达式, 只有当调用next方法、内部指针指向该语句时才会执行,因此等于为 JavaScript 提供了手动的“惰性求值”(Lazy Evaluation)的语法功能 。
yield vs return
遍历器执行next方法时都可以将他们后边的表达式的值作为返回对象的value值,但是生成器函数中只能有一个return但是可以有多个yield。
Generator 函数可以返回一系列的值,因为可以有任意多个yield
如果不用yield,则生成器函数就变成可单纯的 暂缓执行函数。
function*bar() {
console.log('暂缓执行');
}
const bar0 = bar();
// undefined
bar0.next();
// 暂缓执行
yield 表达式只能用在 Generator 函数里面,用在其他地方都会报错,yield表达式如果用在另一个表达式之中,必须放在圆括号里面。
var arr = [1, [[2, 3], 4], [5, 6]];
var flat = function* (a) {
a.forEach(function (item) {
if (typeof item !== 'number') {
yield* flat(item);
} else {
yield item;
}
});
};
for (var f of flat(arr)){
console.log(f);
}
function*foo() {
//console.log('Hello' + yield); //报错
//console.log('hello' + yield 'world'); //报错
console.log('Hello' + (yield));
console.log('hello' + (yield 'world'));
}
yield表达式用作函数参数或放在赋值表达式的右边,可以不加括号。ps: yield不能作为生成器函数的参数
function* demo() {
foo(yield 'a', yield 'b'); // OK
let input = yield; // OK
}
function foo() {}
function*b(yield 'okay') {} //报错
function*c((yield 'okay')) {} //报错
生成器函数与Iterator接口的关系
由于任何对象的Symbol.iterator方法都会返回一个遍历器对象,即该方法就是一个遍历器生成器。由于生成器函数本身就是一个遍历器生成器,所以可以把generator函数赋值给任何对象的Symbol.iterator方法属性,这样对象具有 Iterator 接口
const obj = {};
obj[Symbol.iterator] = function*() {
yield 'a';
yield 'b';
return 'c';
};
const o = [...obj]; //[a, b]
ps:结果中并不会有c
Generator 函数执行后,返回一个遍历器对象。该对象本身也具有Symbol.iterator属性,执行后返回自身
next方法的参数
yield表达式本身没有返回值,或者说总是返回undefined。 next方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值。
如果next方法没有参数,每次运行到yield表达式,变量reset的值总是undefined。当next方法带一个参数true时,变量reset就被重置为这个参数(即true),因此i会等于-1,下一轮循环就会从-1开始递增。
由于 next方法的参数表示上一个yield表达式的返回值, 所以在第一次使用next方法时,传递参数是无效的。V8 引擎直接忽略第一次使用next方法时的参数,只有从第二次使用next方法开始,参数才是有效的。
再看一个通过next方法的参数,向 Generator 函数内部输入值的例子
for...of 循环
for...of循环可以自动遍历 Generator 函数时生成的Iterator对象,且此时不再需要调用next方法。
这里需要注意,一旦next方法的返回对象的done属性为true,for...of循环就会中止,且不包含该返回对象所以上面代码的return语句返回的6,不包括在for...of循环之中。
利用for...of循环,可以写出遍历任意对象(object)的方法。原生的 JavaScript 对象没有遍历接口,无法使用for...of循环,通过 Generator 函数为它加上这个接口,就可以用了。
加上遍历器接口的另一种写法是,将 Generator 函数加到对象的Symbol.iterator属性上面。
除了for...of循环以外,扩展运算符(...)、解构赋值和Array.from方法内部调用的,都是遍历器接口。这意味着,它们都可以将 Generator 函数返回的 Iterator 对象,作为参数。
Generator.prototype.throw()
Generator 函数返回的遍历器对象,都有一个 throw方法,可以在函数体外抛出错误,然后在 Generator 函数体内捕获。
throw方法被捕获以后,会附带执行下一条yield表达式。也就是说,会附带执行一次next方法。
g.throw方法被捕获以后,自动执行了一次next方法,所以会打印b。另外,也可以看到,只要 Generator 函数内部部署了try...catch代码块,那么遍历器的throw方法抛出的错误,不影响下一次遍历。
上面代码中,throw命令抛出的错误只要有catch捕获,不会影响到遍历器的状态,所以两次执行next方法,都进行了正确的操作。
一旦 Generator 执行过程中抛出错误,且没有被内部捕获,就不会再执行下去了。如果此后还调用next方法,将返回一个value属性等于undefined、done属性等于true的对象,即 JavaScript 引擎认为这个 Generator 已经运行结束了。
Generator.prototype.return()
如果 Generator 函数内部有try...finally代码块,那么return方法会推迟到finally代码块执行完再执行。
yield*表达式
在Generator 函数内部调用另一个 Generator 函数,默认情况下是没有效果的
yield*表达式,用来在一个 Generator 函数里面执行另一个 Generator 函数。
yield*后面的 Generator 函数(没有return语句时),等__同于在 Generator 函数内部,部署一个for...of循环__。
上面代码中,yield命令后面如果不加星号,返回的是整个数组,加了星号就表示返回的是数组的遍历器对象。
实际上,任何数据结构 只要有 Iterator 接口,就可以被yield*遍历。