ES6 Generator函式之基本用法(2)
Generator函式之基本用法(2)
上一篇文章中總結了Generator函式基本概念:
yield表示式,與Iterator介面、for…of迴圈的關係,next方法,throw方法,return方法等內容。
這篇文章接著上一篇文章繼續總結Generator函式的基本用法
(1)yield*表示式
直接在Generator函式內部呼叫另一個Generator函式,預設情況下是沒有效果的:
function* foo() {
yield 'a';
yield 'b';
}
function* bar() {
yield 'x';
foo();
yield 'y';
}
for (let v of bar()){
console.log(v);
}
// "x"
// "y"
如果使用yield命令,則會返回一個遍歷器物件:
function* foo() {
yield 'a';
yield 'b';
}
function* bar() {
yield 'x';
yield foo();
yield 'y';
}
for (let v of bar()){
console.log(v);
}
// "x"
//一個遍歷器物件 foo
// "y"
1.yield*語句用來在一個Generator函式內部執行另一個Generator函式
function* foo() {
yield 'a';
yield 'b';
}
function* bar() {
yield 'x';
yield* foo();
yield 'y';
}
for (let v of bar()){
console.log(v);
}
// "x"
// "a"
// "b"
// "y"
這時候我們稱bar為代理者,foo為被代理者。
2.利用yield*等同於在代理Generator函式內部部署一個for…of迴圈
(前提是,被代理的Generator函式沒有return語句)
以下的程式碼與上面的yield*寫法是等效的:
function* foo() {
yield 'a';
yield 'b';
}
function* bar() {
yield 'x';
for (let item of foo()) {
console.log(item)
}
yield 'y';
}
for (let v of bar()) {
console.log(v);
}
// "x"
// "a"
// "b"
// "y"
3.被代理的Generator函式具有return語句,可以向代理它的Generator函式返回值
function* foo() {
yield 'a';
yield 'b';
return "foo"
}
function* bar() {
yield 'x';
let word=yield* foo();
yield word;
yield 'y';
}
for (let v of bar()) {
console.log(v);
}
// "x"
// "a"
// "b"
// "foo"
// "y"
另外一個例子:
function* foo() {
yield 'a';
yield 'b';
return "foo"
}
function* bar() {
yield 'x';
let word=yield* foo();
yield word;
yield 'y';
}
let a=[...bar()];
console.log(a);
//["x","a","b","foo","y"]
4.yield*後面跟著帶有Iterator介面的資料結構
//字串
function* foo() {
yield* "qwe"
}
for (let v of foo()) {
console.log(v);
}
// q
// w
// e
//陣列
function* bar() {
yield* [1, 2, 3]
}
for (let v of bar()) {
console.log(v);
}
// 1
// 2
// 3
//Set結構
function* foo1() {
yield* new Set(["q", "w", "e"])
}
for (let v of foo1()) {
console.log(v);
}
// q
// w
// e
//Map結構
function* bar1() {
yield* new Map([["q", 1], ["w", 2], ["e", 3]])
}
for (let v of bar1()) {
console.log(v);
}
// ["q",1]
// ["w",2]
// ["e",3]
5.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
(2)Generator函式作為物件屬性
如果一個物件的屬性是Generator函式,可以寫成下面的形式:
let obj = {
* myGeneratorMethod() {
···
}
};
//等同於
let obj = {
myGeneratorMethod: function* () {
// ···
}
};
(3)Generator函式的this
Generator函式總是返回一個遍歷器物件,ES6規定這個遍歷器物件是Generator函式的例項:
function* g() {}
let obj = g();
console.log(obj instanceof g)
// true
Generator函式返回的遍歷器會繼承Generator函式的prototype物件上的方法:
function* g() {}
g.prototype.hello = function () {
return 'hi!';
};
let obj = g();
console.log(obj.hello()) // 'hi!'
注意,Generator函式返回的是遍歷器物件,而不是this物件,因此不能當作普通的建構函式
function* g() {
this.a = 11;
}
let obj = g();
obj.next();
obj.a // undefined
Generator函式不能和new一起使用,否則會報錯:
function* F() {
yield this.x = 2;
yield this.y = 3;
}
new F()
// TypeError: F is not a constructor
那麼,有沒有辦法讓 Generator 函式返回一個正常的物件例項,既可以用next方法,又可以獲得正常的this?
下面是一個變通方法。首先,生成一個空物件,使用call方法繫結 Generator 函式內部的this。這樣,建構函式呼叫以後,這個空物件就是 Generator 函式的例項物件了。
function* F() {
this.a = 1;
yield this.b = 2;
yield this.c = 3;
}
var obj = {};
var f = F.call(obj);
f.next(); // Object {value: 2, done: false}
f.next(); // Object {value: 3, done: false}
f.next(); // Object {value: undefined, done: true}
obj.a // 1
obj.b // 2
obj.c // 3
上面程式碼中,首先是F內部的this物件繫結obj物件,然後呼叫它,返回一個 Iterator 物件。這個物件執行三次next方法(因為F內部有兩個yield表示式),完成 F 內部所有程式碼的執行。這時,所有內部屬性都繫結在obj物件上了,因此obj物件也就成了F的例項。
上面程式碼中,執行的是遍歷器物件f,但是生成的物件例項是obj,有沒有辦法將這兩個物件統一呢?
一個辦法就是將obj換成F.prototype:
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
(4)應用
1.處理非同步操作,改寫回調函式
非同步操作的同步化表達。處理非同步操作時,在寫法上可以做到與同步寫法類似。
2.控制流管理
3.部署Iterator介面
利用Generator函式可以在任意物件上部署Iterator介面
function* iterEntries(obj) {
let keys = Object.keys(obj);
for (let i=0; i < keys.length; i++) {
let key = keys[i];
yield [key, obj[key]];
}
}
let myObj = { foo: 3, bar: 7 };
for (let [key, value] of iterEntries(myObj)) {
console.log(key, value);
}
// foo 3
// bar 7