es6之Generator函数

什么是Generator函数

Generator函数,能够理解为一种状态机,封装了多个内部状态。是一个遍历器生成器,返回遍历器对象(即Generator 函数的内部指针)。bash

Generator函数和普通函数区别:函数

  • 一、函数function和函数名称中间有个星号
  • 二、调用 Generator 函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象
  • 三、函数内部使用yield表达式定义内部状态,普通函数不能使用yield表达式。能够不用yield表达式,这时就变成了一个单纯的暂缓执行函数
  • 四、必须调用遍历器对象的next方法,使得指针移向下一个状态
function* foo(){
    console.log('hello');
}
var bar = foo();

bar.next();
bar.next();

//'hello'
复制代码

一个普通函数中使用yield表达式,结果产生一个句法错误ui

(function(){
    yield 1;
})(); // 报错

(function*(){
    yield 1;
})();//ok
复制代码
function* foo() {
    console.log('start');
    yield 1;
    console.log('middle');
    yield 2;
    console.log('end');
    return 3;
}
var bar = foo();
console.log(bar.next());
console.log(bar.next());
console.log(bar.next());
console.log(bar.next());

//start
//{ value: 1, done: false }
//middle
//{ value: 2, done: false }
//end
//{ value: 3, done: true }
//{ value: undefined, done: true }
复制代码

Generator函数的运行流程:this

  • 第 1 次调用next,输出start,遇到第一个yield,返回{ value: 1, done: false }
  • 第 2 次调用next,从上一次yield停下来的地方,直到下一个yield表达式。输出middle,遇到第二个yield,返回{ value: 2, done: false }
  • 第 3 次调用next,从上一次yield停下来的地方,遇到return。输出end,返回{ value: 3, done: true }
  • 第 4 次调用next,返回{ value: 3, done: true }。若是还有next调用都返回这个结果

yield 和return

区别在于每次遇到yield,函数暂停执行,下一次再从该位置继续向后执行,而return语句不具有位置记忆的功能。spa

yield表达式自己没有返回值,或者说老是返回undefined。yield表达式若是用在另外一个表达式之中,必须放在圆括号里面指针

function* foo() {
    //在另外一个表达式中,yield表达式必须加上圆括号
    console.log( 'hello' +  (yield 'world'));
}
var bar = foo();
console.log(bar.next());
console.log(bar.next());

//{ value: 'world', done: false }
//helloundefined
//{ value: undefined, done: true }
复制代码

yield表达式用做函数参数或放在赋值表达式的右边,能够不加括号code

function fun(a){
    console.log('a:'+ a);
}
function* foo() {
    fun(yield 'hello', yield 'world'); //函数参数
    let value = yield; //表达式右侧
}
var bar = foo();
console.log(bar.next()); 
console.log(bar.next());
console.log(bar.next());
console.log(bar.next());

//{ value: 'hello', done: false }
//{ value: 'world', done: false }
//a:undefined
//{ value: undefined, done: false }
//{ value: undefined, done: true }
复制代码

与Iterator接口

任意一个对象的Symbol.iterator方法等于该对象的遍历器生成函数,调用该函数会返回该对象的一个遍历器对象。对象

Generator 函数执行后,返回一个遍历器对象。该对象自己也具备Symbol.iterator属性,执行后返回自身。接口

function* foo() {
    yield 1;
}
var bar = foo();
bar === bar[Symbol.iterator]() //true
复制代码

Generator 函数赋值给Symbol.iterator属性,从而使得myIterable对象具备了 Iterator 接口,能够被...运算符遍历了generator

var myIterable = {};
myIterable[Symbol.iterator] = function* foo() {
    yield 1;
    yield 2;
};
[...myIterable] //[1, 2]
复制代码

next方法参数

next方法能够带一个参数,该参数就会被看成上一个yield表达式的返回值

function* foo() {
    let a = 10;
    let b = yield 10 + a;
    yield b + 10;
}

