生成器
一、基本概念
Generator
函数是 ES6 提供的一种异步编程
解决方案,语法行为与传统函数完全不同.
Generator 函数有多种理解角度。语法上,首先可以把它理解成,Generator 函数是一个状态机
,封装了多个内部状态。
执行 Generator 函数会返回一个遍历器对象,也就是说,Generator 函数除了状态机,还是一个遍历器对象生成函数。返回的遍历器对象
,可以依次遍历 Generator 函数内部的每一个状态。
function * helloGenerator(){
yield "h";
yield "e";
yield "l";
yield "l";
yield "o";
return ;
}
const iterator = helloGenerator();
总结一下,调用 Generator 函数,返回一个遍历器对象,代表 Generator 函数的内部指针。以后,每次调用遍历器对象的 next 方法,就会返回一个有着 value 和 done 两个属性的对象。 value 属性表示当前的内部状态的值,是 yield 表达式后面那个表达式的值; done 属性是一个布尔值,表示是否遍历结束。
二、yield表达式
由于 Generator 函数返回的遍历器对象,只有调用 next 方法才会遍历下一个内部状态,所以其实提供了一种可以暂停执行的函数。yield
表达式就是暂停标志
。
遍历器对象的 next 方法的运行逻辑如下。
(1)遇到 yield 表达式,就暂停执行后面的操作,并将紧跟在 yield 后面的那个表达式的值,作为返回的对象的 value 属性值。
(2)下一次调用 next 方法时,再继续往下执行,直到遇到下一个 yield 表达式。
(3)如果没有再遇到新的 yield 表达式,就一直运行到函数结束,直到 return 语句为止,并将 return 语句后面的表达式的值,作为返回的对象的 value 属性值。
(4)如果该函数没有 return 语句,则返回的对象的 value 属性值为 undefined 。
需要注意的是, yield 表达式后面的表达式,只有当调用 next 方法、内部指针指向该语句时才会执行,因此等于为 JavaScript 提供了手动的“惰性求值”(Lazy Evaluation)的语法功能。
yield与return比较:
- 相同:都能返回紧跟在语句后面的那个表达式的值
- 不同:区别在于每次遇到 yield ,函数暂停执行,下一次再从该位置继续向后执行,而 return 语句不具备位置记忆的功能。一个函数里面,只能执行一次(或者说一个) return 语句,但是可以执行多次(或者说多个) yield 表达式。正常函数只能返回一个值,因为只能执行一次 return ;Generator 函数可以返回一系列的值,因为可以有任意多个 yield 。从另一个角度看,也可以说 Generator 生成了一系列的值,这也就是它的名称的来历(英语中,generator 这个词是“生成器”的意思)
Generator 函数可以不用 yield 表达式,这时就变成了一个单纯的暂缓执行函数。
yield 表达式只能用在 Generator 函数里面,用在其他地方都会报错。
yield 表达式如果用在另一个表达式之中,必须放在圆括号里面。
yield 表达式用作函数参数或放在赋值表达式的右边,可以不加括号。
三、与 Iterator 接口的关系
任意一个对象的 Symbol.iterator 方法,等于该对象的遍历器生成函数,调用该函数会返回该对象的一个遍历器对象。
由于 Generator 函数就是遍历器生成函数,因此可以把Generator
赋值给对象的 Symbol.iterator
属性,从而使得该对象具有Iterator
接口。
四、next 方法的参数
yield
表达式本身没有返回值,或者说总是返回 undefined 。
function * f2(){
console.log(yield 1);
}
const iter = f2();
iter.next() // {"value": 1,"done": false}
iter.next() // undefined {"value":undefined,done:true}
// 第一次执行yield 1 ,返回一个对象,暂停。
// 第二次接着执行console.log(),因为yield表达式没有返回值,所以打印出undefined
next 方法可以带一个参数,该参数就会被当作上一个 yield 表达式的返回值。
function* gen() {
while (true) {
let reslut = yield "hello";
console.log("reslut:", reslut);
}
}
const gens = gen();
gens.next(); // 没有输出,执行yield之后就暂停了(赋值运算符右边先执行)。
gens.next(); // 继续执行赋值运算,因为yield表达式没有返回值,所有resule是undefined
gens.next("world") // 将参数作为上一次yield表达式的返回值,result赋值为world
这个功能有很重要的语法意义。Generator 函数从暂停状态到恢复运行,它的上下文状态(context)是不变的。通过 next 方法的参数,就有办法在 Generator 函数开始运行之后,继续向函数体内部注入值。也就是说,可以在 Generator 函数运行的不同阶段,从外部向内部注入不同的值,从而调整函数行为。
五、for...of
for...of
循环可以自动遍历 Generator
函数运行时生成的Iterator
对象,且此时不再需要调用 next 方法。
function* foo() {
yield 1;
yield 2;
yield 3;
yield 4;
yield 5;
return 6;
}
for (let v of foo()) {
console.log(v);
}
上面代码使用 for...of 循环,依次显示 5 个 yield 表达式的值。这里需要注意,一旦 next 方法的返回对象的 done 属性为 true , for...of 循环就会中止,且不包含该返回对象,所以上面代码的 return 语句返回的 6 ,不包括在 for...of 循环之中。
六、yield*表达式
如果在 Generator 函数内部,调用另一个 Generator 函数。需要在前者的函数体内部,自己手动完成遍历。
function * f1(){
yield 1;
yield 2;
}
function * f2(){
yield 'start';
yield* f1();
yield 'end'
}
const iter = f2();
for(let value of iter){
console.log(value)
}
// 输出
// start
//1
//2
//end
对比使用yield:
function * f1(){
yield 1;
yield 2;
}
function * f2(){
yield 'start';
yield f1();
yield 'end'
}
const iter = f2();
for(let value of iter){
console.log(value)
}
// 输出
// start
// f1 <generator>
//end
// yield f1()返回的是一个迭代器对象,而不是迭代器的内部值
从语法角度看,如果 yield 表达式后面跟的是一个遍历器对象,需要在 yield 表达式后面加上星号,表明它返回的是一个遍历器对象。这被称为 yield* 表达式。
如果被代理的 Generator 函数有 return 语句,那么就可以向代理它的 Generator 函数返回数据。
如果一个对象的属性是 Generator 函数,可以简写成下面的形式。
let obj = {
* myGeneratorMethod() {
···
}
};
// 完整写法
let obj = {
myGeneratorMethod: function* () {
// ···
}
};
七、Generator 函数的 this
Generator 函数总是返回一个遍历器,ES6 规定这个遍历器是 Generator 函数的实例,也继承了 Generator
函数的 prototype
对象上的方法。
function * gen(){
this.name = "hello"
}
gen.prototype.say = function(){
console.log("world")
}
let g = gen();
console.log(g instanceof gen); //true
g.say();// world
console.log(g.name) //undefined
g.name 是undefined ,因为 gen返回的总是遍历器对象,而不是 this 对象。
Generator 函数也不能跟 new 命令一起用,会报错。