[javascript核心-21] 迷人的生成器🦏

本文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 大前端知识体系与面试宝典,从前端到后端,全栈工程师,成为六边形战士

相关推荐
Leyla4 分钟前
【代码重构】好的重构与坏的重构
前端
影子落人间7 分钟前
已解决npm ERR! request to https://registry.npm.taobao.org/@vant%2farea-data failed
前端·npm·node.js
世俗ˊ31 分钟前
CSS入门笔记
前端·css·笔记
子非鱼92131 分钟前
【前端】ES6:Set与Map
前端·javascript·es6
6230_36 分钟前
git使用“保姆级”教程1——简介及配置项设置
前端·git·学习·html·web3·学习方法·改行学it
想退休的搬砖人1 小时前
vue选项式写法项目案例(购物车)
前端·javascript·vue.js
加勒比海涛1 小时前
HTML 揭秘:HTML 编码快速入门
前端·html
啥子花道1 小时前
Vue3.4 中 v-model 双向数据绑定新玩法详解
前端·javascript·vue.js
麒麟而非淇淋1 小时前
AJAX 入门 day3
前端·javascript·ajax
茶茶只知道学习1 小时前
通过鼠标移动来调整两个盒子的宽度(响应式)
前端·javascript·css