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); // 20yield* 表达式表示 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函数的遍历器对象。
如果此文章对您有帮助或启发,那便是我的荣幸
