参考 泉源《ecmascript6 入门》generator部份
熟悉generator函数
情势上,generator
函数有两个特征:一是function
关键字与函数名之间有一个*。二是函数体内运用yield
语句,以下代码。(yield在英语中意义就是 产出)
function* helloWorld(){
yield ‘hello’;
yield ‘world’;
return ‘ending’;
}
var hw=helloWorld();
挪用实行,挪用generator
函数和挪用一般函数的情势一样,没有区分,比方上面helloWorld()
。
然则内部的实行与一般函数是完整差别,挪用generator函数以后,该函数并不实行,返回的也不是函数运转结果,而是一个指向内部状况的指针对象,也就是遍历器对象。也就是说generator函数照样一个遍历器对象天生函数。返回的遍历器对象能够顺次遍历generator函数内部的每个状况。
它是怎样遍历的呢?。遍历器对象每次挪用next要领,内部指针就从函数头部或许上一次停下来的的处所最先实行,碰到yield语句停息并返回一个对象,下一次挪用next,碰到下一个yield停息并返回一个对象(对象具有value,和done属性)。value的值就是yield语句的值,done属性示意遍历是不是终了(false没有终了,true终了)。
上面示例代码用挪用4次next:
第一次挪用next,generator
函数最先实行,碰到第一个yield停息,而且返回一个对象,value =hello,done=false
示意还遍历还没有终了。
第二次挪用next
,从上次停息的位置最先实行,碰到下一个yield停息,并返回一个对象。。
第三次挪用next
,返回value为return的值,done为true示意遍历终了。
第四次挪用next
,generator函数已运转终了,返回value
为undefined,done
为true。
yield和return;
yield
语句与return
语句既有相似之处 ,也有区分。相似之处在于都能够返回紧跟在其后边的表达式的值。区分在于每次碰到yield,函数停息,下一次在从该位置向后实行,而return语句没有此位置影象功用,一个函数内里只能实行一次,而yield正由于能够有多个,能够返回多个值,所以generator函数能够返回一系列的值,这也就是它称号的来源(generator英语意义为天生器)
与Iterator接口的关联
恣意一个对象的iterator接口都是布置在了Symbol.iterator属性,由于generator函数就是遍历器天生函数,所以能够直接把它赋值给Symbol.iterator,从而使的该对象具有Iterator接口。
示例:
var myIterable = {};
myIterable[Symbol.iterator] = function* () {
yield 1;
yield 2;
yield 3;
};
[...myIterable] // [1, 2, 3]
申明:代码中generator
函数赋给了myIterable
对象的Symbol.iterator
属性,使的该对象具有iterator
接口,能够 被(…
)运算符遍历。为何是如许?(…)三个点这里叫做扩大运算符,它的实行是挪用了遍历器要领(它能够将一个数组转为用逗号支解的序列,能够用于函数挪用传参),这里就是generator函数,然后返回一个遍历器对象,然后反复挪用它的next要领。实在不只要扩大运算符,for..of
轮回的实行也是挪用的iterator接口要领,也就是说只要布置了iterator接口的数据鸠合才能够运用for…of,扩大运算符遍历。
Generator.prototype.throw()
Generator
函数返回的遍历器对象,都有一个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
上面代码遍历器对象一连抛出两个毛病,第一个被generator
函数体内的catch
捕捉。第二个由于generator函数体内的catch已实行过了,所以被表面的catch捕捉。假如generator函数体内没有try...catch...
语句,那末就会被表面的catch语句捕捉。假如都没有try…catch…,那末顺序报错。
5.Generator.prototype.return()
yield*语句
假如在 Generator
函数内部,挪用另一个 Generator 函数,默许情况下是没有结果的。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';
}
// 等同于
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
敕令背面跟的是一个遍历器对象,需要在yield
敕令背面加上星号,表明它返回的是一个遍历器对象。这被称为yield*
语句。
let delegatedIterator = (function* () {
yield 'Hello!';
yield 'Bye!';
}());
let delegatingIterator = (function* () {
yield 'Greetings!';
yield* delegatedIterator;
yield 'Ok, bye.';
}());
for(let value of delegatingIterator) {
console.log(value);
}
// "Greetings!
// "Hello!"
// "Bye!"
// "Ok, bye.”
yield*
背面的Generator
函数(没有return语句时),等同于在Generator函数内部,布置一个for...of
轮回。
function* concat(iter1, iter2) {
yield* iter1;
yield* iter2;
}
// 等同于
function* concat(iter1, iter2) {
for (var value of iter1) {
yield value;
}
for (var value of iter2) {
yield value;
}
}
上面代码,yield*
实行的是一个遍历器,for...of...
轮回的也是一个遍历器,所以for…of…返回yield value时等同于yield*
。
两个寻常会用到的示例:
1)遍历嵌套的数组:
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
2)关于状况的掌握:
var clock = function*() {
while (true) {
console.log('Tick!');
yield;
console.log('Tock!');
yield;
}
};
作为对象属性的generator
假如一个对象的属性是**Generator**
函数,能够简写成下面的情势
let obj = {
* myGeneratorMethod() {
···
}
};
等同于
let obj = {
myGeneratorMethod: function* () {
// ···
}
};
Generator函数的this
Generator
函数老是返回一个遍历器,ES6划定这个遍历器是Generator
函数的实例,也继续了Generator
函数的prototype对象上的要领。
function* g() {}
g.prototype.hello = function () {
return 'hi!';
};
let obj = g();
obj instanceof g // true
obj.hello() // 'hi!'
上面代码表明,Generator
函数g返回的遍历器obj,是g的实例,而且继续了g.prototype
。然则,假如把g看成一般的组织函数,并不会见效,由于g返回的老是遍历器对象,而不是this对象。所以假如在generator
函数内运用this,obj对象接见不到。
那末,有无方法让Generator
函数返回一个一般的对象实例,既能够用next
要领,又能够获得一般的this
?
function* F() {
this.a = 1;
yield this.b = 2;
yield this.c = 3;
}
var f = F.call(F.prototype);
f.next(); // Object {value: 2, done: false}
f.next(); // Object {value: 3, done: false}
f.next(); // Object {value: undefined, done: true}
f.a // 1
f.b // 2
f.c // 3
上面代码:起首运用call函数将F函数的this绑定到F.prototype;而f照样谁人遍历器对象是F函数的实例,又能够继续F.prototype的属性,所以也就能够接见F.prototype代表的this的属性了。
Generator函数的运用
generator
函数最大的作用能够用作异步使命的封装(由于它的yield敕令特征,能够停息和恢复实行)。而之前javascript
关于异步的完成重要就是 回调函数,事宜监听,promise
等。
示例:
var fetch = require('node-fetch');
function* gen(){
var url = 'https://api.github.com/users/github';
var result = yield fetch(url);
console.log(result.bio);
}
上面代码中,Generator
函数封装了一个异步操纵,该操纵先读取一个长途接口,然后从 JSON
花样的数据剖析信息。就像前面说过的,这段代码异常像同步操纵,除了加上了yield
敕令。
var g = gen();
var result = g.next();
result.value.then(function(data){
return data.json();
}).then(function(data){
g.next(data);
});
上面代码中,起首实行 Generator
函数,猎取遍历器对象,然后运用next
要领(第二行),实行异步使命的第一阶段。由于Fetch模块返回的是一个 Promise
对象,而这个对象被yield返回到了它的value属性中,因而要用.value.then要领挪用then要领。胜利后 return数据参数data能够被第二个then要领中接收。而第二次挪用then要领传入的data又传回了gen函数给了变量result。value往出传值,next能够往里传值。