ES6 详解之 Generator
基本概念
Generator 函数有多种理解角度。语法上,首先可以把它理解成,Generator 函数是一个状态机,封装了多个内部状态。
执行 Generator 函数会返回一个遍历器对象,也就是说,Generator 函数除了状态机,还是一个遍历器对象生成函数。返回的遍历器对象,可以依次遍历 Generator 函数内部的每一个状态。
Generator 本质是一个遍历器生成函数
形式上,Generator 函数是一个普通函数,但是有两个特征。
- function 关键字与函数名之间有一个星号; function*()
- 函数体内部使用 yield 表达式,定义不同的内部状态(yield 在英语里的意思就是 产出)。
function* generator(){
yield 1;
yield 2;
yield 3;
return 'ending';
}
let hw = generator();
Generator 函数的调用方法与普通函数一样,也是在函数名后面加上一对圆括号。
不同的是:调用 Generator 函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象,也就是上一章介绍的遍历器对象(Iterator Object)。
下一步,必须调用遍历器对象的 next 方法,使得指针移向下一个状态。也就是说,每次调用next方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个的yield表达式(或return语句)为止。换言之,Generator 函数是分段执行的,yield表达式是暂停执行的标记,而 next 方法可以恢复执行。
hw.next()
// { value: 1, done: false }
hw.next()
// { value: 2, done: false }
hw.next()
// { value: 'ending', done: true }
hw.next()
// { value: undefined, done: true }
总结一下
调用 Generator 函数,返回一个遍历器对象,代表 Generator 函数的内部指针。以后,每次调用遍历器对象的next方法,就会返回一个有着 value 和 done 两个属性的对象。value属性表示当前的内部状态的值,是yield表达式后面那个表达式的值;done属性是一个布尔值,表示是否遍历结束.
另外需要注意:yield 表达式只能用在 Generator 函数里面,用在其他地方都会报错。
(function (){
yield 1;
})()
// SyntaxError: Unexpected number
通过 yield* 来剥离复杂数据
{
let arr = [1,[2,3],[[4,5,6],7],[[8],[[9],[10]]]];
let flat = function* (a) {
let length = a.length;
for (let i=0;i<length;i++){
let item = a[i];
if(typeof item !== 'number') {
yield* flat(item);
} else {
yield item;
}
}
};
for (var f of flat(arr)) {
console.log(f); // 1,2,3,4,5,6,7,8,9,10
}
}
与 Iterator 接口的关系
任意一个对象的 Symbol.iterator 方法,等于该对象的遍历器生成函数,调用该函数会返回该对象的一个遍历器对象.
由于 Generator 函数就是遍历器生成函数,因此可以把 Generator 赋值给对象的Symbol.iterator属性,从而使得该对象具有 Iterator 接口。
{
let myInterable = {};
myInterable[Symbol.iterator] = function* (){
yield 1;
yield 2;
yield 3;
};
for (const value of myInterable) {
console.log(value); // 1,2,3
}
console.log([...myInterable]); // [1,2,3]
}
next 方法的参数
yield 表达式本身没有返回值,或者说总是返回 undefined。next 方法可以带一个参数,该参数就会被当作上一个 yield 表达式的返回值。
function* f() {
for(var i = 0; true; i++) {
var reset = yield i;
if(reset) { i = -1; }
}
}
var g = f();
g.next() // { value: 0, done: false }
g.next() // { value: 1, done: false }
g.next(true) // { value: 0, done: false }
另外一个例子:
function* foo(x) {
var y = 2 * (yield (x + 1));
var z = yield (y / 3);
return (x + y + z);
}
var a = foo(5);
a.next() // Object{value:6, done:false}
a.next() // Object{value:NaN, done:false}
a.next() // Object{value:NaN, done:true}
var b = foo(5);
b.next() // { value:6, done:false }
b.next(12) // { value:8, done:false }
b.next(13) // { value:42, done:true }
如果向 next 方法提供参数,返回结果就完全不一样了。上面代码
第一次调用 b 的 next 方法时,x的值为5,返回x+1的值6;
第二次调用next方法,将上一次yield表达式的值设为12,因此y等于24,返回y / 3的值8;
第三次调用next方法,将上一次yield表达式的值设为13,因此z等于13,这时x等于5,y等于24,所以return语句的值等于42。
下面是一个利用 Generator 函数和 for...of 循环,实现斐波那契数列的例子。
{
function* fiboncci() {
let [prev,curr] = [0,1];
for(;;){
yield curr;
[prev,curr] = [curr,prev + curr];
}
}
let array = [];
for (let n of fiboncci()) {
if(n>1000) break;
array.push(n);
}
console.log(array); // [1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987]
}
Generator.prototype.return()
Generator 函数返回的遍历器对象,还有一个return方法,可以返回给定的值,并且终结遍历 Generator 函数。
function* gen() {
yield 1;
yield 2;
yield 3;
}
var g = gen();
g.next() // { value: 1, done: false }
g.return('foo') // { value: "foo", done: true }
g.next() // { value: undefined, done: true }
上面代码中,遍历器对象g调用return方法后,返回值的value属性就是return方法的参数foo。并且,Generator 函数的遍历就终止了,返回值的done属性为true,以后再调用next方法,done属性总是返回true。
注意点:如果 Generator 函数内部有 try...finally 代码块,且正在执行 try 代码块,那么 return 方法会推迟到 finally 代码块执行完再执行。
function* numbers () {
yield 1;
try {
yield 2;
yield 3;
} finally {
yield 4;
yield 5;
}
yield 6;
}
var g = numbers();
g.next() // { value: 1, done: false }
g.next() // { value: 2, done: false }
g.return(7) // { value: 4, done: false }
g.next() // { value: 5, done: false }
g.next() // { value: 7, done: true }
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
上一篇: ES6 详解之模拟 Set 实现
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论