var bar = foo();

bar.next();    //{ value: 20, done: false }
bar.next(20); //{ value: 30, done: false }
bar.next();   //{ value: undefined, done: true }
复制代码

第二个next参数20做为,第一次yield的返回值,全部b = 20 ,因此第二个yield表达式为值为30

Generator 函数从暂停状态到恢复运行,它的上下文状态(context)是不变的。经过next方法的参数,就有办法在 Generator 函数开始运行以后,继续向函数体内部注入值。

for...of循环

for...of循环遍历Iterator对象,再也不须要调用next方法

function* foo() {
  yield 1;
  yield 2;
  return 3;
}

for (let v of foo()) {
  console.log(v);
}//1 2
复制代码

须要注意,一旦next方法的返回对象的done属性为true,for...of循环就会停止,且不包含该返回对象,因此上面代码的return语句返回的3,不包括在for...of循环之中

for...of循环、扩展运算符(...)、解构赋值和Array.from方法内部调用的,都是遍历器接口。这意味着,它们均可以将 Generator 函数返回的 Iterator 对象,做为参数。

使对象类型数据也能使用for...of 第一种

function* foo() {
    let keys = Object.keys(this);

    for (let key of keys) {
        yield [key, this[key]];
    }
}
var bar = { name: 'li', age: 20};
bar[Symbol.iterator] = foo;

for (let value of bar) {
    console.log(value);
}
//[ 'name', 'li' ]
//[ 'age', 20 ]
复制代码

第二种实现

function* foo(obj) {
    let keys = Object.keys(obj);
    for(let key of keys){
        yield [key, obj[key]];
    }
}
var bar = {name: 'li', age: 20, sex: 'man'};

for (let value of foo(bar)){
    console.log(value);
}
复制代码

throw方法

generator.throw()抛出错误先看内部又没有捕获,若是内部没有捕获,就外部捕获。若是都没有捕获,那么程序将报错,直接中断执行。

注意区分throw方法和全局的throw

function* foo() {
    try{
        yield 1;
    } catch(error){
        console.log('error');
    }
    yield 2;
}

var bar = foo();

bar.next(); //{ value: 1, done: false }
bar.throw();//{ value: 2, done: false }

throw new Error('xxx');
复制代码
  • bar.throw方法抛出的错误要被内部捕获,前提是必须至少执行过一次next方法
  • bar.throw方法被捕获之后,会附带执行下一条yield表达式,也就是说,会附带执行一次next方法
  • throw命令与g.throw方法是无关的,二者互不影响
  • 一旦 Generator 执行过程当中抛出错误,且没有被内部捕获,就不会再执行下去了。若是此后还调用next方法,返回{ value: undefined, done: true }

return方法

若是return方法传有参数就当作返回对象的value值。日后再调用next方法都返回{ value: undefined, done: true }

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

var g = gen();

g.next(); //{ value: 1, done: false }
g.return('foo'); //{ value: 'foo', done: true }
g.next(); //{ value: undefined, done: true }
复制代码

若是Generator中有try...finaly,调用return()方法后,就开始执行finally代码块,不执行try里面剩下的代码了,而后等到finally代码块执行完,再返回return()方法指定的返回

function* gen() {
    try {
        yield 1;
        yield 2;
    } finally {
        yield 3;
        yield 4;
    }
}

var g = gen();

g.next();//{ value: 1, done: false }
g.return('foo');//{ value: 3, done: false }
g.next(); //{ value: 4, done: false }
g.next(); //{ value: 'foo', done: true }
g.next(); //{ value: undefined, done: true }
复制代码

yield*表达式

在 Generator 函数内部,调用另外一个 Generator 函数。

function* foo() {
    yield 1;
    yield* bar();
    yield 2;
}
function* bar() {
    yield 3;
    yield 4;
}

var g = foo();

[...g];//[ 1, 3, 4, 2 ]
复制代码
相关文章
相关标签/搜索