Generator介绍
Generator 的中文名称是生成器,它是ECMAScript6中提供的新特性。在过去,封装一段运算逻辑的单元是函数。函数只存在“没有被调用”或者“被调用”的情况,不存在一个函数被执行之后还能暂停的情况,而Generator的出现让这种情况成为可能。
通过function*
来定义的函数称之为“生成器函数”(generator function),它的特点是可以中断函数的执行,每次执行yield
语句之后,函数即暂停执行,直到调用返回的生成器对象的next()
函数它才会继续执行。
也就是说Generator 函数是一个状态机,封装了多个内部状态。执行 Generator 函数返回一个遍历器对象(一个指向内部状态的指针对象),调用遍历器对象的next方法,使得指针移向下一个状态。每次调用next方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个yield表达式(或return语句)为止。
yield关键字
真正让Generator具有价值的是yield
关键字,这个yield
关键字让 Generator内部的逻辑能够切割成多个部分。
let compute = function* (a, b) {
let sum = a + b;
yield console.log(sum);
let c = a - b;
yield console.log(c);
let d = a * b;
yield console.log(d);
let e = a / b;
console.log(e);
};
// 执行一下这个函数得到 Generator 实例
let generator = compute(4, 2);
// 要使得函数中封装的代码逻辑得到执行,还得需要调用一次next方法。
generator.next();
----------
var compute = function* (a, b) {
let sum = a + b;
yield sum;
let c = a - b;
yield c;
let d = a * b;
yield d;
let e = a / b;
e;
};
输出结果:
6
{value: undefined, done: false}
发现函数执行到第一个yield
关键字的时候就停止了。要让业务逻辑继续执行完,需要反复调用.next()
。
generator.next();
generator.next();
generator.next();
generator.next();
可以简单地理解为yield
关键字将程序逻辑划分成几部分,每次.next()
执行时执行一部分。这使得程序的执行单元再也不是函数,复杂的逻辑可以通过yield来暂停。
-
.next()
调用时,返回一个对象
yield
除了切割逻辑外,它与.next()
的行为息息相关。每次.next()
调用时,返回一个对象,这个对象具备两个属性。
其中一个属性是布尔型的done
。它表示这个Generator对象的逻辑块是否执行完成。
另一个属性是value
,它来自于yield
语句后的表达式的结果。
function * getNumbers(num) {
for(let i=0; i<num;i++) {
yield i
}
return 'ok';
}
const gen = getNumbers(10);
function next() {
let res = gen.next();
console.log(res);
if(res.done) {
console.log('done');
} else {
setTimeout(next,300)
}
}
next();
输出结果:
{value: 0, done: false}
undefined
{value: 1, done: false}
{value: 2, done: false}
{value: 3, done: false}
{value: 4, done: false}
{value: 5, done: false}
{value: 6, done: false}
{value: 7, done: false}
{value: 8, done: false}
{value: 9, done: false}
{value: "ok", done: true}
done
- 可以通过向
.next()
传递参数
let compute = function* (a, b) {
var foo = yield a + b;
console.log(foo);
};
let generator = compute(4, 2);
generator.next(); // {value: 6, done: false}
generator.next("Hello world!"); //Hello world! {value: undefined, done: true}
通过.next()传递参数,可以赋值给yield
关键字前面的变量声明。
所以,对于Generator而言,它不仅可以将逻辑的单元切分得更细外,还能在暂停和继续执行的间隔中,动态传入数据,使得代码逻辑可以更灵活。
使用 Generator 编写状态切换逻辑代码
function * loop(list, max=Infinity) {
for(let i=0; i<max;i++) {
yield list[i % list.length];
}
}
function toggle(...actions) {
let gen = loop(actions);
//错误写法:先调用loop(actions).next();
return function(...args) {
return gen.next().value.apply(this, args);
}
}
// switcher.addEventListener('click', toggle(
// e => e.target.className = 'off',
// e => e.target.className = 'on'
// ));
switcher.addEventListener('click', toggle(
e => e.target.className = 'off',
e => e.target.className = 'warn',
e => e.target.className = 'on'
));
完整代码:https://code.h5jun.com/yeyo/edit?html,css,js,output
网友评论