7 es6 迭代器 iterator , 生成器 generator

"Hello World, Hello Blog"

Posted by wudimingwo on December 15, 2018

1.相比for循环, forEach的优点在哪里?

  • 语义化更好
  • 需要管理维护的变量变少.
  • 多层循环时更加明显.
  • 复杂度降低
  1. 什么是迭代器?
    • 迭代器是专门为可迭代对象设计的统一接口, 用来遍历数据

特点

  • 每个迭代器都有next方法, 每次执行next方法,都会返回结果对象{value,done}
  • value: 每次迭代的数据
  • done: 迭代是否结束
  • 每个迭代器都有专用指针,开始时指向数据结构的第一个值,每次调用next()指针向下移一位
  • 每个迭代器支持for of 循环

写数组的迭代器 ``` let arr = [1,2,3,4,5];

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
      function createIterater (arr = []) {
      	let nextIndex = 0;
      	return {
      	  next(){
      	    if (arr.length - 1 < nextIndex ) {
      	    	return {
      	    	  value : undefined,
      	    	  done : true
      	    	}
      	    }else{
      	      return {
      	         value : arr[nextIndex++],
      	         done : false
      	      }
      	    }
      	  }
      	  
      	}
      }
      
      let nod = createIterater(arr); ```

##es6提供的 迭代器接口

  1. arr,map,set
    • entries()
    • keys()
    • values()
      1
      2
      3
      4
      5
      6
      7
      8
      9
      
           let arr = [1,2,3];
           let map = new Map([[1,2],['name','mike'],['age',18]]);
           let set = new Set(arr);
           console.log(map);
           console.log(set);
                
            let arrentry = arr.entries();/[index,value]
            let mapentry = map.entries();/[key,value]
            let setentry = set.entries();/[key,key]
      

      支持 for of

      1
      2
      3
      4
      5
      6
      7
      8
      9
      
             for(let item of arrentry) {
               console.log(item);
             }
             for(let item of mapentry) {
               console.log(item);
             }
             for(let item of setentry) {
               console.log(item);
             }
      

      image.png

      1
      2
      3
      
             for(let [key,value] of mapentry) {
               console.log(key,value);
             }
      

      image.png for of 调用的就是 next方法

      1
      2
      3
      4
      
             for(let [key,value] of mapentry) {
               console.log(key,value);
             }
             mapentry.next()
      

      image.png

keys() , values() 类似

1
2
3
4
5
6
7
8
           let arrkeys = arr.keys();
           let mapkeys = map.keys();
           let setkeys = set.keys();
            
            for(let key of mapkeys) {
              console.log(key);
            }
            mapkeys.next()

image.png

1
2
3
4
5
6
7
8
           let arrvalues = arr.values();
           let mapvalues = map.values();
           let setvalues = set.values();
            
            for(let value of mapvalues) {
              console.log(value);
            }
            mapvalues.next()

image.png ——————– 每个可迭代的数据,都可以用for of

1
2
3
            for(let value of arr) {
              console.log(value);
            }

实际上是通过arr的默认迭代器接口 所有的迭代器都有 Symbal.iterator , 必须以中括号方式访问[Symbal.iterator],返回默认迭代器

1
2
3
4
            for(let value of arr[Symbol.iterator]()) {
              console.log(value);
            }

疑问 我们很容易发现一个事实, 调用next之后, 指针只能向后移动 假设我们对一个arr迭代器进行了for of 遍历, next走完指针 请问, 如果我们对该 arr 再来一次 for of 循环会如何? 能否遍历? 不能

1
2
3
4
5
6
            for(let key of mapkeys) {
              console.log(key);
            }
            for(let key of mapkeys) {
              console.log(key,123);
            }

image.png 疑问 既然无法用 for of 遍历同一个迭代器两次, 请问for of 能遍历同一个 arr 两次嘛? 可以

1
2
3
4
5
6
            for(let item of arr){
              console.log(item);
            }
            for(let item of arr){
              console.log(item,123);
            }

image.png 疑问, 可以认为每次for of 遍历 arr的时候, 实际上每次生成的 arrSymbal.iterator 都是新的迭代器. 合理 因为形式上函数每次都执行, 返回新的迭代器对象.


迭代原理 我们可以知道指针的移动必然是线性遍历 数组中 迭代器的指针, 每次指向下一个指针 map set 中 会把邻接链表转换成单向链表

默认调用的规则

1
2
3
4
5
6
7
8
9
10
            for(let item of arr){/arr[Symbol.iterator]() => 调用的规则是 arr.values();
              console.log(item);
            }
            for(let item of map){/map[Symbol.iterator]() => 调用的规则是 map.entries();
              console.log(item,123);
            }
            for(let item of set){/set[Symbol.iterator]() => 调用的规则是 set.values();
              console.log(item,123);
            }

