【Javascript】生成器

生成器

Generator 函数是 ES6 提供的一种异步编程解决方案
调用Generator 函数后,该函数并不执行,返一个指向内部状态的指针对象,即遍历器对象。

Generator 函数是分段执行的,yield表达式是暂停执行的标记,而next方法可以恢复执行。

yield表达式

  1. 遇到yield表达式,就暂停执行后面的操作,并将紧跟在yield后面的那个表达式的值,作为返回的对象的value属性值。
  2. 如果没有再遇到新的yield表达式,就一直运行到函数结束,直到return语句为止,并将return语句后面的表达式的值,作为返回的对象的value属性值。
  3. 如果该函数没有return语句,则返回的对象的value属性值为undefined

Generator 函数可以不用yield表达式,这时就变成了一个单纯的暂缓执行函数。只有调用next方法时,函数才会执行

function* f() {
  console.log('执行了!')
}

var generator = f();

setTimeout(function () {
  generator.next()
}, 2000);

参数

next方法的参数是上一个yield表达式的返回值,所以在第一次使用next方法时,传递参数是无效的

function* foo(x) {
  var y = 2 * (yield (x + 1));
  var z = yield (y / 3);
  return (x + y + z);
}

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

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

第一次调用bnext方法时,返回x+1的值6;第二次调用next方法,将上一次yield表达式的值设为12,因此y等于24,返回y / 3的值8;第三次调用next方法,将上一次yield表达式的值设为13,因此z等于13,这时x等于5,y等于24,所以return语句的值等于42

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属性为truefor...of循环就会中止,且不包含该返回对象,所以上面代码的return语句返回的6,不包括在for...of循环之中

throw()

throw方法,可以在函数体外抛出错误,然后在 Generator 函数体内捕获

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

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

接受参数

var g = function* () {
  while (true) {
    try {
      yield;
    } catch (e) {
      if (e != 'a') throw e;
      console.log('内部捕获', e);
    }
  }
};

var i = g();
i.next();

try {
  throw new Error('a');
  throw new Error('b');
} catch (e) {
  console.log('外部捕获', e);
}
// 外部捕获 [Error: a]

之所以只捕获了a,是因为函数体外的catch语句块,捕获了抛出的a错误以后,就不会再继续try代码块里面剩余的语句了

特点

throw方法被捕获以后,会附带执行一次next方法.只要 Generator 函数内部部署了try...catch代码块,那么遍历器的throw方法抛出的错误,不影响下一次遍历。

var gen = function* gen(){
  try {
    yield console.log('a');
  } catch (e) {
    // ...
  }
  yield console.log('b');
  yield console.log('c');
}

var g = gen();
g.next() // a
g.throw() // b
g.next() // c

next() throw() return()的共同点

它们的作用都是让 Generator 函数恢复执行,并且使用不同的语句替换 yield 表达式

  1. next()是替换成一个值(如果没有参数,则是undefined
  2. throw()是替换成一个throw语句
  3. return()是替换成一个return语句

yield*

用来在一个 Generator 函数里面执行另一个 Generator 函数

function* foo() {
  yield 'a';
  yield 'b';
}

function* bar() {
  yield 'x';
  yield* foo();
  yield 'y';
}

// 等同于
function* bar() {
  yield 'x';
  yield 'a';
  yield 'b';
  yield 'y';
}

扩展

只要数据结构中有 Iterator 接口,就可以被yield*遍历。如数组:

function* gen(){
  yield* ["a", "b", "c"];
}

gen().next() 
// { value:"a", done:false }
  • 取出嵌套数组的所有成员
function* iterTree(tree) {
  if (Array.isArray(tree)) {
    for(let i=0; i < tree.length; i++) {
      yield* iterTree(tree[i]);
    }
  } else {
    yield tree;
  }
}

const tree = [ 'a', ['b', 'c'], ['d', 'e'] ];

for(let x of iterTree(tree)) {
  console.log(x);
}
// a b c d e

应用

异步操作的同步表达

function one(){
    setTimeout(()=>{
        console.log(111);
        iterator.next();
    },1000)
}

function two(){
    setTimeout(()=>{
        console.log(222);
        iterator.next();
    },2000)
}

function * gen(){
    yield one();
    yield two();
}

//调用生成器函数
let iterator = gen();
iterator.next();

部署 Iterator 接口

利用 Generator 函数,可以在任意对象上部署 Iterator 接口

const obj = { a: 1, b: 2, c: 3 }

function* entries(obj) {
  for (let key of Object.keys(obj)) {
    yield [key, obj[key]];
  }
}

for (let [key, value] of entries(obj)) {
  console.log(key, '->', value);
}
// a -> 1
// b -> 2
// c -> 3
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值