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

本文目录

[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/

最后编辑于 :
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容