为了便于书写,下文Iterator遍历器(迭代器)简称“I接口/I对象”;Generator生成器函数简称“G函数”。

Iterator遍历器 & for…of

  1. 作用:为不同数据类型提供统一接口,以便执行遍历。

  2. 当使用for…of循环遍历某种数据结构时,会自动寻找I接口。

  3. 本质上是一个指针对象。

  4. 对象可遍历条件:包含Symbol.iterator属性,值为一个返回I对象的方法,该I对象具有next方法,返回当前成员信息。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    //给Object变量部署I接口
    var obj={
    [Symbol.iterator]:function(){
    var obj2={
    next:function(){
    ...
    return {value:...,done:...};//值 状态
    }
    }
    return obj2;//I对象
    }
    };

    //执行Array变量的I接口
    var arr=['a','b'];
    var iter=arr[Symbol.iterator];
    iter.next();//{value:'a',done:false}
    iter.next();//{value:'b',done:false}
    iter.next();//{value:undefined,done:true}
  5. 原生具备I接口的数据类型:Array,Map,Set,String,TypedArray,函数的arguments对象,DOM NodeList对象,G函数对象,其它类似Array的对象。其他数据类型只要按照上一行设置属性也可遍历,即可以使用for…of/while循环遍历。(Object没有I接口是因为对象属性顺序不确定)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    //类似Array的Object变量部署Array的I接口的例子:
    let obj={
    0:'a',
    1:'b',
    2:'c',
    [Symbol.iterator]:Array.prototype[Symbol.iterator]
    };
    for(let item of obj){
    console.log(item);//a b c
    }
    //普通Object变量部署Array的I接口没有用

    ES6与C++,Java,C#,Python同步,使for…of作为遍历所有数据结构的统一方法。
    for…in获得键,for…of获得值。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    for(let temp in ['a','b']){...}//temp:0 1

    for(let temp of ['a','b']){...}//temp:'a' 'b'

    for(let temp in ['a','b'].keys()){...}//temp:0 1

    for(let temp in ['a','b'].values()){...}//temp:'a' 'b'

    for(let temp in ['a','b'].entries()){...}//temp:[0,'a'] [1,'b']

    let arr=['a','b'];
    arr.name='c';
    for(let temp in arr){...}//temp:0 1 'c'
    for(let temp of arr){...}//temp:'a' 'b'
  6. 默认调用I接口的场合:(1)Array和Set的解构赋值;(2)扩展运算符(…);(3)yield*后跟可遍历结构;(4)接收数组作为参数的其它场合

  7. 自己写I对象,next方法必写,return和throw可选。return用于提前结束,必须返回一个Object;throw主要配合G函数使用,一般不用。

  8. Map和Set结构:(1)for…of循环Map内的临时变量为[key,value]得到键和值,而Set只能得到值,可用上述keys(),values(),entries()方法拓展;(2)遍历顺序为成员被添加进去的顺序。

  9. for…of循环字符串可以自动识别32位UTF-16字符。

  10. 将类似数组的对象通过Array.from()转为数组可以方法遍历。

  11. 普通Object可用for…in遍历属性名,用for…of遍历会报错。
    解决方法:(1)用Object.keys()生成键名数组,可以遍历这个数组;(2)结合Object.keys用G函数重新包装。

    1
    2
    3
    4
    5
    6
    let obj={...};
    function* genObj(obj){
    for(let key of Object.keys(obj)){
    yield [key, obj[key]];
    }
    }
  12. 各种for循环的比较
    (1)原始的for循环for(var i=0;i<10;i++):写法麻烦;
    (2)forEach:无法中途跳出循环;
    (3)for…in:会把数组的键名转为字符串,会包含手动添加的键,遍历顺序不定;
    (4)for…of:没有for…in的缺点,是遍历所有数据结构的统一接口。

