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