基本概念
Generator
函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同。
Generator
函数有多种理解角度。语法上,首先可以把它理解成,Generator
函数是一个状态机,封装了多个内部状态。执行 Generator
函数会返回一个遍历器对象,也就是说,Generator
函数除了状态机,还是一个遍历器对象生成函数。返回的遍历器对象,可以依次遍历 Generator
函数内部的每一个状态。
形式上,Generator
函数是一个普通函数,但是有两个特征。一是,function
关键字与函数名之间有一个星号;二是,函数体内部使用yield表达式,定义不同的内部状态(yield在英语里的意思就是“产出”)
简单使用例子如下:
function* helloWorld() {
yield 'hello';
yield 'world';
return 'ending';
}
var m = helloWorld();
print(m.next().value)
print(m.next().done)
print(m.next().value)
print(m.next().done)
-----hello
-----false
-----ending
-----true
总结一下,调用 Generator
函数,返回一个遍历器对象,代表 Generator
函数的内部指针。以后,每次调用遍历器对象的next
方法,就会返回一个有着value
和done
两个属性的对象。value
属性表示当前的内部状态的值,是yield表达式后面那个表达式的值;done
属性是一个布尔值,表示是否遍历结束。
与 Iterator 接口的关系
任意一个对象的Symbol.iterator
方法,等于该对象的遍历器生成函数,调用该函数会返回该对象的一个遍历器对象。
由于 Generator
函数就是遍历器生成函数,因此可以把 Generator
赋值给对象的Symbol.iterator
属性,从而使得该对象具有Iterator
接口。
var myIterator = {};
myIterator[Symbol.iterator] = function* () {
yield 1;
yield 2;
yield 3;
};
var iter = [...myIterator];
print(iter[0])
print(iter[1])
print(iter[2])
-----1
-----2
-----3
for…of 循环
for...of
循环可以自动遍历Generator
函数运行时生成的Iterator
对象,且此时不再需要调用next
方法。
function *foo() {
yield 'a';
yield 'b';
yield 'c';
yield 'd';
return 'e';
}
for(let v of foo()) {
print(v);
}
-----a
-----b
-----c
-----d
利用 Generator
函数和for...of
循环,实现斐波那契数列的例子。
function* fibonacci() {
let [prev, curr] = [0, 1];
for (; ;) {
[prev, curr] = [curr, prev + curr];
yield curr;
}
}
for(let n of fibonacci()) {
if(n>100) break;
print(n);
}
-----1
-----2
-----3
-----5
-----8
-----13
-----21
-----34
-----55
-----89
利用for...of
循环,可以写出遍历任意对象(object)的方法。原生的 JavaScript
对象没有遍历接口,无法使用for...of
循环,通过 Generator
函数为它加上这个接口,就可以用了。
function* objectEntries(obj) {
let propKeys = Reflect.ownKeys(obj);
for (let propKey of propKeys) {
yield [propKey, obj[propKey]];
}
}
let jane = { first: 'Jane', last: 'Doe' };
for (let [key, value] of objectEntries(jane)) {
console.log(`${key}: ${value}`);
}
// first: Jane
// last: Doe
上面代码中,对象jane原生不具备Iterator
接口,无法用for...of
遍历。这时,我们通过Generator
函数objectEntries
为它加上遍历器接口,就可以用for...of
遍历了。加上遍历器接口的另一种写法是,将Generator
函数加到对象的Symbol.iterator
属性上面。
function* objectEntries() {
let propKeys = Object.keys(this);
for (let propKey of propKeys) {
yield [propKey, this[propKey]];
}
}
let jane = { first: 'Jane', last: 'Doe' };
jane[Symbol.iterator] = objectEntries;
for (let [key, value] of jane) {
console.log(`${key}: ${value}`);
}
// first: Jane
// last: Doe