1 Generator 函数是一个状态机,封装了多个内部状态
2 执行 Generator 函数会返回一个遍历器对象;返回的遍历器对象,可以依次遍历 Generator 函数内部的每一个状态
3 特征
- function关键字与函数名之间有一个星号
- 函数体内部使用 yield 表达式,定义不同的内部状态(yield在英语里的意思就是“产出”)
4 调用 Generator 函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象,即遍历器对象(Iterator Object)
5 每次调用next方法,内部指针就从函 数头部或上一次停下来的地方开始执行,直到遇到下一个yield表达式(或return语句)为止
6 Generator 函数是分段执行的,yield表达式是暂停执行的标记,而next方法可以恢复执行
7 每次调用遍历器对象的next方法,就会返回一个有着value和done两个属性的对象。value属性表示当前的内部状态的值,是yield表达式后面那个表达式的值;done属性是一个布尔值,表示是否遍历结束
8 yield表达式如果用在另一个表达式之中,必须放在圆括号里面
9 yield表达式用作函数参数或放在赋值表达式的右边,可以不加括号
10 由于 Generator 函数就是遍历器生成函数,因此可以把 Generator 赋值给对象的Symbol.iterator属性,从而使得该对象具有 Iterator 接口
11 Generator 函数执行后,返回一个遍历器对象。该对象本身也具有Symbol.iterator属性,执行后返回自身。
function* gen(){
// some code
}
var g = gen();
g[Symbol.iterator]() === g
// true
12 遍历器对象的next方法的运行逻辑:
(1)遇到yield表达式,就暂停执行后面的操作,并将紧跟在yield后面的那个表达式的值,作为返回的对象的value属性值
(2)下一次调用next方法时,再继续往下执行,直到遇到下一个yield表达式。
(3)如果没有再遇到新的yield表达式,就一直运行到函数结束,直到return语句为止,并将return语句后面的表达式的值,作为返回的对象的value属性值。
(4)如果该函数没有return语句,则返回的对象的value属性值为undefined。
13 yield表达式本身没有返回值,或者说总是返回undefined。next方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值
function* f() {
for(var i = 0; true; i++) {
console.log('i', i);
var reset = yield i;
console.log('reset',reset);
if(reset) { i = -1; }
console.warn('i', i);
}
}
var g = f();
console.log(g.next()) // { value: 0, done: false }
console.log(g.next()) // { value: 1, done: false }
console.log(g.next(true)) // { value: 0, done: false }
// 执行第一个next
// i 0
// {value: 0, done: false} // 遇到yield,暂停后面的操作,紧跟在yield后面的那个表达式的值0,作为返回的对象的value属性值
// 执行第二个next,没有传入参数,reset为undefined
// reset undefined
// i 0
// i 1
// {value: 1, done: false}
// 执行第三个next,传入参数true,reset为true
// reset true
// i -1
// i 0
// {value: 0, done: false}
14 由于next
方法的参数表示上一个yield
表达式的返回值,所以在第一次使用next
方法时,传递参数是无效的。V8 引擎直接忽略第一次使用next
方法时的参数,只有从第二次使用next
方法开始,参数才是有效的。从语义上讲,第一个next
方法用来启动遍历器对象,所以不用带有参数。
15 一旦next
方法的返回对象的done
属性为true
,for...of
循环就会中止,且不包含该返回对象,
Generator.prototype.throw()
1、Generator 函数返回的遍历器对象,都有一个throw
方法,可以在函数体外抛出错误,然后在 Generator 函数体内捕获。
var g = function* () {
try {
yield;
} catch (e) {
console.log('内部捕获', e);
}
};
var i = g();
i.next();
try {
// 遍历器对象i连续抛出两个错误
i.throw('a'); // 第一个错误被 Generator 函数体内的catch语句捕获。
i.throw('b'); // i第二次抛出错误,由于 Generator 函数内部的catch语句已经执行过了,不会再捕捉到这个错误了,所以这个错误就被抛出了 Generator 函数体,被函数体外的catch语句捕获。
} catch (e) {
console.log('外部捕获', e);
}
// 内部捕获 a
// 外部捕获 b
2、全局的throw
命令只能被函数体外的catch
语句捕获。
var g = function* () {
while (true) {
try {
yield;
} catch (e) {
if (e != 'a') throw e;
console.log('内部捕获', e);
}
}
};
var i = g();
i.next();
try {
throw new Error('a'); // 全局的throw命令
// 函数体外的catch语句块,捕获了抛出的a错误以后,就不会再继续try代码块里面剩余的语句了
throw new Error('b'); // 不会执行
} catch (e) {
console.log('外部捕获', e);
}
// 外部捕获 [Error: a]
3、如果 Generator 函数内部没有部署try...catch
代码块,那么throw
方法抛出的错误,将被外部try...catch
代码块捕获。
4、如果 Generator 函数内部和外部,都没有部署try...catch
代码块,那么程序将报错,直接中断执行。
5、throw
方法抛出的错误要被内部捕获,前提是必须至少执行过一次next
方法。因为第一次执行next
方法,等同于启动执行 Generator 函数的内部代码,否则 Generator 函数还没有开始执行,这时throw
方法抛错只可能抛出在函数外部。
6、throw
方法被捕获以后,会附带执行下一条yield
表达式。也就是说,会附带执行一次next
方法。
var gen = function* gen(){
try {
yield console.log('a');
} catch (e) {
// ...
}
yield console.log('b');
yield console.log('c');
}
var g = gen();
g.next() // a
g.throw() // b -- g.throw方法被捕获以后,自动执行了一次next方法,所以会打印b
g.next() // c -- 只要 Generator 函数内部部署了try...catch代码块,那么遍历器的throw方法抛出的错误,不影响下一次遍历。
Generator.prototype.return()
1、返回给定的值,并且终结遍历 Generator 函数
2、如果 Generator 函数内部有try...finally
代码块,那么return
方法会推迟到finally
代码块执行完再执行
function* numbers () {
yield 1;
try {
yield 2;
yield 3; // 不执行
} finally {
yield 4;
yield 5;
}
yield 6; // 不执行
}
var g = numbers();
g.next() // { value: 1, done: false }
g.next() // { value: 2, done: false }
g.return(7) // { value: 4, done: false }
g.next() // { value: 5, done: false }
g.next() // { value: 7, done: true }
g.next() // { value: undefined, done: true }
yield* 表达式
如果在 Generator 函数内部,调用另一个 Generator 函数,默认情况下是没有效果的。
yield*
表达式,用来在一个 Generator 函数里面执行另一个 Generator 函数。
function* bar() {
yield 'x';
yield* foo();
yield 'y';
}
// 等同于
function* bar() {
yield 'x';
yield 'a';
yield 'b';
yield 'y';
}
// 等同于
function* bar() {
yield 'x';
for (let v of foo()) {
yield v;
}
yield 'y';
}
for (let v of bar()){
console.log(v);
}
// "x"
// "a"
// "b"
// "y"
如果yield*
后面跟着一个数组,由于数组原生支持遍历器,因此就会遍历数组成员
function* gen(){
yield* ["a", "b", "c"];
}
// yield命令后面如果不加星号,返回的是整个数组,加了星号就表示返回的是数组的遍历器对象
gen().next() // { value:"a", done:false }
yield*
命令可以很方便地取出嵌套数组的所有成员。
function* iterTree(tree) {
if (Array.isArray(tree)) {
for(let i=0; i < tree.length; i++) {
yield* iterTree(tree[i]);
}
} else {
yield tree;
}
}
const tree = [ 'a', ['b', 'c'], ['d', 'e'] ];
for(let x of iterTree(tree)) {
console.log(x);
}
// a
// b
// c
// d
// e
作为对象属性的 Generator 函数
如果一个对象的属性是 Generator 函数,可以简写成下面的形式。
let obj = {
* myGeneratorMethod() {
···
}
};
上面代码中,myGeneratorMethod
属性前面有一个星号,表示这个属性是一个 Generator 函数。
它的完整形式如下,与上面的写法是等价的。
let obj = {
myGeneratorMethod: function* () {
// ···
}
};
Generator 函数的this
Generator 函数总是返回一个遍历器,ES6 规定这个遍历器是 Generator 函数的实例,也继承了 Generator 函数的prototype
对象上的方法。
function* g() {
this.a = 12;
}
g.prototype.hello = function () {
return 'hi!';
};
let obj = g();
obj instanceof g // true
obj.hello() // 'hi!'
// 把g当作普通的构造函数,并不会生效,因为g返回的总是遍历器对象,而不是this对象。
obj.a // undefined
Generator 函数也不能跟new
命令一起用,会报错。