目录
yield* 表达式
如果在 Generator 函数内部调用另一个 Generator 函数,需要在前者的函数内部自己手动完成遍历。
function* foo() {
yield 'a';
yield 'b';
}
function* bar() {
yield 'x';
// 手动遍历 foo()
for (let i of foo()) {
console.log(i);
}
yield 'y';
}
for (let v of bar()){
console.log(v);
}
// x
// a
// b
// y
ES6 提供 yield* 函数,用来在另一个 Generator 函数内部调用另一个 Generator 函数。
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* 的作用就是遍历。
function* inner() {
yield 'hello!';
}
function* outer1() {
yield 'open';
yield inner();
yield 'close';
}
var gen = outer1()
gen.next().value // "open"
gen.next().value // 返回一个遍历器对象
gen.next().value // "close"
function* outer2() {
yield 'open'
yield* inner()
yield 'close'
}
var gen = outer2()
gen.next().value // "open"
gen.next().value // "hello!"
gen.next().value // "close"
从语法角度看,如果 yield* 表达式后面跟的是一个遍历器对象,那么就需要在 yield 后面加一个星号,表明它返回的是一个遍历器对象。
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* 只会输出遍历器的 yield 表达式,不会输出 return 表达式。
function* g1() {
yield 1;
yield 2;
return 3;
}
function* g2() {
yield 4;
yield* g1();
yield 5;
}
for (var k of g2()) {
console.log(k);
}
// 4 1 2 5
上面代码不会将 g1 函数的 return 值输出。
function* g1() {
yield 1;
yield 2;
return 3;
}
function* g2() {
yield 4;
yield yield* g1();
yield 5;
}
for (var k of g1()) {
console.log(k);
}
// 1 2
for (var k of g2()) {
console.log(k);
}
// 4 1 2 3 5
let opr = g2();
console.log(opr.next());
console.log(opr.next());
console.log(opr.next());
console.log(opr.next());
console.log(opr.next());
console.log(opr.next());
console.log(opr.next());
// {value: 4, done: false}
// {value: 1, done: false}
// {value: 2, done: false}
// {value: 3, done: false}
// {value: 5, done: false}
// {value: undefined, done: true}
// {value: undefined, done: true}
如果 yield* 后面的 Generator 函数有 return 语句,则需要用变量来接收这个返回值(var val = yield* iterator)。
如果 yield* 后面跟的是一个数组,则会遍历所有数组成员。如果不加星号,返回的就是整个数组。
function* gen(){
yield* ["a", "b", "c"];
}
gen().next() // { value:"a", done:false }
function* g() {
yield [1, 2];
}
console.log([...g()]); // [[1, 2]]
console.log([...gen()]); // [1, 2]
g() 方法返回的遍历器对象,调用 next() 方法返回的是 yield 后面的表达式 => 数组 [1, 2];gen() 方法返回的遍历器对象,调用 next() 方法返回的是数组每一项。
任何部署了 Iterator 接口的数据结构,都可以被 yield* 遍历。
let read = (function* () {
yield 'hello';
yield* 'hello';
})();
read.next().value // "hello"
read.next().value // "h"
用 yield* 命令遍历嵌套数组。
function* iterTree(tree) {
if (Array.isArray(tree)) {
for (let i = 0; i < tree.length; i++) {
yield* iterTree(tree[i]);
}
} else {
yield tree;
}
}
let tree = [1, 2, [3, 4], [[5]]];
for (let k of iterTree(tree)) {
console.log(k);
}
// 1 2 3 4 5
console.log([...iterTree(tree)]);
// [1, 2, 3, 4, 5]
作为对象属性的 Generator 函数
let obj = {
*a() {
yield 1
}
};
let opr = obj.a();
console.log(opr.next());
// {value: 1, done: false}
Generator 函数中的 this
Generator 函数总是返回一个遍历器对象,ES6 规定这个遍历器是 Generator 函数的实例,也继承了 Generator 函数 prototype 上的方法。
function* g() { }
g.prototype.sayHello = function () {
console.log('Hello world');
}
let obj = g();
console.log(obj instanceof g); // true
obj.sayHello(); // Hello world
Generator 函数和普通函数的区别:不能使用 new 命令,否则会报错;Generator 函数不会返回 this 对象,返回的总是遍历器对象。
function s() {
this.a = 1;
}
var obj = new s();
console.log(obj.a); // 1
function* g() {
this.a = 11;
}
let obj = g();
obj.a // undefined
Generator 函数 g 在 this 上面绑定了一个属性,但是作为 g 的实例 obj 却拿不到这个属性。
使用 call() 来绑定 Generator 函数内部 this(除了使用 call,还可以使用 apply、bind,都可以改变函数内部 this 指向)。
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
Generator 函数返回的遍历器对象,是 Generator 函数的实例,那是这个实例就可以获取到 Generator 函数原型上的属性。由此,可以把 Generator 函数内部的 this 指针指向改为 Generator 函数的原型,那么遍历器对象自己就可以继承这些属性了。
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
值得注意的是,如果上面代码没有调用一次 next 方法,f.a 依旧是 undefined。因为这样 F() 调用Generator 函数仅仅返回了一个遍历器对象,并没有开始执行内部代码,this 指针上面并没有挂 a 属性,只有当代码运行了才会有 a 属性。
改造 Generator 函数,可以使用 new 命令。
function* gen() {
this.a = 1;
yield this.b = 2;
yield this.c = 3;
}
function F() {
return gen.call(gen.prototype);
}
var f = new F();
// ...