本文github地址:JavaScript_Everything 大前端知识体系与面试宝典,从前端到后端,全栈工程师,成为六边形战士
什么是生成器
生成器是一种返回迭代器
的函数,其定义形式为:function *fn(){}
。生成器中会使用yield
关键字指定调用迭代器的next
方法时的返回值。
javascript
function *fn(){
yield 1;
yield 2;
yield 3;
}
既然返回的是生成器,那么返回的结果可以调用迭代器的next
方法:
javascript
let iterator = fn();
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
上面的输出为:
javascript
{value: 1, done: false}
{value: 2, done: false}
{value: 3, done: false}
{value: undefined, done: true}
需要注意的是:每当执行完一条yield
语句,函数就会自动停止执行。再下一次调用迭代器的next
方法时,才会继续执行下一条yield
语句。
通过上面的例子我们可以看到,其实生成器的调用方法与普通函数相同,只是它返回的是一个迭代器,在定义时需要用*
进行标识。
需要注意的是yield
只能在生成器内部使用,即使在生成器内部的函数中也不可以使用。
例如:
javascript
function *fn(items){
items.forEach(function(item){
yield item; // ❌
})
}
利用生成器常见可迭代对象
默认情况下我们自己定义的对象都是不可迭代对象,但如果给 对象的Symbol.iterator
属性添加一个生成器,就可以将对象变为可迭代对象了。
javascript
let iteratorObject = {
items: [],
*[Symbol.iterator](){
for(let item of this.items){
yield item;
}
}
};
然后我们给iteratorObject
对象的items
属性中添加值:
javascript
iteratorObject.items.push(1);
iteratorObject.items.push(2);
iteratorObject.items.push(3);
这样我们就可以去使用for...of
迭代我们定义的iteratorObject
对象了:
javascript
for(let item of iteratorObject){
console.log(item)
}
给迭代器传递参数
我们创建一个生成器,其实就是创建了迭代器,因为生成器函数的返回结果是迭代器。在上面的例子中,我们直接指定了每一次yield
语句生成的值,使其在调用迭代器的next
方法时返回:
javascript
function *fn(){
yield 1;
yield 2;
yield 3;
}
但其实我们完全可以通过给迭代器的next
方法传递参数来控制每一次yield
语句的生成值:
javascript
function *createIterator(){
let first = yield 1;
let second = yield first + 2;
yield second + 3;
}
let iterator = createIterator();
console.log(iterator.next());
console.log(iterator.next(4));
console.log(iterator.next(5));
console.log(iterator.next(6));
console.log(iterator.next());
打印的结果为:
javascript
{value: 1, done: false}
{value: 6, done: false}
{value: 8, done: false}
{value: undefined, done: true}
{value: undefined, done: true}
可以看到iterator.next(4)
传入的 4,直接赋值给了生成器函数的内部变量first
。
一条yield
语句,由一次next
调用消费。因此当执行iterator.next(4)
时,实际对应的yield
语句是yield first + 2
。生成的值是yield
右边的值,而变量first
被传递的参数赋值为了 4,因此该yield
语句生成的值是 6,即调用next
方法时返回的值是 6。
而执行iterator.next(5)
时,实际对应的yield
语句是yield second + 3
。而second
被传递的参数赋值为了 5,因此该yield
语句生成的结果为5 + 3
等于 8。
再调用iterator.next(6)
时,尽管传递了参数,但是迭代器已经执行完毕。
注意:由于传递给next
方法的参数会替代上一次yield
语句的返回值,因此在第一次调用next
方法时传递的参数会被丢弃。因为第一次调用的next
方法前不会执行任何的yield
语句。
生成器返回结果
在这里我们不禁要问,既然生成器也是函数,那它可以有明确的return
返回值吗?当然是肯定的。我们可以在生成器中通过return
指定返回值,它表示所有迭代器已经完成。此时结果对象的属性done
会被设置为true
,若return
返回了值,则属性value
会被设置为该值,若无返回值,则为undefined
。
javascript
function *createIterator(){
yield 1;
yield 2;
return;
yield 3;
}
let iterator = createIterator();
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
打印的结果为:
javascript
{value: 1, done: false}
{value: 2, done: false}
{value: undefined, done: true}
可以看到,return
之后的yield
不会执行,调用next
返回的结果对象的状态属性done
已变为了true
。
委托生成器
生成器是函数,那么生成器内部便可以执行其他的生成器函数,这样我们就能将其中一个生成器生成数据的过程,交由其他生成器进行,而该生成器只需要通过yield
控制生成顺序即可。
javascript
function *fn1(){
yield 1;
yield 2;
yield 3;
}
function *fn2(nums){
for(let i=0;i<nums;i++){
yield nums[i]+1
}
}
function *fn3(){
let nums = yield *fn1();
yield *fn2(nums);
}
let iterator = fn3();
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
打印结果为:
javascript
{value: 1, done: false}
{value: 2, done: false}
{value: 3, done: false}
{value: undefined, done: true}
使用生成器和迭代器控制执行流程
我们可以将多个具有执行顺序关系的函数传递给迭代器,通过yield
来执行函数。并通过生成器返回的迭代器对象的next
方法来控制函数的执行时机。
javascript
let steps = [()=>{return 1}, ()=>{return 2}, ()=>{return 3}];
function* iterateSteps(steps){
for (var i=0; i< steps.length; i++){
let step = steps[i];
yield step();
}
}
let iterator = iterateSteps(steps)
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
打印结果为:
javascript
{value: 1, done: false}
{value: 2, done: false}
{value: 3, done: false}
{value: undefined, done: true}
如果是异步函数,则我们控制异步函数的执行顺序,替代回调函数。但Promise
则是比生成器+迭代器更好的异步流程控制方法。
本文github地址:JavaScript_Everything 大前端知识体系与面试宝典,从前端到后端,全栈工程师,成为六边形战士