Generator笔记

1 Generator 函数是一个状态机,封装了多个内部状态

2 执行 Generator 函数会返回一个遍历器对象;返回的遍历器对象,可以依次遍历 Generator 函数内部的每一个状态

3 特征

  • function关键字与函数名之间有一个星号
  • 函数体内部使用 yield 表达式,定义不同的内部状态(yield在英语里的意思就是“产出”)

4 调用 Generator 函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象,即遍历器对象(Iterator Object)

5 每次调用next方法,内部指针就从函 数头部或上一次停下来的地方开始执行,直到遇到下一个yield表达式(或return语句)为止

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

7 每次调用遍历器对象的next方法,就会返回一个有着value和done两个属性的对象。value属性表示当前的内部状态的值,是yield表达式后面那个表达式的值;done属性是一个布尔值,表示是否遍历结束

8 yield表达式如果用在另一个表达式之中,必须放在圆括号里面

9 yield表达式用作函数参数或放在赋值表达式的右边,可以不加括号

10 由于 Generator 函数就是遍历器生成函数,因此可以把 Generator 赋值给对象的Symbol.iterator属性,从而使得该对象具有 Iterator 接口

11 Generator 函数执行后,返回一个遍历器对象。该对象本身也具有Symbol.iterator属性,执行后返回自身。

function* gen(){
  // some code
}

var g = gen();

g[Symbol.iterator]() === g
// true

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

(1)遇到yield表达式,就暂停执行后面的操作,并将紧跟在yield后面的那个表达式的值,作为返回的对象的value属性值

(2)下一次调用next方法时,再继续往下执行,直到遇到下一个yield表达式。

(3)如果没有再遇到新的yield表达式,就一直运行到函数结束,直到return语句为止,并将return语句后面的表达式的值,作为返回的对象的value属性值。

(4)如果该函数没有return语句,则返回的对象的value属性值为undefined。

13 yield表达式本身没有返回值,或者说总是返回undefined。next方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值

function* f() {
  for(var i = 0; true; i++) {
    console.log('i', i);
    var reset = yield i;
    console.log('reset',reset);
    if(reset) { i = -1; }
    console.warn('i', i);
  }
}

var g = f();

console.log(g.next()) // { value: 0, done: false }
console.log(g.next()) // { value: 1, done: false }
console.log(g.next(true)) // { value: 0, done: false }

// 执行第一个next
// i 0
// {value: 0, done: false}  // 遇到yield,暂停后面的操作,紧跟在yield后面的那个表达式的值0,作为返回的对象的value属性值
// 执行第二个next,没有传入参数,reset为undefined
// reset undefined
// i 0
// i 1
// {value: 1, done: false}
// 执行第三个next,传入参数true,reset为true
// reset true
// i -1
// i 0
// {value: 0, done: false}

14  由于next方法的参数表示上一个yield表达式的返回值,所以在第一次使用next方法时,传递参数是无效的。V8 引擎直接忽略第一次使用next方法时的参数,只有从第二次使用next方法开始,参数才是有效的。从语义上讲,第一个next方法用来启动遍历器对象,所以不用带有参数。

15  一旦next方法的返回对象的done属性为truefor...of循环就会中止,且不包含该返回对象,

 

Generator.prototype.throw()

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

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

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

try {
  // 遍历器对象i连续抛出两个错误
  i.throw('a');   // 第一个错误被 Generator 函数体内的catch语句捕获。
  i.throw('b');   // i第二次抛出错误,由于 Generator 函数内部的catch语句已经执行过了,不会再捕捉到这个错误了,所以这个错误就被抛出了 Generator 函数体,被函数体外的catch语句捕获。
} catch (e) {
  console.log('外部捕获', e);
}
// 内部捕获 a
// 外部捕获 b

2、全局的throw命令只能被函数体外的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命令
  // 函数体外的catch语句块,捕获了抛出的a错误以后,就不会再继续try代码块里面剩余的语句了
  throw new Error('b');  // 不会执行
} catch (e) {
  console.log('外部捕获', e);
}
// 外部捕获 [Error: a]

3、如果 Generator 函数内部没有部署try...catch代码块,那么throw方法抛出的错误,将被外部try...catch代码块捕获。

4、如果 Generator 函数内部和外部,都没有部署try...catch代码块,那么程序将报错,直接中断执行。

5、throw方法抛出的错误要被内部捕获,前提是必须至少执行过一次next方法。因为第一次执行next方法,等同于启动执行 Generator 函数的内部代码,否则 Generator 函数还没有开始执行,这时throw方法抛错只可能抛出在函数外部。

6、throw方法被捕获以后,会附带执行下一条yield表达式。也就是说,会附带执行一次next方法。

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.throw方法被捕获以后,自动执行了一次next方法,所以会打印b
g.next() // c  -- 只要 Generator 函数内部部署了try...catch代码块,那么遍历器的throw方法抛出的错误,不影响下一次遍历。

 

Generator.prototype.return()

1、返回给定的值,并且终结遍历 Generator 函数

2、如果 Generator 函数内部有try...finally代码块,那么return方法会推迟到finally代码块执行完再执行

function* numbers () {
  yield 1;
  try {
    yield 2;
    yield 3; // 不执行
  } finally {
    yield 4;
    yield 5;
  }
  yield 6; // 不执行
}
var g = numbers();
g.next() // { value: 1, done: false }
g.next() // { value: 2, done: false }
g.return(7) // { value: 4, done: false }
g.next() // { value: 5, done: false }
g.next() // { value: 7, done: true }
g.next()  // { value: undefined, done: true }

 

yield* 表达式

如果在 Generator 函数内部,调用另一个 Generator 函数,默认情况下是没有效果的。

yield*表达式,用来在一个 Generator 函数里面执行另一个 Generator 函数。

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

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

// 等同于
function* bar() {
  yield 'x';
  for (let v of foo()) {
    yield v;
  }
  yield 'y';
}

for (let v of bar()){
  console.log(v);
}
// "x"
// "a"
// "b"
// "y"

如果yield*后面跟着一个数组,由于数组原生支持遍历器,因此就会遍历数组成员

function* gen(){
  yield* ["a", "b", "c"];
}
// yield命令后面如果不加星号,返回的是整个数组,加了星号就表示返回的是数组的遍历器对象
gen().next() // { value:"a", done:false }

yield*命令可以很方便地取出嵌套数组的所有成员。

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

 

作为对象属性的 Generator 函数

如果一个对象的属性是 Generator 函数,可以简写成下面的形式。

let obj = {
  * myGeneratorMethod() {
    ···
  }
};

上面代码中,myGeneratorMethod属性前面有一个星号,表示这个属性是一个 Generator 函数。

它的完整形式如下,与上面的写法是等价的。

let obj = {
  myGeneratorMethod: function* () {
    // ···
  }
};

 

Generator 函数的this

Generator 函数总是返回一个遍历器,ES6 规定这个遍历器是 Generator 函数的实例,也继承了 Generator 函数的prototype对象上的方法。

function* g() {
  this.a = 12;
}

g.prototype.hello = function () {
  return 'hi!';
};

let obj = g();

obj instanceof g // true
obj.hello() // 'hi!'
// 把g当作普通的构造函数,并不会生效,因为g返回的总是遍历器对象,而不是this对象。
obj.a  // undefined

Generator 函数也不能跟new命令一起用,会报错。

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值