Generator

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

yield

generator函数返回的是一个遍历器对象,只有通过next方法来逐一访问每一个状态,所以其内部提供了一个暂停执行函数的机制,yield就是这个机制。
(1)执行遍历器对象的next方法时,函数内部遇到未执行的yield时,会暂停后边的执行,同时会把当前遇到的yield后边的语句作为返回对象的value值。
(2)如果执行next方法过程中再也没有遇到yield,而是遇到return则函数执行完成,并把return后边表达式的值作为返回对象的value值,done属性变为true。
ps:yield表达式后面的表达式, 只有当调用next方法、内部指针指向该语句时才会执行,因此等于为 JavaScript 提供了手动的“惰性求值”(Lazy Evaluation)的语法功能 。

yield vs return

遍历器执行next方法时都可以将他们后边的表达式的值作为返回对象的value值,但是生成器函数中只能有一个return但是可以有多个yield。
Generator 函数可以返回一系列的值,因为可以有任意多个yield
如果不用yield,则生成器函数就变成可单纯的 暂缓执行函数

function*bar() {
console.log('暂缓执行');
}

const bar0 = bar();
// undefined
bar0.next();
// 暂缓执行

yield 表达式只能用在 Generator 函数里面,用在其他地方都会报错,yield表达式如果用在另一个表达式之中,必须放在圆括号里面。

var arr = [1, [[2, 3], 4], [5, 6]];

var flat = function* (a) {
  a.forEach(function (item) {
    if (typeof item !== 'number') {
      yield* flat(item);
    } else {
      yield item;
    }
  });
};

for (var f of flat(arr)){
  console.log(f);
}

function*foo() {
     //console.log('Hello' + yield); //报错
     //console.log('hello' + yield 'world'); //报错

    console.log('Hello' + (yield));
    console.log('hello' + (yield 'world'));
}

yield表达式用作函数参数或放在赋值表达式的右边,可以不加括号。ps: yield不能作为生成器函数的参数

function* demo() {
  foo(yield 'a', yield 'b'); // OK
  let input = yield; // OK
}
function foo() {}
function*b(yield 'okay') {} //报错
function*c((yield 'okay')) {} //报错

生成器函数与Iterator接口的关系

由于任何对象的Symbol.iterator方法都会返回一个遍历器对象,即该方法就是一个遍历器生成器。由于生成器函数本身就是一个遍历器生成器,所以可以把generator函数赋值给任何对象的Symbol.iterator方法属性,这样对象具有 Iterator 接口

const obj = {};
obj[Symbol.iterator] = function*() {
    yield 'a';
    yield 'b';
    return 'c';
};
 
const o = [...obj]; //[a, b]

ps:结果中并不会有c
Generator 函数执行后,返回一个遍历器对象。该对象本身也具有Symbol.iterator属性,执行后返回自身

next方法的参数

yield表达式本身没有返回值,或者说总是返回undefined。 next方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值。
实例1
如果next方法没有参数,每次运行到yield表达式,变量reset的值总是undefined。当next方法带一个参数true时,变量reset就被重置为这个参数(即true),因此i会等于-1,下一轮循环就会从-1开始递增。
实例2
由于 next方法的参数表示上一个yield表达式的返回值所以在第一次使用next方法时,传递参数是无效的。V8 引擎直接忽略第一次使用next方法时的参数,只有从第二次使用next方法开始,参数才是有效的。
再看一个通过next方法的参数,向 Generator 函数内部输入值的例子
实例3

for...of 循环

for...of循环可以自动遍历 Generator 函数时生成的Iterator对象,且此时不再需要调用next方法。
实例3
这里需要注意,一旦next方法的返回对象的done属性为true,for...of循环就会中止,且不包含该返回对象所以上面代码的return语句返回的6,不包括在for...of循环之中。
利用for...of循环,可以写出遍历任意对象(object)的方法。原生的 JavaScript 对象没有遍历接口,无法使用for...of循环,通过 Generator 函数为它加上这个接口,就可以用了。
实例4
加上遍历器接口的另一种写法是,将 Generator 函数加到对象的Symbol.iterator属性上面。
除了for...of循环以外,扩展运算符(...)、解构赋值和Array.from方法内部调用的,都是遍历器接口。这意味着,它们都可以将 Generator 函数返回的 Iterator 对象,作为参数。

Generator.prototype.throw()

Generator 函数返回的遍历器对象,都有一个 throw方法,可以在函数体外抛出错误,然后在 Generator 函数体内捕获。
throw方法被捕获以后,会附带执行下一条yield表达式。也就是说,会附带执行一次next方法。

g.throw方法被捕获以后,自动执行了一次next方法,所以会打印b。另外,也可以看到,只要 Generator 函数内部部署了try...catch代码块,那么遍历器的throw方法抛出的错误,不影响下一次遍历

上面代码中,throw命令抛出的错误只要有catch捕获,不会影响到遍历器的状态,所以两次执行next方法,都进行了正确的操作。

一旦 Generator 执行过程中抛出错误,且没有被内部捕获,就不会再执行下去了。如果此后还调用next方法,将返回一个value属性等于undefined、done属性等于true的对象,即 JavaScript 引擎认为这个 Generator 已经运行结束了。

Generator.prototype.return()

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

yield*表达式

在Generator 函数内部调用另一个 Generator 函数,默认情况下是没有效果的
yield*表达式,用来在一个 Generator 函数里面执行另一个 Generator 函数

yield*后面的 Generator 函数(没有return语句时),等__同于在 Generator 函数内部,部署一个for...of循环__。

上面代码中,yield命令后面如果不加星号,返回的是整个数组,加了星号就表示返回的是数组的遍历器对象。
实际上,任何数据结构 只要有 Iterator 接口,就可以被yield*遍历。

posted @ 2017-11-13 00:26  joe_ice  阅读(187)  评论(0)    收藏  举报