Generator生成器函数

  1. ES6引入的特殊函数。
  2. 是异步编程解决方法之一。
  3. G函数写法:
    1
    2
    3
    4
    function* myGen(){//function与函数名间有没有空格不限
    yield 'hello';
    yield 'world';
    }
  4. G函数有两个作用:
    (1)一个封装了多个内部状态的状态机;
    (2)调用G函数不会执行该函数,而是返回一个I对象,I对象可遍历G函数内部的每个状态(指向内部状态的指针对象)。G函数是I对象最简单的实现。
    1
    2
    3
    4
    5
    6
    7
    let obj={
    * [Symbol.iterator](){
    yield 'a';
    yield 'b';
    }
    };//简洁写法,此时obj可遍历
    obj.next();//{value:'a',done:false}
    每次调用next方法,指针指向下一条yield语句或return语句,直到没有下一条则永远返回{value:undefined,done:true}
  5. G函数不使用yield语句,则变为一次暂缓执行函数
    G函数执行时只生成一个变量obj,obj执行next方法时执行G函数内语句。
  6. G函数执行后返回I对象,该对象执行[Symbol.iterator]()方法,返回I对象自身。
  7. next方法的参数
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    function* f(){
    for(var i=0;i<5;i++){
    var check=yield i;
    if(check) i=-1;
    }
    }
    let g=f();
    g.next();//{value:0,done:false}
    g.next();//{value:1,done:false}
    g.next(true);//{value:0,done:false}
    由于i用var修饰,是一个全局变量;
    next内传入的参数会作为yield表达式的返回值;
    每次yield暂停G函数时,G函数原本的上下文保持不变,
    以上述函数为例,第一次跑next时,第一轮循环走到yield i暂停,
    第二次跑next时,仍在第一轮循环,由于第一次跑next没有参数,check=undefined,往下跑到第二轮循环,此时i=1,第二个next依然没有给参数,还是跑到yield i暂停,
    第三次跑next时,传入的参数值为true,此时仍在第二轮循环,赋给check,进入if判断,i赋值为-1,继续走第三轮循环,先走i++,使i=0,跑到yield i暂停,next方法返回一个对象{value:0,done:false}

而G函数也可接收参数,可作为影响整个G函数的外部传入值。

1
2
3
4
5
6
7
8
9
10
11
function* m(n=0){
yield n+1;
yield n+5;
}
var g1=m();
g1.next();//{value:1,done:false}
g1.next();//{value:5,done:false}

var g2=m(10);
g2.next();//{value:11,done:false}
g2.next();//{value:15,done:false}
  1. throw方法:既可以在G函数内部抛出异常,也可以在G函数外部抛出异常
  2. return方法:终止G函数。
  3. G函数可以不完全实现“协程”。(看不懂)
  4. G函数可用于控制流管理。(不了解应用场景,大概是每次执行一步,每个步骤必须是同步操作)
  5. 异步编程
    异步编程的方法:回调函数,事件监听,发布/订阅,Promise对象,G函数(ES6)。

yield & yield*

  1. yield
    yield译为“产出”,在代码里有“生成”的意思。
    yield表达式只能在G函数内使用,在G函数嵌套的普通函数内使用也会报错。
    yield表达式如果放在另一表达式之中,必须套上圆括号,例如:console.log('aaa',(yield 'bbb'));;
    yield表达式如果作为其他函数的参数或放在表达式的右边,可以不加括号。getData(yield 'a'); let name=yield 'b';
    yield表达式是函数可以暂停执行的标志,提供了可以手动“惰性求值”的语法。

  2. yield*
    yield*语句可用于在G函数中,执行另一个G函数。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    function* m(){
    yield 'a';
    yield* n();//没有yield*则无法执行
    yield 'd';
    }

    function* n(){
    yield 'b';
    yield 'c'
    }
    for(let v of m()){
    console.log(v);
    }
    //'a' 'b' 'c' 'd'
    //内部的yield会全部执行,按照上下文的顺序

    yield相当于for of的简写形式。yield后跟字符串、数组等含I接口的都会遍历。

G函数的异步应用

JS使用回调函数(重新调用:callback)实现异步编程。
异步即两步,第一步先顺序执行,等拿到了第一步的返回值才会进入第二步,第二步的任务直接单独放在一个函数里,第一步抛出的错误也在第二步作为参数捕捉。
异步回调函数的缺点是多重嵌套情况,就会逻辑混乱。(回调地狱)
Promise对象就是用来解决这个缺点。(.then.then.catch写法)
Promise的缺点是代码冗余。(个人觉得挺清晰的- -)

G函数用来解决这个缺点。
G函数的最大特点是能交出执行权,使当前异步任务暂停。
调用G函数返回一个指针(即遍历器I),G函数内的yield和I接口的next方法的组合技就是恢复执行。(next->执行当前yield->到下一yield暂停)
G函数的缺点是不方便流程管理,难以确认什么时候执行下一阶段。

Thunk函数(T函数)用于解决G函数的缺点。
T函数实现“传名调用”(而JS是传值调用),用来替换作为函数参数的表达式。

语法糖:async函数就是将 G函数的星号(*)替换成async,将yield替换成await,仅此而已。
async函数的返回值是Promise对象。

End.🐧