JavaScript中的Generator生成器的基本用法

ES6入门-阮一峰:Generator 函数

1. 介绍

Generator 生成器是 ES6 提供的一种异步编程解决方案。是一个极为灵活的结构,拥有在函数块中暂停和恢复代码执行的能力。

执行 Generator 生成器 会返回一个 ( Iterator )迭代器对象,也就是说:Generator 函数除了状态机,还是一个迭代器对象生成函数,返回的迭代器对象,可以依次遍历 Generator 函数内部的每一个状态。

形式上,Generator 函数就是一个普通函数,但是又两个特征:

  1. function 关键字 和 函数名中间有一个 * 号
  2. 函数体内部使用 yield 表达式,定义不同的内部状态
function* generatorFn() {
    yield 'hello'
    yield 'world'
    return 'ending'
}

var hw = generatorFn()

/****************
	上面代码定义了一个 Generator 函数 ( generatorFn ),  
	它内部有两个 yield 表达式 ( 'hello' 和 'world' ), 
	即该函数有三个状态: hello、world 和 return 语句 ( 结束执行 )。
*****************/

2. next() 方法

Generator 函数的调用方法与普通函数一样,函数名后加上小括号即可, 不同的是,调用 Generator 函数后,该函数并不执行,返回的也不是函数运行结果, 而是一个执行内部状态的指针对象,也就是 Iterator 遍历器对象 。

下一步,必须调用 Iterator 遍历器对象 的 next 方法,使指针移向下一个状态。也就是说,每次调用 next 方法,内部指针就会从函数头部或者上一次停下的位置开始执行,直到遇到下一个 yield 表达式 ( 或 return 语句 ) 为止。

换而言之,就是说 yield 语句是暂停执行的标记,而 next 方法则为恢复执行。

function* generatorFn() {
    yield 'hello'
    yield 'world'
    return 'ending'
}

var hw = generatorFn()

hw.next()
hw.next()
hw.next()
hw.next()
// { value: 'hello', done: false } 
// { value: 'world', done: false }
// { value: 'ending', done: true }
// { value: undefined, done: true }
function* gen() {
    console.log(1)
    yield '嘿嘿'

    console.log(2)
    yield '呵呵'

    console.log(3)
    yield '哈哈'

    console.log(4)
}

let test = gen()
test.next()
test.next()
test.next()
test.next()
/*
1
2
3
4
*/

yield 表达式本身没有返回值,或者说总是返回 undefined。next() 方法可以携带一个参数,这个参数会被当成上一个 yield 表达式的返回值。

注意:因为每个 next() 方法传入的值都是上一个 yield 表达式的返回值,所以第一个 next() 传入的值是无效的,V8 引擎直接忽略第一次使用 next() 时的参数,只有第二次使用才会生效。

function* gen(arg) {
    console.log(arg);

    let one = yield 111;
    console.log(one);

    let two = yield 222
    console.log(two);

    let three = yield 333
    console.log(three);
}

// 执行获取迭代器对象
let test = gen('AAA')

test.next('EE'); // 第一个 next 的参数会被忽略

// next 方法可以传入实参
test.next('BBB');
test.next('CCC');
test.next('DDD');

// AAA
// BBB
// CCC
// DDD

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
// 之所以没有输出 6,是因为一旦 next 返回对象的 done 属性为 true, for...of 循环就会自动终止,且不包含该返回对象。

3.1 实现斐波那契数列

第一次执行,先定义了 prev (上一个), curr (现在的), 并且赋值为 0、1,然后进入死循环返回 curr。

返回后输出,回到函数,将 curr 和 prev 的值相加后赋值给 curr,并且把 curr 的值交给 prev ,这两个需要使用解构赋值一起执行,否则需要一个中转值先承载某一个的值。

function* fibonacci() {
  let [prev, curr] = [0, 1]
  for (;;) {
    yield curr
      ;[prev, curr] = [curr, prev + curr]
  }
}

for (let n of fibonacci()) {
  if (n > 1000) break
  console.log(n)
}

// 不使用解构赋值语法

function* fibonacci() {
  let prev = 0
  let curr = 1
  let temp = null

  for (;;) {
    yield curr;
    temp = prev
    prev = curr
    curr = temp + curr
  }
}

for (let n of fibonacci()) {
  if (n > 1000) break
  console.log(n)
}

4. 错误捕获 throw()

Generator 函数返回的遍历器对象,都有一个 throw 方法,可以在函数体外抛出错误,然后在 Generator 函数体内捕获。

如果没有在 Generator 函数体内设置 try... catch 代码块,那么抛出的错误会直接被外部的 catch 代码块捕获,如果 Generator 函数体内部和外部都没有部署 try...catch 代码块,那么程序将报错,直接中断执行。

throw 抛出的错误如果想被内部捕获,最少需要执行一次 next 方法。如果没有执行就会直接被外部的 catch 代码块捕获。

var g = function* () {
  try {
    yield
  } catch (e) {
    console.log('内部捕获', e)
  }
}

var i = g()
i.next()

try {
  i.throw('a')
  i.throw('b')
} catch (e) {
  console.log('外部捕获', e)
}

// 内部捕获 a
// 外部捕获 b

上面代码中,遍历器对象连续抛出两个错误。其中,第一个错误被 Generator 函数内部的 catch 语句捕获。第二个错误由于 Generator 函数内部的 catch 语句已经执行了,不会再执行,所以被外部的 catch 语句捕获了。

5. 停止 Generator 函数

Generator 函数返回的遍历器对象,还有一个 return() 方法,可以返回给指定的值,并且可以终结遍历 Generator 函数。

调用 return() 方法后,返回值的 value 属性就是 return() 方法接收的参数,如果不带参数,那么默认返回的 value 属性就是 undefined。

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

var g = gen()

console.log(g.next())
console.log(g.return('foo'))
// console.log(g.return())
console.log(g.next())

// {value: 1, done: false}
// {value: 'foo', done: true}
// {value: undefined, done: true}  // 这是注释的那行的输出
// {value: undefined, done: true}
posted @ 2022-10-22 01:20  如是。  阅读(182)  评论(0编辑  收藏  举报