美文网首页
你不知道的JS(中卷)笔记——生成器(Generator)和迭代

你不知道的JS(中卷)笔记——生成器(Generator)和迭代

作者: 李牧敲代码 | 来源:发表于2020-04-03 00:39 被阅读0次

    本文目录

    [iterator]

    1. iterator的定义
    2. Iterator 的作用
    3. iterator默认结构
    4. 调用iterator的场合
    5. 字符串的iterator
    6. 遍历器对象的 return(),throw()
    7. for...of 循环
    8. iterator的实现

    [generator]

    1. 简介
    2. 运行规则
    3. Generator.prototype.throw()
    4. Generator.prototype.return()
    5. next()、throw()、return() 的共同点
    6. yield* 表达式
    7. 作为对象属性的 Generator 函数
    8. Generator 函数的this
    9. Generator 函数的异步应用

    1. iterator的定义

    [iterator]

    [定义]

    为各种不同的数据结构提供统一的访问机制的一种接口

    Iterator 的作用:
    1. 是为各种数据结构,提供一个统一的、简便的遍历访问接口;
    2. 使得数据结构的成员能够按某种次序排列;
    3. ES6 创造了一种新的遍历命令for...of循环,Iterator 接口主要供for...of消费。
    iterator默认结构

    一种数据结构只要部署了 Iterator 接口,我们就称这种数据结构是“可遍历的”(iterable)。
    ES6 规定,默认的 Iterator 接口部署在数据结构的[Symbol.iterator]属性。
    看例子:

    const obj = {
        [Symbol.iterator] : function () {
          return {
            next: function () {
              return {
                value: 1,
                done: true
              };
            }
          };
        }
      };
    

    其中value表示遍历到的值,done表示遍历是否结束。
    原生具备 Iterator 接口的数据结构有7种:

    1. Array
    2. Set
    3. Map
    4. TypedArray(Int8Array(), Uint8Array()等等 )
    5. 函数的 arguments 对象
    6. NodeList
    7. String
    调用iterator的场合(用数组作为参数的都是)
    1. 解构赋值
    2. 扩展运算符
    3. Array.from
    4. for...of
    5. new Map,new WeakMap, new Set,new WeakSet
    6. yield*
    7. promise.all()
    8. promise.race()

    字符串的iterator

    let str = 'wcx';
    let it = str[Symbol.iterator]();
    
    console.log(it.next().value)//w
    console.log(it.next().value)//c
    console.log(it.next().value)//x
    
    遍历器对象的 return(),throw()
    1. 自定义的遍历器中next方法是必须的,return和throw是可选的,如果用for...of访问可遍历的对象,那么想中途中断可以通过break或者throw,同时会进入return方法。
    2. Genterator规格中规定return必须返回一个对象。
    3. throw方法主要是配合 Generator 函数使用,一般的遍历器对象用不到这个方法。
    let obj = {
        a: 1,
        b: 2,
        c: 3,
        [Symbol.iterator]: function () {
            return {
                next: function () {
                    let arr = Object.keys(obj);
                    let value = obj[arr[obj.count]];
                    obj.count++;
                    return {
                        value: value,
                        done: obj.count === arr.length + 1 ? true : false
                    }
                },
                return() {
                    console.log('enter return')
                    return {
                        value: 123,
                        done: false
                    }
                }
            }
        }
    }
    //设置个不可枚举的计数器用于遍历计数
    Object.defineProperty(obj, 'count', {
        value: 0,
        enumerable: false,
        writable: true,
        configurable: true
    })
    //break 和 throw都会进入enter
    for(let i of obj) {
        if(i === 3) {
            // break
            throw Error(i)
        }else {
            console.log(i)
        }
    }
    
    //改良版
    
    
    let obj1 = {
        a: 1,
        b: 2,
        c: 3,
    }
    
    
    Object.defineProperty(obj1, Symbol.iterator, {
        value: function() {
            let index = 0;
            let arr = Object.keys(obj);
            return {
                next: function() {
                    let rlt = arr[index];
                    index ++;
                    return {
                        value: rlt,
                        done: index > arr.length
                    }
                }
            }
        }
    })
    
    
    for(let i of obj1) {
        console.log(i)
    }
    
    
    iterator的实现

    iterator的实现除了上面那种方法歪还可以通过Generator实现

    let obj = {
        a: 1,
        b: 2,
        c: 3,
        [Symbol.iterator]: function *() {
            yield obj.a
            yield obj.b
            yield obj.c
        }
    }
    for(let i of obj) {
        console.log(i)
    }
    
    

    [generator]

    简介
    1. 定义:
      Generator 函数是 ES6 提供的一种异步编程解决方案。
    2. 三种理解角度:
    • 封装了多个内部状态的状态机
    • 能生成遍历器对象的函数(将一个生成器赋值给一个对象的[Symbol.iterator]等价于给这对象部署了一个iterator接口)
    • 由关键字function和* 组成的函数
    运行规则
    1. yield 后面紧跟着的表达式的值做为返回的遍历器对象的value,如果遇到return,那么return后面跟着的值作为遍历器对象的value,如果都没有,则value的值为undefined
    2. 如果没有中途中断,那么next比yield多一个的时候才会运行结束。
    3. next(x)的参数x作为上一个yield表达式的值,第一个next不赋值。
    4. 每次调用next方法会是函数运行停留在对应的yield之前(可以停在表达式中间!),再次调用next方法会使函数运行到下一个yield之前。
    5. yield表达式只能用在 Generator 函数里面,用在其他地方都会报错。
    6. yield表达式如果用在另一个表达式之中,必须放在圆括号里面。

    下面的例子从简单到复杂来说明下generator的运行规则:

    规则1~3:

    let a,b,c;
    function *foo() {
        a = yield 1;
        b = yield 2;
        c = yield 3;
        return 123;
    }
    
    
    let it = foo();
    
    
    //yield后面的值每次赋值给对应的value,第一个yield对第一个next,依次类推
    console.log(it.next())//{valueL: 1, done: false}//第一个next不会进行赋值
    console.log(it.next(10))//{valueL: 2, done: false}
    console.log(it.next(20))//{valueL: 3, done: false}
    console.log(it.next(30))//{valueL: 123, done: true}
    
    console.log(a)//10
    console.log(b)//20
    console.log(c)//30
    
    
    function *bar() {
        console.log(123)
    }
    
    let itx = bar();
    console.log(it.next())//{valueL: undefined, done: true}
    

    规则 4:

    let a = 1;
    function *foo() {
        a = a + (yield 2)
    }
    
    let it = foo();
    
    it.next();//运行到这里时候,函数的状态是 a = a + ....,此时a为初始值1 
    console.log(a);//1
    it.next(4);//运行到这里的时候,yield 2的值是4也就是next里的参数赋值的,这个时候a = 1 + 4 = 5
    console.log(a)//5
    

    规则5:

    
    function foo() {
        yield 123
    }
    
    
    let it = foo();
    
    it.next()//报错
    

    规则6:

    
    let str;
    function *foo() {
        // str = 'hello' + yield//错误
        str = 'hello' + (yield)//正确
    }
    
    
    let it = foo();
    it.next();
    console.log(str)//undefined
    it.next(123)
    console.log(str)//hello123
    

    下面的例子出自《你不知道的JS》,是2个生成器交替运行的例子

    let a = 1;
    let b = 2;
    function *foo() {
            a++;
            yield;
            b = b * a;
            a = (yield b) + 3;
    }
    
    function *bar() {
        b--;
        yield;
        a = (yield 8) + b;
        b = a * (yield 2);
    }
    
    
    function step(gen) {
        let it = gen();
        let last;
    
        return function() {//yiled给我传什么我都给我上一个next传过去
            last = it.next(last).value;
        }
    }
    
    
    let s1 = step(foo);
    let s2 = step(bar);
    //操作//a的值//b的值//it.next(last).value中last的值
    s2() // 1 1 undefined//line1
    s2() // 1 1 8//line2
    s1() // 2 1 undefined//line3
    s2() // 9 1 2//line4
    s1() // 9 9 9//line5
    s1() // 12 9 undefiend//line6
    s2() // 12 24 undefiend//line7
    console.log(a, b)//line8
    
    //s2(),这个时候b进行了b--操作,b变为了1,a还是默认值1,传入的last是初始化的值还未赋值,所以是undefined, 赋值后的last由于第一个yield后面未跟任何值所以也是undefined;所以结果是1 1 undfined
    //s2(), 这个时候运行到a = (yield 8) + b 中的yield 8时暂停了,yield 8还要等待下次的next的参数, a 维持原来的值1,而此时 last通过yield 8 变为了8, b是1。所以结果是1 1 8
    //s1(), 这个时候由于进行了a++ 操作,a 变为了2,b还是原来的值为1,last为未赋值的状态undefined,所以结果是 2 1 undefined
    //s2(),这个时候 yield 8 的值是line2中的last也就是 8,b 是1,因此a = 8 + 1= 9, last由于 yield 2 传的值变为2,所以结果是9 1 2
    //s1().这个时候 b = b * a = 9 * 1 = 9; a 保持原值为9,last的值是b也就是9,所以结果是9 9 9 
    //s1(), 这个时候 a = (yield b) + 3 也就是line5中的last值 + 3所以是9 + 3 = 12, 由于没有新的yield和return了所以last的值变为undefined
    //s2(), 这个时候 b = (yield 2) * a 其中yield 2的值是line4中的值是2, a 是 12, 所以结果是24
    
    //最终输出结果 12 24
    
    
    //如果函数bar的最后一行变为这样呢
    //b = a * (yield 2);
    //解析:这个时候line4 中yield暂停的时候a还是 9,此时所有上下文中的一切都被‘冻结了’,直到下次next调用,所以b = 9 * 2 = 18
    
    
    Generator.prototype.throw()

    Generator 函数返回的遍历器对象,都有一个throw方法,可以在函数体外抛出错误,然后在 Generator 函数体内捕获。
    错误捕获遵循如下几点:

    1. 由于throw相当于调用了next方法,如果throw的时候函数体已经运行到最后了,如果generator函数外部有try...catch,那么多出的错误会被外部的try...catch捕获。
    2. throw方法可以接受一个参数,该参数会被catch语句接收。
    3. 遍历器的throw和全局的throw不一样,后者只能被generator函数体外的try...cathch捕获
    4. 如果 Generator 函数内部和外部,都没有部署try...catch代码块,那么程序将报错,直接中断执行。
    5. throw方法抛出的错误要被内部捕获,前提是必须至少执行过一次next方法。
    6. Generator 函数体外抛出的错误,可以在函数体内捕获;反过来,Generator 函数体内抛出的错误,也可以被函数体外的catch捕获。
      例子:
    let it = foo();
    
    it.next()
    try{
        it.throw(new Error('err1'))//内部捕获
        it.throw(new Error('err2'))//外部捕获
    }catch(e) {
        console.log('外部捕获:', e)
    } 
    
    function *bar() {
        yield console.log('第一次next')
        
        yield console.log('第二次next')
    }
    
    let itx = bar();
    itx.next()
    itx.throw('err')//没有设置捕获直接中断运行
    
    function *gen() {
        yield;
        throw('我想给外部捕获')
    }
    
    let itg = gen();
    
    
    try {
        itg.next()
        itg.next()
    }catch(e) {
        console.log('外部捕获:', e)
    }
    
    
    Generator.prototype.return()
    function *foo() {
        try {
            yield console.log('第一次next')
    
            yield console.log('第二次next')
        }catch(e) {
            console.log('内部捕获:', e)
        }
    }
    
    let it = foo();
    
    it.next();
    
    
    it.return(console.log('return'))
    //第一次next
    //return
    
    
    next()、throw()、return() 的共同点(这个好记)

    三者都相当于替换yield表达式
    next(1):
    // 相当于将 let result = yield x + y
    // 替换成 let result = 1;
    throw('出错了')
    // 相当于将 let result = yield x + y
    // 替换成 let result = throw(new Error('出错了'));
    return(2):
    // 相当于将 let result = yield x + y
    // 替换成 let result = return 2;

    yield*表达式

    yield*后面跟一个可遍历的对象,那么相当于for...of了以便后面的对象

    function *foo() {
        console.log("inside *foo(): ", yield "B")
        console.log("inside *foo(): ", yield "C")
        return 'D'
    }
    
    function *bar() {
        console.log("inside *bar(): ", yield "A")
        console.log("inside *bar(): ", yield *foo())
        console.log("inside *bar(): ", yield "E")
        return 'F'
    }
    
    //相当于
    // function *bar() {
    //     console.log("inside *bar(): ", yield "A")
    //     console.log("inside *foo(): ", yield "B")
    //     console.log("inside *foo(): ", yield "C")
    
    //     console.log("inside *bar(): ", 'D')
    //     console.log("inside *bar(): ", yield "E")
    //     return 'F'
    // }
    
    
    let it = bar();
    
    console.log('outside:', it.next().value)
    
    //outside: A
    console.log('outside:', it.next(1).value)
    //inside *bar(): 1
    //outside: B
    console.log('outside:', it.next(2).value)
    //inside *foo(): 2
    //outside: C
    console.log('outside:', it.next(3).value)
    //inside *foo(): 3
    //inside *bar(): D
    //outside: E
    console.log('outside:', it.next(4).value)
    //inside *bar(): 4
    //outside: F
    
    作为对象属性的 Generator 函数

    let obj = {
    * myGeneratorMethod() {
    yield 1
    }
    };

    //相当于

    // let obj = {
    // myGeneratorMethod: function *() {
    // yield 1
    // }
    // }

    Generator 函数的this

    由于Generator 函数总是返回遍历器对象 ,这里的this不适用默认绑定规则。想取得你想要的的结果,可以通过硬绑定或其他方法。

    Generator 函数的异步应用

    不管是基于Promise的+ Generatror还是基于Thunk + Generator,都是一个控制权的交替来实现的,我们从下面一个最小的例子实现,同步执行异步操作来作为启发:

    let a = 1;
    function *foo() {
        yield console.log('操作开始了');
        console.log('a1', a)
        setTimeout(() => {
            a = 666;
            it.next();
        },1000)
    
        yield
        console.log('a2', a)
    }
    
    
    let it = foo();
    
    it.next();
    it.next();
    //a1 1
    //a2 666 如果换成普通的函数,a 打印出来的值永远是1
    
    

    参考文献:https://es6.ruanyifeng.com/

    相关文章

      网友评论

          本文标题:你不知道的JS(中卷)笔记——生成器(Generator)和迭代

          本文链接:https://www.haomeiwen.com/subject/jhpxphtx.html