Generator
是与 Promise
同时由 ES6
引入标准的语法,最早由社区提出和实现
主要用于实现一种新的状态机制管理,让一段代码逻辑可以动态控制分段执行
Generator 新增关键字及函数
*
关键字
*
关键词用于生成 Generator函数
(*
前后的空格没有影响),但有以下几个限制:
- 只能用于
function
关键字 - 不能用于箭头函数
function * fun1 () {} // work
function* fun2 () {} // work
function *fun3 () {} // work
const fun4 = function * () {} // work
const fun5 = *() => {} // 报错!
一旦函数被生成为 Generator函数
,这个函数就不能作为构造函数被 new
关键字使用了
function * Fun() {}
const obj = new Fun() {} // 报错!
yield
关键字
yield
的返回值
yield
在功能上和 return
有些类似,都用于将值返回,但有些差异:
-
return
语句之后无法进行任何操作;yield
后续的程序可以通过调用next
继续执行 -
return
无法和赋值符一起使用;yield
可以和赋值符一起使用 -
return
可以独立使用;yield
必须配合Generator函数
和next
函数才能使用
function * test() {
const first = yield 1
const second = yield 2
const third = yield 3
}
const obj = test()
obj.next() // {value: 1, done: false}
obj.next() // {value: 2, done: false}
obj.next() // {value: 3, done: false}
obj.next() // {value: undefined, done: true}
yield
可以拼接的表达式
yield
后面可以拼接多种类型的值
- 基本类型:
yield '123'
、yield 123
、yield { name: 'Peter' }
等等 - 函数:
yield function test(){}
- 表达式:
yield 1 + 2 + 3
多个 yield
拼接
yield
和 yield
拼接时,后一个 yield
作为变量使用,为 undefined
function * test() {
yield yield
}
const obj = test()
obj.next() // {value: undefined, done: false}
yield 的类函数形式
看一段代码
function * test() {
yield 1
}
// 上面的写法,等价于下方的写法
function * test() {
yield(1)
}
yield(1)
咋一看,会让人觉得是 yield 作为一个函数被调用了
其实是 yield
返回了一个使用小括号包裹的表达式(1)
这里纯粹是个人见解,没有找到权威的文档佐证,欢迎指正!
next 函数
第一次 next 调用的传参没有作用
function *test(x) {
const re = yield 1 + x;
return re + 2;
}
const obj = test(5);
obj.next(2) // {value: 6, done: false}
obj.next(3) // {value: 5, done: true}
如上
- 变量
x
来自生成Generator对象
时的参数5
- 第一次
next
调用,函数内部开始执行到 yield 返回1 + 5
,因此本次next
参数无效 - 第二次
next
调用,next 的参数 3 赋值给 yield 后面的整个表达式const re = 3
,因此结果为3 + 2
next 函数参数是对上一个 yield 及后方整个表达式的值覆盖
function *test() {
console.log(yield 5) // 2
}
const obj = test();
obj.next()
obj.next(2)
第二次 next
调用,yield
5
的值为 2
,被参数覆盖
yield 和 next 的返回值
对于 yield
和 next
的返回值问题一直比较模糊
{ value: any, done: Boolean }
到底是 yield
的返回值还是 next
的返回值?
下面分别引用了 MDN 上对 yield
和 next
返回值的描述
yield关键字实际返回一个IteratorResult对象,它有两个属性,value和done。value属性是对yield表达式求值的结果,而done是false,表示生成器函数尚未完全完成。
参考自yield
next() 方法返回一个包含属性 done 和 value 的对象。该方法也可以通过接受一个参数用以向生成器传值。
参考自Generator.prototype.next()
说实话,从两段描述上来看,yield
和 next
返回值都是 { value: any, done: Boolean }
所以写了下方的代码做验证
function *test() {
console.log(yield 1) // 2
}
const obj = test();
obj.next()
obj.next(2) // { value: undefined, done: true }
从结果上来看
-
yield
返回的是value
-
next
返回的是{ value: undefined, done: true }
Generator 执行流程
- 生成
Generator对象
,这时候Generator函数
内部不会执行
function *test() {
console.log('start')
let re = yield 1;
return re + 2;
}
const testObj = test(); // no console
const testObj2 = test(); // no console
如果生成多个 Generator对象
,则各个 Generator对象
保持着各自的执行阶段,互不影响
-
Generator对象
调用next
函数
从函数开头或上一个 yield 开始,执行到下一个 yield 或者 return
function *test() {
console.log('before first yield')
let first = yield 1;
console.log('after first yield')
let second = yield 2;
console.log('after second yield')
let third = yield 3;
console.log('after third yield')
return re + 2;
}
const testObj = test(); // no console
const firstResult = testObj.next(); // before first yield
const secondResult = testObj.next(); // after first yield
const thirdResult = testObj.next(); // after second yield
const fourthResult = testObj.next(); // after third yield
yield 委托迭代
*
和 yield
组合使用,可以将多个生成器连接在一起
function * anotherGenerator(i) {
yield i + 1;
yield i + 2;
yield i + 3;
}
function * generator(i) {
yield* anotherGenerator(i);
}
var gen = generator(1);
gen.next().value; // 2
gen.next().value; // 3
gen.next().value; // 4
类型判断
业界常见的判断 Generator 对象和函数的方法
如何判断 Generator 对象
function isGenerator(obj) {
return obj && typeof obj.next === 'function' && typeof obj.throw === 'function';
}
这里运用鸭子模型进行判断
如果对象中有 next 与 throw 两个方法,那么就认为这个对象是一个生成器对象
如何判断 Generator 函数
function isGeneratorFunction(obj){
var constructor = obj.constructor;
if(!constructor) return false;
if(
'GeneratorFunction' === constructor.name ||
'GeneratorFunction' === constructor.displayName
) return true;
return isGenerator(constructor.prototype);
}
利用函数的 constructor 构造器的名字来判断(name 与 displayName 为了处理兼容性)
这里递归调用 isGenerator 判断 constructor 的原型是因为有自定义迭代器的存在
总结
Generator
函数作为一种新的状态机制管理,让一段代码逻辑可以动态控制分段执行,有独到的作用和使用场景
但如果独立使用,操作繁琐,语义也不清晰
所以在 ES6 引入后,社区使用例如 co 库进行二次封装
在前端异步编程领域,开阔思路的作用大于实际应用,有点昙花一现的意思
为 Async/Await 做铺垫