# Generator函数
Generator函数是ES6提供的一种异步变成解决方案,语法行为与传统函数完全不同。
Generator函数有多种理解角度,语法上,可以将Generator函数理解为状态机,封装了多个内部状态。
执行Generator函数会返回一个遍历器对象,可以依次遍历Generator函数中的每一个状态。
Generator函数有两个特征:一是,function关键字紧跟一个*号;二是,函数体内部使用yield表达式,定义不同的内部状态。
yield能返回跟在语句后面的表达式的值。
function* helloWorldGenerator() {
yield 'hello'
yield 'world'
return 'ending'
}
var it = helloWorldGenerator()
console.log(it.next())
console.log(it.next())
console.log(it.next())
如果helloWorldGenerator函数没有return 'ending',那么第三次调用next函数的返回值中,value = undefined.
// 星号位置,都可以
function* generator() { }
function *generator() { }
function * generator() { }
function*generator() { }
# yield 表达式
yield表达式就是Generator函数暂停执行的标志。
遍历器的next方法运行如下:
- 第一次执行next(),遇到yield表达式,就暂停后面的操作,并将紧跟yield后的表达式的值,作为返回对象value的值。
- 下一次调用next方法时,再继续执行,直到遇到下一个yield表达式。
- 如果没有遇到yield表达式,就一直运行到函数结束,直到return语句为止,并将return后的表达式的值,作为返回对象的value属性的值。
- 如果没有return语句,那么返回对象的value属性的值为undefined。
# 与Iterator接口的关系
对于任意一个对象的System.iterator方法,等于该对象的遍历器生成函数,调用该函数 会返回该对象的一个遍历器对象。
var myIterator = {};
myIterator[Symbol.iterator] = function*() {
yield 1;
yield 2;
yield 3;
yield 4;
}
console.log([...myIterator])
# next方法的参数
yield表达式本身没有返回值,或者说总是undefined。next()方法可以带一个参数,该参数就会被当做上一个yield表达式的返回值。
该例子可以证明,yield返回值总是undefined,除非next方法传参。
function* f() {
for (var i = 0; true; i++){
var reset = yield i;
console.log(reset)
if (reset) {
i = -1;
}
}
}
var g = f();
console.log(g.next());
console.log(g.next());
console.log(g.next());
console.log(g.next());
console.log(g.next());
console.log(g.next());
console.log(g.next());
// console.log(g.next(true));
下面这个例子说明 yield表达式本身是返回undefined的,除非next方法传参
function* foo(x) {
var y = 2 * (yield x + 1)
var z = yield y / 3
return x + y + z
}
var a = foo(5)
// 6
console.log(a.next())
NaN
console.log(a.next())
NaN
console.log(a.next())
var b = foo(5)
// 6
console.log(b.next())
8
console.log(b.next(12))
// 42 y=24 z=13 x=5
console.log(b.next(13))
下面再来一个面试题,看看结果是什么
function* dataConsumer() {
console.log('started');
console.log(`1. ${yield}`)
console.log(`2. ${yield}`)
return 'result'
}
var it = dataConsumer();
// started
console.log(it.next())
// 1. a
console.log(it.next('a'))
// 1. b
console.log(it.next('b'))
# for...of 循环
for...of循环能够遍历Generator函数运行时生成的Iterator对象,并且不需要调用next方法。
function* foo() {
yield 1;
yield 2;
yield 3;
yield 4;
yield 5;
return 6;
}
let it = foo();
for (let key of it) {
console.log(key)
}
如果你运行这个代码就会发现,return的6没有输出,这是因为next方法返回对象的done属性为true时,for...of就会停止执行。
除了for...of外,扩展运算符(...),解构赋值和Array.form方法内部调用的,都是遍历器接口。这意味着他们都可以将Generator函数返回的遍历器对象作为参数。
function* foo() {
yield 1;
yield 2;
yield 3;
return 4;
yield 5;
}
// 扩展运算符
console.log([...foo()])
// Array.from
console.log(Array.from(foo()))
// 解构赋值
var [a, ...b] = foo()
console.log(a)
console.log(b)
# Generator.prototype.throw()
Generator函数返回的遍历器对象,都有一个throw方法,可以在函数体外跑出错误,然后在Generator函数体内部捕获。
function* g() {
try {
yield;
} catch (e) {
console.log('函数内部捕获错误', e)
}
return 123123
}
var it = g();
console.log(it.next())
try {
it.throw('a');
it.throw('b');
it.throw(new Error('记住,这么抛错误!'))
} catch (e) {
console.log('函数体外捕获错误', e)
}
# Generator.prototype.return()
Generator函数返回的遍历器对象,还有个return()方法,可以返回给定的值,并且终结遍历Generator函数。
function* foo() {
yield 1;
yield 1;
yield 1;
return 4;
}
var it = foo();
console.log(it.next())
console.log(it.return(666))
如果Generator函数内部有try...finally代码块,且正在执行try块,那么return方法会推迟到finally块执行完后再执行
// 即使return 也会先执行finally
function* foo2() {
yield 1
try {
yield 2
yield 3
} finally {
yield 4;
yield 5;
}
}
var it2 = foo2();
console.log(it2.next())
// 此处注意,要是没有执行到try内部,则finally不会执行。
// console.log(it2.next())
console.log(it2.return(555))
console.log(it2.next())
# next()、throw()、return() 的共同点
他们的作用都是让Generator函数恢复执行,并且使用不同的语句替换yield表达式。
next()是将yield表达式替换为一个值。
function* foo() {
let res = yield 1;
return res
}
var it = foo()
console.log(it.next())
console.log(it.next(123123))
第二个next(123123)相当于把yield表达式的值替换为123123,如果next参数为空,相当于替换为undefined;
throw()是将yield表达式替换成一个throw语句。
var it2 = foo();
console.log(it2.next())
console.log(it2.throw(new Error('又错了!')))
return()是将yield表达式替换成一个return语句。
var it3 = foo();
console.log(it3.next())
console.log(it3.return(666))
# yield* 表达式
在一个Generator函数内部,调用另一个Generator函数,需要在前者的函数体内,手动完成遍历
function* foo() {
yield 1;
yield 2;
return 3;
}
function* bar() {
yield 'x';
for (let i of foo()) {
console.log(i)
}
}
for (let i of bar()) {
console.log(i)
}
如果在bar函数内部调用多个Generator函数,处理就会变得特别麻烦,所以出现了yield*表达式。
// yield*版本
function* bar2() {
yield 'x'
yield* foo()
yield 'y'
return 'zzz';
}
let it2 = bar2();
console.log(it2.next())
console.log(it2.next())
console.log(it2.next())
console.log(it2.next())
console.log(it2.next())
// 等同于
function* bar3() {
yield 'x';
yield 1;
yield 2
yield 'y'
return 'zzz'
}
console.log('\n')
// 等同于
function* bar4() {
yield 'x';
for (let v of foo()) {
yield v
}
yield 'y'
return 'zzz'
}
for (let v of bar4()) {
console.log(v)
}
再看下面的一个例子,outer2用了yield*, outer1没用,结果是outer1返回了一个遍历器对象,outer2返回了遍历器对象内部的值。 从语法的角度看,如果yield后面跟着一个遍历器对象,那么需要在yield后加星号,表明它返回的是遍历器对象。这称为yield*表达式
function* inner() {
yield 'hello';
}
function* outer1() {
yield 'open';
yield inner()
yield 'close';
}
var it1 = outer1();
console.log(it1.next())
console.log(it1.next())
console.log(it1.next())
console.log(it1.next())
console.log(`\n outer2 `)
function* outer2() {
yield 'open';
yield* inner()
yield 'close'
}
var it2 = outer2();
console.log(it2.next())
console.log(it2.next())
console.log(it2.next())
console.log(it2.next())
再看下面这段代码,delegratingIterator是代理者,delegratedIterator是被代理者。由yield* delegratedIterator语句得到的是一个遍历器,所以要用星号表示。 运行结果就是使用了一个遍历器,遍历了多个Generator函数,有递归的效果。
let delegratedIterator = (function* () {
yield 'hello';
yield 'bye'
}())
let delegratingIterator = (function* () {
yield 'Greeting';
yield* delegratedIterator;
yield 'ok, bye'
}())
for (let value of delegratingIterator) {
console.log(value)
}
# 作为对象属性的Generator函数
let obj = {
*myGeneratorMethods() {
yield 3
yield 4
return 5
}
}
for (let value of obj.myGeneratorMethods()) {
console.log(value)
}
console.log(`\n 另一种方式声明对象方法`)
// 上面的写法等同于下面
let obj2 = {
myGeneratorMethods: function* () {
yield 1;
return 2;
}
}
for (let value of obj2.myGeneratorMethods()) {
console.log(value)
}
# Generator函数的this
Generator函数总是返回一个遍历器,ES6规定这个遍历器是Generator函数的实例,也继承了Generator函数的prototype对象上的方法。
function* foo() {
}
foo.prototype.hello = function () {
return 'hello'
}
let obj = foo();
console.log(obj instanceof foo)
console.log(obj.hello())
上述代码表明,Generator函数foo返回的遍历器obj,是foo的实例,同时也继承了foo.prototype。
# 含义
# Generator与状态机
var ticking = true;
var clock = function () {
if (ticking) {
console.log('tick')
} else {
console.log('tock')
}
ticking = !ticking
}
clock()
clock()
clock()
// Generator版本
function* clock2() {
while (true) {
console.log('tick')
yield
console.log('tock')
yield
}
}
var it = clock2();
console.log(it.next())
console.log(it.next())
console.log(it.next())
Generator实现与ES5的实现相比,少了外部保存状态的ticking变量,更简洁、安全。Generator之所以不用保存状态,是因为它本身就包含了一个状态信息, 即目前是否出于暂停状态。