generator函数是ES6提供的异步解决方案,跟之前的完全不同。先看看语法:
function* generator() {
yield 'a';
yield 'b';
return 'c';
}
var ge = generator();
console.log(ge.next());//{value: "a", done: false}
console.log(ge.next());//{value: "b", done: false}
console.log(ge.next());//{value: "c", done: true}
Function关键字之后加*内部用yield表达式,相当于这个函数有三个状态a、b、c。调用的时候跟普通函数一样,但是不是执行这个函数,而是返回一个指针对象,也就是iterator Object。当我们调用遍历器对象的next方法的时候,指针向下移动。每次调用next方法,内部指针从函数头部或上一次停下的地方开始执行直到下一个yield或者return 语句。Generator函数就是分段执行,yield表达式就是暂停执行,next方法继续往下执行。
打印next,可以看见返回的是value和done,这跟iterator是一样的,当done是true的时候表示遍历结束了,结束之后继续调用则都是undefined和true。要注意的是,如果没有return,那么返回对象的值就是undefined。
Yield和return的区别就是yield可以多个,return只能一个,yield可以记住位置,return不行。如果generator没有yield,那么这个函数就只是普通的函数,而且是赋值之后还要调用next才会执行的暂缓执行函数。要特别注意,yield表达式只能在generator函数里面,其他全部会报错。
Yield如果放在其他表达式里面,要加圆括号:
console.log(1 + yield); // SyntaxError
console.log(1 + yield 123); // SyntaxError
console.log(1 + (yield));
console.log(1 + (yield 1));
赋值的时候放第一个也不会报错
let a = yield + 1;
let b = 1 + yield;//SyntaxError
函数参数的时候也可以不用圆括号:
function* generator() {
fn(yield 1)
}
Next方法可以携带参数,当作yield表达式返回的值:
function* generator() {
var a = yield 'a';
console.log(a);//1
var b = yield 'b';
console.log(b);//2
return a + b;
}
var ge = generator();
console.log(ge.next(0));//{value: "a", done: false}
console.log(ge.next(1));//{value: "b", done: false}
console.log(ge.next(2));//{value: "3", done: false}
传参是非常有用的,因为你只有通过这一个方法向内部注入变量,去调整行为。注意,参数表示的是上一个,一定要注意是上一个yield表达式后面的返回值,第一次调用的时候传参是无效的。
刚说过generator是一个生成iterator对象的函数,所以可以直接使用for of,不需要调用next方法:
function* generator() {
yield 1;
yield 2;
yield 3;
yield 4;
yield 5;
return 6;
}
for(let i of generator()){
console.log(i);
}//1 2 3 4 5
当next返回的done是true的时候,循环会终止,且不包含返回的对象,所以return的语句不会在循环中。
通过这个特性,可以做好多循环的拓展,比如让json可以循环:
let obj = {a: 1, b: 2, c: 3};
function* fn(obj) {
let keys = Reflect.ownKeys(obj);
for(let key of keys){
yield [key, obj[key]]
}
}
for(let [key, val] of fn(obj)){
console.log(key + val);
}
可以让很多不能循环的通过generator函数加上遍历接口。
Generator.prototype.return() :
除了内部return终止generator函数之外,还有一个外部的return方法:
function* generator() {
yield 1;
yield 2;
yield 3;
}
let ge = generator();
console.log(ge.next());//{value: 1, done: false}
console.log(ge.return());//{value: undefined, done: true}
console.log(ge.next());//{{value: undefined, done: true}
且最后一个返回的是return的参数,如果没有参数则是undefined。
Yield*表达式主要是用在generator表达式内部嵌套generator函数的时候,如果我们在generator函数内部嵌套多个generator,那么我们需要手动完成遍历,然后ES6还提供了yield*表达式,用来执行内部generator函数:
function* ge1() {
yield 'a';
}
function* ge2() {
yield 'b';
yield ge1();
yield 'c';
}
var gen = ge2()
console.log(gen.next().value); //b
console.log(gen.next().value); //遍历器对象
console.log(gen.next().value); //c
function* ge3() {
yield 'd'
yield* ge1()
yield 'e'
}
var gen = ge3()
console.log(gen.next().value); //d
console.log(gen.next().value); //a
console.log(gen.next().value); //e
要注意的是,如果是嵌入使用的generator,最好不要有return语句,如果有就要自己去赋值获取return语句的值。没有return语句相当于内置了for of循环,且有iterator接口的都能被yield*遍历。
Generator的上下文比较特殊,当遇到yield的时候,会暂时退出堆栈,但是不会消失,直到执行next的时候重新加入调用栈。
Generator对于异步的处理显而易见了,当你需要同步执行的时候,调用next就能按顺序执行。
还有一部分关于throw的方法、this、状态机、协程等,个人觉得实在是篇幅太多,有兴趣可以自己去阮一峰大神的ECMAScript去看看,本人也只是通过这本书做一些笔记而已。
(完)