ES6中Generator函数的详解

概念

    Generator 函数是 ES6 提供的一种异步编程解决方案。它既是一个生成器,也是一个状态机,内部拥有值及相关的状态,生成器返回一个迭代器 Iterator 对象,我们可以通过这个迭代器,手动地遍历相关的值、状态,保证正确的执行顺序。

特征

  1. function 关键字与函数名之间有一个星号(ES6 没有规定,function 关键字与函数名之间的星号,写在哪个位置)
  2. 函数体内部使用 yield 表达式,定义不同的内部状态
  3. 调用 Generator 函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象,也就是遍历器对象(Iterator Object)
function* gen() { 
  yield 1
  yield 2
  return 3
  yield 4
}

let g = gen(); 
console.log(g.next())   // {value: 1, done: false}
console.log(g.next())   // {value: 2, done: false}
console.log(g.next())   // {value: 3, done: true}
console.log(g.next())   // {value: undefined, done: true}

    每次调用 next() 方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个 yield 表达式(或 return 语句)为止。换言之,Generator 函数是分段执行的,yield 表达式是暂停执行的标记,而 next() 方法可以恢复执行。
    调用 Generator 函数,返回一个遍历器对象 (Iterator),代表 Generator 函数的内部指针。每次调用遍历器对象的 next() 方法,就会返回一个有着 value 和 done 两个属性的对象。value 属性表示当前的内部状态的值,是 yield 表达式后面那个表达式的值;done 属性是一个布尔值,表示是否遍历结束。
    Generator 函数的暂停执行的效果,意味着可以把异步操作写在 yield 语句里面,等到调用 next 方法时再往后执行。这实际上等同于不需要写回调函数了,因为异步操作的后续操作可以放在 yield 语句下面,反正要等到调用 next() 方法时再执行。所以,Generator 函数的一个重要实际意义就是用来处理异步操作,改写回调函数。

    注意:如果 return 语句后面还有 yield 表达式,那么后面的 yield 完全不生效

Iterator 的 return 的值不会被 for…of 循环到 , 也不会被扩展符遍历到

function* gen() { 
  yield 1
  yield 2
  return 3
}

let g = gen()

console.log([...g])     // [1, 2]

for(let foo of g) {
    console.log( foo )  // 1, 2
}

yield 表达式和 next() 方法

    由于 Generator 函数返回的遍历器对象,只有调用next方法才会遍历下一个内部状态,所以其实提供了一种可以暂停执行的函数。yield 表达式就是暂停标志。

遍历器对象的next方法的运行逻辑:

  1. 遇到 yield 表达式,就暂停执行后面的操作,并将紧跟在 yield 后面的那个表达式的值,作为返回的对象的 value 属性值
  2. 下一次调用 next() 方法时,再继续往下执行,直到遇到下一个 yield 表达式
  3. 如果没有再遇到新的 yield 表达式,就一直运行到函数结束,直到 return 语句为止,并将 return 语句后面的表达式的值,作为返回的对象的 value 属性值
  4. 如果该函数没有 return 语句,则返回的对象的 value 属性值为 undefined

注意:

  1. yield 表达式后面的表达式,只有当调用next方法、内部指针指向该语句时才会执行
  2. yield 语句只能用于 function* 的作用域,如果 function* 的内部还定义了其他的普通函数,则函数内部不允许使用 yield 语句
  3. yield 语句如果参与运算,必须用括号括起来
function* gen() {
  	yield  123 + 456;      // yield后面的表达式123 + 456,不会立即求值,只会在next方法将指针移到这一句时,才会求值
}

function* gen() {
	return function () {
	    yield 1   // SyntaxError
	}
}
    
function* gen() {
  console.log('Hello' + yield); // SyntaxError
  
  console.log('Hello' + (yield)); // OK
}

next() 方法的参数

  • yield 表达式本身没有返回值,或者说总是返回undefined
  • next() 方法可以带一个参数,该参数会改变上一个yield表达式的返回值
function* gen(x) {
  var y = 2 * (yield (x + 1))
  var z = yield (y / 3)
  return (x + y + z)
}

var a = gen(5);
a.next()    // {value: 6, done: false}
a.next()    // {value: NaN, done: false}
a.next()    // {value: NaN, done: true}

var b = gen(5);
b.next() 	// {value: 6, done: false}
b.next(12)  // {value: 8, done: false}
b.next(13)  // {value: 42, done: true}

    第一次调用 a 的 next() 方法时,返回 x+1 的值6。第二次运行a的 next() 方法的时候不带参数,导致 y 的值等于2 * undefined(即NaN),除以 3 以后还是NaN,因此返回对象的 value 属性也等于 NaN。第三次运行a的 next() 方法的时候不带参数,所以 z 等于undefined,返回对象的 value 属性等于5 + NaN + undefined,即NaN。
     第一次调用b的 next() 方法时,返回 x+1 的值6;第二次调用 next() 方法,将上一次 yield 表达式的值设为12,因此y等于24,返回y / 3的值8;第三次调用 next() 方法,将上一次 yield 表达式的值设为13,因此 z 等于13,这时 x 等于5,y 等于24,所以 return 语句的值等于42。

注意:

  1. 在第一次使用next方法时,传递参数是无效的
  2. V8 引擎直接忽略第一次使用 next() 方法时的参数,只有从第二次使用next方法开始,参数才是有效的。
  3. 从语义上讲,第一个next方法用来启动遍历器对象,所以不用带有参数。

Generator 的应用

1. 斐波那契数列

function* flb(){
    let [pre, cur] = [0, 1]
    for(;;) {
        [pre, cur] = [cur, pre + cur]
        yield cur
    }
}
for(let k of flb()){
    if( k > 1000 ) break
    console.log(k)
}

// 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987

2. Genarator 对数组部署 Iterator 接口

function* deploy(arr){
    var Index = 0
    while(Index < arr.length){
        yield arr[Index++]
    }
}

var arr = ['name', 'age', 'sex', 'hobby']

var val = deploy(arr)
console.log(val.next())  // {value: "name", done: false}
console.log(val.next())  // {value: "age", done: false}
console.log(val.next())  // {value: "sex", done: false}
console.log(val.next())  // {value: "hobby", done: false}
console.log(val.next())  // {value: undefined, done: true}

for(let k of deploy(arr)) {
	console.log(k)  // name age sex hobby
}

参考文献 http://es6.ruanyifeng.com/#docs/generator

猜你喜欢

转载自blog.csdn.net/fu983531588/article/details/89482179