Generator
Generator
Generator 函数
在聊async/await
前,必须先聊一下generator
,因为async/await
是generator
的语法糖。
前端人员都知道,generator
有一下几个特点:
- function 关键字与函数名之间有一个星号 "*" 。
- 函数体内使用 yield 表达式,定义不同的内部状态 (可以有多个 yield)。
- 直接调用
Generator
函数并不会执行,也不会返回运行结果,而是返回一个迭代器对象(Iterator Object)。 - 依次调用遍历器对象的
next
方法,遍历Generator
函数内部的每一个状态。 仅仅知道这些我认为还不够,我们先来看一下,声明完这个函数后,这个函数中到底有什么东西:
- 首先我们在
prototype
下的__proto__
中找到了挂载在实例下的方法next()
,return()
,throw()
。这些方法我们肯定是要搞懂的。继续向下看。 - 普通函数的
__proto__
下面的constructor
都是Function()
, 说明是被Function
实例化出的对象,但它的是GeneratorFunction()
。这是个问题。 通过这张图,目前发现这两个问题,我们先解决第一个,但在这之前我还有做一个简单的介绍作为基础。
在普通函数中,我们想要一个函数最终的执行结果,一般都是return
出来,或者以return
作为结束函数的标准。运行函数时也不能被打断,期间也不能从外部再传入值到函数体内。
但在Generator
中就打破了这几点,所以他与其他函数完全不同。
当以function*
的方式声明了一个Generator
生成器时,内部是可以有许多状态的,以yield
进行断点间隔。期间我们执行调用这个生成的Generator
,他会返回一个遍历器对象,用这个对象上的方法,实现获得一个yield
后面输出的结果。
function* generator() {
yield 1
yield 2
};
let iterator = generator();
iterator.next() // {value: 1, done: false}
iterator.next() // {value: 2, done: false}
iterator.next() // {value: undefined, done: true}
Generator 实例上的方法
next()
1)从上面代码中,我们也大概了解到.next()
其实是为了遍历这个遍历器对象, 每一次的调用,都会返回一个对象:
value
值为内部yield
表达式的返回值。- 还有一个表示是否执行完毕的值
done
,next()
找不到后面的yield
关键字时,返回值为true
,同时由于没找到最后一个yield
表达式的值,所以value
为undefined
。 - 如果在
yield
之前执行了return
,那么遍历到return
时就会返回{value: undefined, done: true}
无论之后再遍历,也不会执行后的热yield
。 2)我们还可以向next()
中传递参数,可以作为下次输出的内容。
function* gen(x) {
var y = 2 * (yield (x + 1));
var z = yield (y / 3);
return (x + y + z);
}
var g = gen(5);
g.next() // { value:6, done:false }
g.next(15) // { value:10, done:false }
g.next(20) // { value:55, done:true }
- 第一运行用
next
方法,系统自动忽略参数,返回x+1
的值 6; - 第二次调用
next
方法,将上一次yield
表达式的值设为 15,因此 y 等于 30,返回y / 3
的值 10; - 第三次调用
next
方法,将上一次yield
表达式的值设为 20,因此 z 等于 20,这时 x 等于 5,y 等于 30,所以 return 语句的值等于 55。 可以看出该参数会被当做上一条yield
语句的返回值。
3)由于for...of
是用于迭代可迭代对象的循环方法,所以同样适用于我们的generator
返回的遍历器对象。此时就不需要调用next
。
同理相同性质的方法也是可以遍历出遍历器对象的值,如扩展运算符,Array.from()
.
function* gen(x) {
yield 1;
yield 2;
yield 3;
}
var g = gen();
for (let item of g) {
console.log(item) // 1, 2, 3
}
[...g] // [1,2,3]
Array.from(g) // [1,2,3]
return()
无论 generator
中有对少个 yield
,只要被调用了 return()
函数后,返回的结果的 done
值被立即置为 true
,结束遍历 Generator
函数。
如果 return()
函数不传参数时,value
为 undifined
,传递参数后,value
值为传递的参数。
function* generator() {
yield 1
yield 2
yield 3
yield 4
}
let g = generator()
console.log(g.next()); // {value: 1, done: false}
console.log(g.return()); // {value: undefined, done: true}
console.log(g.next(5)); // {value: undefined, done: true}
console.log(g.return(new Date())) // {value: Mon Sep 13 2021 23:37:57 GMT+0800 (中国标准时间), done: true}
console.log(g.next()); // {value: undefined, done: true}
throw()
调用了 throw()
函数后,Generator
函数内部可以通过 try/catch
来捕获此错误。并返回的结果的 done
值被立即置为 true
,结束遍历 Generator
函数。
如果 throw()
函数不传参数时,捕获的异常为 undifined
,传递参数后,捕获的异常为传递的参数。一般实例化 Error()
配合使用
function* gen() {
try {
yield 1;
yield 2;
yield 3;
yield 4;
} catch (e) {
console.log("generator内部错误", e);
}
}
var g = gen();
console.log(g.next()); // {value: 1, done: false}
console.log(g.next()); // {value: 2, done: false}
try {
g.throw(new Error("Something went wrong")); // generator内部错误 Error: Something went wrong
g.throw(new Error("oh no"));
} catch (error) {
console.log('外部错误', error); // 外部错误 Error: oh no
}
console.log(g.next()); // {value: undefined, done: true}
遍历器对象抛出了两个错误,第一个被 Generator
函数内部捕获。
第二个因为函数体内部的catch
函数已经执行过了,不会再捕获这个错误,所以这个错误就抛出 Generator
函数体,被函数体外的catch
捕获。 之后再执行 next()
方法也不会再继续向下遍历了。
Generator 内部是如何被实例化出来的
其实我们无论是声明一个数组、字符串、方法,其实内部都是通过实例化对应构造函数而生成的。
但这不能一概而论,还是有差别的,回头可以一起聊聊定义基础数据类型发生了什么,new Function
和函数声明的区别,基础和引用数据类型在内存的存储。
这里我们为了引出generator
函数是如何被实例化出来的?在控制台打印GeneratorFunction
也没有对应的构造函数,说明不在window
上。于是使用获取原型对象的方法,来找找他的原型对象上的构造函数。
console.dir(Object.getPrototypeOf(function*(){}).constructor)
// 打印
1. GeneratorFunction()
1. 1. arguments: [Exception: TypeError: 'caller', 'callee', and 'arguments' properties may not be accessed on strict mode functions or the arguments objects for calls to them at Function.invokeGetter (<anonymous>:2:14)]
1. caller: [Exception: TypeError: 'caller', 'callee', and 'arguments' properties may not be accessed on strict mode functions or the arguments objects for calls to them at Function.invokeGetter (<anonymous>:2:14)]
1. length: 1
1. name: "GeneratorFunction"
1. prototype: GeneratorFunction
1. 1. arguments: (...)
1. caller: (...)
1. constructor: ƒ GeneratorFunction()
1. prototype: Generator {constructor: GeneratorFunction, next: ƒ, return: ƒ, throw: ƒ, Symbol(Symbol.toStringTag): "Generator"}
1. Symbol(Symbol.toStringTag): "GeneratorFunction"
1. __proto__: ƒ ()
1. __proto__: ƒ Function()
1. [[Scopes]]: Scopes[0]
//那么我们验证下找到的构造函数是不是generator的构造函数
function* gen() {
yield 1
}
console.log(gen instanceof Object.getPrototypeOf(function*(){}).constructor) // true 是的
如果找到Object.getPrototypeOf(function*(){}).constructor
为generator
的构造函数,那反过来,我们通过实例化构造函数,应该也能生成generator
。
var GeneratorFunction = Object.getPrototypeOf(function* () { }).constructor;
var g = new GeneratorFunction("num", "yield num * 2");
console.log(g); //ƒ* anonymous(num) {yield num * 2}
var iterator = g(10);
console.log(iterator.next().value); // 20
console.log(iterator);
/*__proto__: Generator
* [[GeneratorLocation]]
* [[GeneratorStatus]]: "suspended"
* [[GeneratorFunction]]: ƒ* anonymous(num )
* [[GeneratorReceiver]]: Window
* [[Scopes]]: Scopes[2]
*/
GeneratorStatus 有两个值"suspended"和"closed" 对应done的true和false
实践证明,通过实例化GeneratorFunction
是可以生成一个 Generator
函数对象。
补充:yield* 表达式
通过上述两个问题,我们大概的了解了generator
的语法及来源,如果我们想在 Generator
函数中调用另一个Generator
函数,就可以通过yield*
来实现。
function* anotherGenerator(i) {
yield i + 1;
yield i + 2;
yield i + 3;
}
function* generator(i) {
yield i;
yield* anotherGenerator(i); // 移交执行权
yield i + 10;
}
var gen = generator(10);
console.log(gen.next().value); // 10
console.log(gen.next().value); // 11
console.log(gen.next().value); // 12
console.log(gen.next().value); // 13
console.log(gen.next().value); // 20
yield*
表达式表示 yield
返回一个遍历器对象,用于在 Generator
函数内部。
Generator
函数告一段落,总结一下:
- 定义时使用
function*
作为函数声明方式。 - 调用
Generator
函数时,返回的是一个迭代器对象不是执行结果,内部GeneratorStatus
有suspended
,closed
两个状态值,对应是否完成。 - 内部可以有多个
yield
来完成类似普通函数return
的功能。 - 可以通过 API 提供的
next()
方法,或者迭代循环的语句for...of
,Array.from()
,展开运算符...
,对迭代器对象进行输出每个yield
后的值,next()
也可以传递参数作为上一个yield
值参与下次运算。API 还有return()
,throw()
GeneratorFunction
是generator
的构造函数,但没挂载在window
上。yield *
表达式可以在一个Generator
函数中返回另一个Generator
函数的遍历器对象。
如果此文章对您有帮助或启发,那便是我的荣幸