image.png image.png ——————

对象是不可迭代的, 因为顺序不确定?


字符串的迭代 双字节词 ``` let str = “a𠮷b”; console.log(str); console.log(str.length);/4

1
2
3
4
5
6
7
8
9
10
11
12
      console.log(str[0]);/ a
      console.log(str[1]);/无法输出
      console.log(str[2]);/无法输出
      console.log(str[3]);/ b ``` ![image.png](https://upload-images.jianshu.io/upload_images/13637909-90c71c36074cacdc.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 可以用迭代器的 for of? ```
      let str = "a𠮷b";
      console.log(str);
      for(let key of str) {
        console.log(key);
      } ``` ![image.png](https://upload-images.jianshu.io/upload_images/13637909-0d77e9f2c9454083.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 不能用 for in ```
      for(let key in str) {
        console.log(str[key]);
      } ``` ![image.png](https://upload-images.jianshu.io/upload_images/13637909-653cf2337571865f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

疑问能不能转换成数组再遍历? 不能

1
2
3
4
5
          let arr = str.split('');
          console.log(arr);
          for(let key in arr) {
            console.log(arr[key]);
          }

image.png 也就是说, split 遇到双字节字体的时候, 是失效的


##生成器 generator 用于生成 迭代器

1
2
3
4
5
6
7
8
9
10
11
12
          function *createIterator () {
          	console.log("first");
          	yield 1;
          	console.log("second");
          	yield 2;
          	console.log("third");
          	yield 3;
          	console.log("over");
          	
          }
          
          i = createIterator();

image.png

可以看出 yield 是指针,每次 执行next, 会执行前一个指针到这一个指针之间的代码块

1
2
3
4
5
6
7
8
9
          function *createIterator () {
          	let i;
          	yield i = 1;
          	console.log(i);
          	yield i = 2;
          	console.log(i);
          	yield 3;
          	
          }

image.png

可以看出 yield 可以返回一个变量 可以看出这些代码块之间是同一个作用域.

1
2
3
4
5
6
7
8
9
10
11
          function *createIterator (arr = []) {
            console.log('start');
          	for(let i = 0;i < arr.length;i++) {
          	  console.log(arr[i]);
          	  yield arr[i]
          	}
          	
          }
          
          i = createIterator([1,2,3,,4,5]);
          

image.png

可以看出

  • 可以用for循环
  • for循环里面的代码被认为是两个yield之间的代码块, (也正常)
  • yield 值 是 undefined 并不会导致迭代停止.
  • yield 能暂停for循环
1
2
3
4
5
6
7
8
          function *createIterator (arr = []) {
            
            arr.forEach(function (item) {
            	yield item;
            })
          }
          
          i = createIterator([1,2,3,,4,5]);

image.png

可以看出

  • 不允许函数嵌套
  • 不允许在普通函数中使用 yield ``` function *createIterator () {
1
2
3
4
5
6
7
8
9
        yield 1;
        yield 2;
        yield 3;
        return 8;
        yield 4;
        
      }
      
      i = createIterator(); ``` ![image.png](https://upload-images.jianshu.io/upload_images/13637909-089e56980dea617f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) > 可以看出 > * 遇到return 后面的yield 将失去指针作用 > * 默认情况可以认为所有yield 指针之后 有个 return undefined 用来终止 > *可以认为 return 能够停止 next

可以测试一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
          function *createIterator () {
            let flag = true;
            let i = 0;
            while (flag){
            	i++;
            	yield i;
            	if (i > 3) {
            	  return
            	}
            }
            
          }
          
          i = createIterator();

image.png


##高级部分, 可以把异步的描述成同步的!

问题是这样的, 我们常常有异步处理事情的时候, 但异步处理事情时, 我们还需要控制执行顺序 异步当中解决执行顺序的最基本的方案就是回调函数.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
          function first () {
            console.log('first ing');
          	setTimeout(function () {
          		console.log("first");
          		second();
          	},1000)
          }
          function second () {
            console.log('second ing');
          	setTimeout(function () {
          		console.log("second");
          		third();
          	},2000)
          }
          function third () {
            console.log('third ing');
          	setTimeout(function () {
          		console.log("third");
          	},1000)
          }

但这么写, 就要求我们每个函数都要清楚的知道, 自己下一个回调函数是谁, 依赖图不够直观, 我们想把 嵌套的变成线性的, 或者看起来像是线性的. 这也很合理, 因为执行顺序是依次的, 所以应该可以表现成线性的. 利用 生成器,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
          function first () {
            console.log('first ing');
          	setTimeout(function () {
          		console.log("first");
          		process.next();
          	},1000)
          }
          function second () {
            console.log('second ing');
          	setTimeout(function () {
          		console.log("second");
          		process.next();
          	},2000)
          }
          function third () {
            console.log('third ing');
          	setTimeout(function () {
          		console.log("third");
          	},1000)
          }
          
           function *createIterator (arr = []) {
            yield first();
            yield second();
            yield third();
          }
          
          let process = createIterator();

异步执行顺序在createIterator 中可以看得很清楚 而在各自的回调函数中则不必关心具体的下一个回调是谁.


yield的返回值? (我觉得叫做返回值稍微不恰当) ``` function *createIterator () { let aaa; aaa = yield 1; console.log(aaa); aaa = yield 2; console.log(aaa); aaa = yield 3; console.log(aaa); }

1
      let process = createIterator(); ``` ![image.png](https://upload-images.jianshu.io/upload_images/13637909-fdc4547f6b2f15ff.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) > 可以理解为 > * aaa = yield 1 可以看成是 设置了一个形参,用来接收下一次next()时传的参数 > * aaa 的值 是有 next( )传入的参数决定.

疑问, 可以看出, createIterator 函数内部, 是无法实现指针的移动的, 需要靠 实例的next, 我们有没有可能让开启一个就自动往下移动指针执行? 通过 上面的参数通道, 我刚开始以为是可以的. ``` function *createIterator () { let aaa; console.log(‘a’); aaa = yield 1; aaa.next(aaa); console.log(‘b’); aaa = yield 2; console.log(‘c’); aaa.next(aaa);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
        aaa = yield 3;
        console.log('d');
        aaa.next(aaa);
        
      }
      
      let process = createIterator(); ``` ![image.png](https://upload-images.jianshu.io/upload_images/13637909-4f588825f8ee5f09.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) > 不行, 逻辑上就有问题, 问题在于, 我的指针需要从1 到达2 > 但是在到达2之前,又开启了一个指针移动命令, 从逻辑上就有问题. ```
       function *createIterator () {
         let aaa;
         console.log('a');
        aaa = yield 1;
        setTimeout(function () {
          aaa.next(aaa);
        	
        },1000)
        console.log('b');
        aaa = yield 2;
        console.log('c');
        setTimeout(function () {
          aaa.next(aaa);
          
        },1000)
        
        aaa = yield 3;
        console.log('d');
        setTimeout(function () {
          aaa.next(aaa);
          
        },1000)
        
      }
      
      let process = createIterator(); ``` > 用setTimeout 勉强能够实现这个内部自动指针向下走, > 但这么弄没意思. > 除非我们能够监听指针走没走完. > 能监听指针走完嘛?

下面这个例子, 丁老师突然间秀的当真是令人措手不及.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
          function first () {
            console.log('first ing');
          	setTimeout(function () {
          		console.log("first");
          	},1000)
          }
          function second () {
            console.log('second ing');
          	setTimeout(function () {
          		console.log("second");
          	},2000)
          }
          function third () {
            console.log('third ing');
          	setTimeout(function () {
          	  console.log("third");
          	},1000)
          }
          
           function *createIterator () {
            let result = `${yield first()}${yield second()}${yield third()}`;
            console.log(result);
          }
          
          let process = createIterator();

image.png

可以看到

  • 再次可以看到第一次执行next 的时候, 是无法接收传入的参数的.
  • 这里模板字符串用得非常的6
  • 用模板字符串接受了,传进来的参数
  • 在模板字符串的变量区里直接定义了代码,
  • , 读到 ${yield first()}的时候, 尽管在字符串模板里, 他也正常停止了.
  • 丁老师秀的我确实感觉这就是另一门语言.. 疑问? 不是模板字符串, 而是普通字符是否也可以?
    1
    2
    3
    4
    5
    
             function *createIterator () {
    //          let result = `${yield first()}${yield second()}${yield third()}`;
              let result = '' + (yield first()) +  (yield second()) + (yield third());
              console.log(result);
            }
    

    image.png


委托迭代器

1
2
3
4
5
          function *pro (arr,string) {
          	yield * arr;
          	yield * string;
          }
          let i = pro([1,2,3,4],'abcdef');

image.png

1
2
3
4
5
6
7
8
9
          function *pro (arr,string) {
          	yield * arr;
          	yield * string;
          }
          let i = pro([1,2,3,4],'abcdef');
          
          for(let key of i){
            console.log(key);
          }

image.png

可以理解为

  • 把arr[Symbol.iterator] 里的指针和 arr[Symbol.iterator]的指针合在了一起?