1. 简介
Generator 函数是 ES6 提供的一种异步编程的解决方案;
执行 Generator 函数会返回一个遍历器对象。即,Generator 函数除了状态机(封装了多个内部状态),还是一个遍历器对象生成函数。返回的遍历器对象,可以依次遍历Generator 函数内部的每一个状态;
形式上,Generator 函数是一个普通函数,有两个特征:
fucntion
关键字与函数名之间有一个星号*
;- 函数内部使用
yield
表达式,定义不同的内部状态;
function* hellWorld(){
yield 'hello'
yield 'world'
return 'ending'
}
var hw = helloWorld() // 函数被调用并不执行,返回一个指向内部状态的指针对象(Iterator Object)
hw.next() // {value: 'hello', done: false}
hw.next() // {value: 'world', done: false}
hw.next() // {value: 'ending', done: true}
hw.next() // {value: undefined, done: true}
Generator 函数是分段执行的,yield
表达式是暂停执行的标记,而 next
方法可以恢复执行;
// ES6 没有规定,function 关键字与函数名之间的星号位置,下面的写法都能通过:
fucntion*foo(x, y){...}
fucntion *foo(x, y){...}
fucntion* foo(x, y){...} // 推荐写法
fucntion * foo(x, y){...}
yield 表达式
遍历器对象的 next 方法的运行逻辑如下:
- 遇到
yield
表达式,暂停执行后面的操作,并将紧跟在yield
后面那个表达式的值,作为返回的对象的value
属性值; - 下一次调用
next
方法时,再继续往下执行,直到遇到下一个yield
表达式; - 如果没有再遇到新的
yield
表达式,就一直运行到函数结束,直到return
语句为止,并将return
语句后面的表达式的值,作为返回的对的value
属性值; - 如果该函数没有
return
返回语句,则返回的对象的value
属性值为undefined
;
注意:
- Generator 函数可以不用 yield 表达式,变成一个单纯的暂缓执行函数;
- yield 表达式只能用在 Generator 函数里面,用在其他地方会报错;
- yield 表达式如果用在另外一个表达式中,必须放在圆括号里面;
function* demo(){
console.log('Hello' + yield) // SyntaxError
console.log('Hello' + yield 123) // SyntaxError
console.log('Hello' + (yield)) // OK
console.log('Hello' + (yield 123)) // OK
}
- yield 表达式用作函数或放在赋值表达式的右边,可以不加括号;
function* demo(){
foo(yield 'a', yield 'b') // OK
let input = yield // OK
}
与 Iterator 接口的关系
任意一个对象的 Symbol.iterator
方法,等于该对象的遍历器生成函数,调用该函数会返回该对象的一个遍历器对象;
- 由于 Generator 函数就是遍历器生成函数,因此可以把 Generator 赋值给对象的 Symbol.iterator 属性,从而使得该对象具有 Iterator 接口:
var myIterable = {}
// Generator 函数赋值给 Symbol.iterator 属性,从而使得 myIterable 对象具有了 Iterator 接口;
// 可以被 ... 运算符遍历;
myIterable[Symbol.iterator] = fucntion* (){
yield 1
yield 2
yield 3
}
[...myIterator] // [1, 2, 3]
- Generator 函数执行后,返回一个遍历器对象。该对象本身也具有
Symbol.iterator
属性,执行后返回自身:
function* gen(){}
// gen 是一个 Generator 函数,调用它会生成一个遍历器对象 g
var g = gen()
// g 的 Symbol.iterator 属性,也是一个遍历器生成对象,执行后返回 g 自己
g[Symbol.iterator]() === g // true
2. next 方法的参数
yield 表达式本身没有返回值,或者说总是返回 undefined 。 next 方法可以带一个参数,当作上一个 yield 表达式的返回值:
// 定义了一个可以无限运行的 Generator 函数 f
function* f(){
for(var i=0; true; i++){
var reset = yield i
// next方法没有传参,每次运行到 yield 表达式,变量 reset 的值总是 undefined ;
// 当 next 方法传入参数 true ,变量 reset 就被重置为 true ;
// 因此 i 会等于 -1 ,下个循环就会从-1 开始递增;
if(reset){ i = -1 }
}
}
var g = f()
g.next() // {value: 0, done: false}
g.next() // {value: 1, done: false}
g.next(true) // {value: 0, done: false}
Generator 函数从暂停状态到恢复运行,它的上下文状态(context)是不变的。通过 next 方法的参数,就有办法在Generator 函数开始运行后,继续向函数体内部注入值。即,在Generator 函数运行的不同阶段,从外部向内部注入不同的值,从而调整函数行为:
function* foo(x){
var y = 2 * (yield (x + 1))
var z = yield(y / 3)
return (x + y + z)
}
var a = foo(5)
a.next() // {value: 6, done: false}
a.next() // {value: NaN, done: false}
// 第二次运行 next 方法时不带参数,y = 2*undefined(即 NaN)
// NaN/3 是 NaN ,返回对象的 value 属性是 NaN ;
a.next() // {value: NaN, done: true}
// 第三次运行 next 方法时不带参数,z = undefined
// x + y + z (5 + NaN + undefined = NaN)
var b = foo(5)
b.next() // {value: 6, done: false}
b.next(12) // {value: 8, done: false}
// 第二次运行 next 方法时传入参数 12 ,y = 2*12(即 24)
// 24/3 是 8 ,返回对象的 value 属性是 8 ;
b.next(13) // {value: 42, done: true}
// 第三次运行 next 方法时传入参数 13 ,z = 13
// x + y + z (5 + 24 + 13 = 42),返回对象的 value 属性是 42 ;
注意:由于 next 方法的参数表示上一个 yield 表达式的返回值,在第一次调用 next 方法时,传递的参数无效;
function* dataConsumer(){
console.log('Started')
console.log(`1. ${ yield }`)
console.log(`2. ${ yield }`)
return 'result'
}
let genObj = dataConsumer()
genObj.next()
// Started
// {value: undefined, done: false}
genObj.next('a')
// 1. a
// {value: undefined, done: false}
genObj.next('b')
// 2. b
// {value: 'result', done: true}
3. for...of 循环
for...of 循环可以自动遍历 Generator 函数运行时生成的 Iterator 对象,且此时不再需要调用 next 方法:
function* foo(){
yield 1
yield 2
yield 3
yield 4
yield 5
return 6
}
for(let v of foo()){
console.log(v)
}
// 1,2,3,4,5
一旦 next 方法的返回对象的 done 属性为 true ,for...of 循环就会中止,且不包含该返回对象,所以上面代码的 return 语句返回的 6,不包括在 for...of 循环之中。
实现斐波那契数列的🌰 :
// 斐波那契数列 0,1,1,2,3,5,8,13,21...
function* fbnq(){
let [i, j] = [0, 1]
for(;;){
yield j
[i, j] = [j, i+j]
}
}
for(let n of fbnq()){
if(n > 100) break;
console.log(n)
}
通过 Generator 函数为原生js对象加上 Iterator 接口:
fucntion* objectEntries(obj){
let propKeys = Reflect.ownKeys(obj)
for(let key of propKeys){
yield [key, obj[key]]
}
}
let obj = {first: 'first', last: 'last'}
// === 第一种写法 ===
for(let [key, value] of objectEntries(obj)){
console.log(`${key} : ${value}`)
}
// first : first
// last : last
// === 第二种写法 ===
// 将 Generator 函数加到对象的 Symbol.Iterator 属性上
obj[Symbol.Iterator] = objectEntries
for(let [key, value] of obj){
console.log(`${key} : ${value}`)
}
// first : first
// last : last
除了 for...of 循环外,扩展运算符(...)、解构赋值和Array.form 方法内部调用,都是遍历器接口。它们都可以将 Generator 函数返回的 Iterator 对象,作为参数:
function* numbers(){
yield 1
yield 2
return 3
yield 4
}
// 扩展运算符
[...numbers()] // [1, 2]
// Array.form 方法
Array.form(numbers()) // [1, 2]
// 解构赋值
let [x, y] = numbers()
x // 1
y // 2
7. yield* 表达式
如果在Generator 函数内部,调用另一个 Generator 函数,需要在前者的函数体内部,自己手动完成遍历:
fucntion* foo(){
yield 'a'
yield 'b'
}
function* bar(){
yield 'x'
for(let i of foo()){ // 手动遍历foo()
console.log(i)
}
yield 'y'
}
for(let v of bar()){
console.log(v)
}
// x
// a
// b
// y
ES6 提供 yield* 表达式,用来在一个 Generator 函数里面执行另一个 Generator 函数:
fucntion* foo(){
yield 'a'
yield 'b'
}
function* bar(){
yield 'x'
yield* foo()
yield 'y'
}
// 等同于
function* bar(){
yield 'x'
for(let v of foo()){
yield v
}
yield 'y'
}
// 等同于
function* bar(){
yield 'x'
yield 'a'
yield 'b'
yield 'y'
}
for(let v of var()){
console.loh(v)
}
// 'x'
// 'a'
// 'b'
// 'y'
使用 yield 表达式,返回一个遍历器对象;使用 yield* 表达式,返回遍历器对象的内部值:
function* inner(){
yield 'hello'
}
function* outer1(){
yield 'open'
yield inner()
yield 'close'
}
function* outer2(){
yield 'open'
yield* inner()
yield 'close'
}
var gen1 = outer1()
gen1.next().value // 'open'
gen1.next().value // 返回一个遍历器对象
gen1.next().value // 'close'
var gen2 = outer2()
gen2.next().value // 'open'
gen2.next().value // 'hello'
gen2.next().value // 'close'
使用 yield* 表达式遍历完全二叉树:
// 下面是二叉树的构造函数
// 三个参数分别是左数、当前节点、右树
fucntion Tree(left, label, right){
this.left = left
this.label = label
this.right = right
}
// 下面是中序(inorder)遍历函数
// 由于返回的是一个遍历器,所以用 Generator 函数
// 函数体内采用递归算法,所以左树和右树要用 yield* 遍历
function* inorder(t){
if(t){
yield* inorder(t.left)
yield t.label
yield* inorder(t.right)
}
}
// 生成二叉树
function make(arr){
// 判断是否为叶节点
if(arr.length == 1) return new Tree(nill, arr[0],null)
return new Tree(make(arr[0]), arr[1], make(arr[2]))
}
let tree = make([[['a'], 'b', ['c']], 'd', [['e'], 'f', ['g']]])
var result = []
for(let node of inorder(tree)){
result.push(node)
}
result // ['a','b','c','d','e','f','g']
参考链接: ECMAScript 6 入门