美文网首页
7 es6 迭代器 iterator , 生成器 generat

7 es6 迭代器 iterator , 生成器 generat

作者: wudimingwo | 来源:发表于2018-12-07 16:10 被阅读0次

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

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

特点

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

写数组的迭代器

          let arr = [1,2,3,4,5];
          
          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()
          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

            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
            for(let [key,value] of mapentry) {
              console.log(key,value);
            }
image.png

for of 调用的就是 next方法

            for(let [key,value] of mapentry) {
              console.log(key,value);
            }
            mapentry.next()
image.png

keys() , values() 类似

           let arrkeys = arr.keys();
           let mapkeys = map.keys();
           let setkeys = set.keys();
            
            for(let key of mapkeys) {
              console.log(key);
            }
            mapkeys.next()
image.png
           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

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

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

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

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

            for(let key of mapkeys) {
              console.log(key);
            }
            for(let key of mapkeys) {
              console.log(key,123);
            }
image.png

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

            for(let item of arr){
              console.log(item);
            }
            for(let item of arr){
              console.log(item,123);
            }
image.png

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


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

默认调用的规则

            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
          
          console.log(str[0]);/ a
          console.log(str[1]);/无法输出
          console.log(str[2]);/无法输出
          console.log(str[3]);/ b
image.png

可以用迭代器的 for of?

          let str = "a𠮷b";
          console.log(str);
          for(let key of str) {
            console.log(key);
          }
image.png

不能用 for in

          for(let key in str) {
            console.log(str[key]);
          }
image.png

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

          let arr = str.split('');
          console.log(arr);
          for(let key in arr) {
            console.log(arr[key]);
          }
image.png

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


生成器 generator

用于生成 迭代器

          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, 会执行前一个指针到这一个指针之间的代码块

          function *createIterator () {
            let i;
            yield i = 1;
            console.log(i);
            yield i = 2;
            console.log(i);
            yield 3;
            
          }
image.png

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

          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循环
          function *createIterator (arr = []) {
            
            arr.forEach(function (item) {
                yield item;
            })
          }
          
          i = createIterator([1,2,3,,4,5]);
image.png

可以看出

  • 不允许函数嵌套
  • 不允许在普通函数中使用 yield
          function *createIterator () {
            
            yield 1;
            yield 2;
            yield 3;
            return 8;
            yield 4;
            
          }
          
          i = createIterator();
image.png

可以看出

  • 遇到return 后面的yield 将失去指针作用
  • 默认情况可以认为所有yield 指针之后 有个 return undefined 用来终止
  • 可以认为 return 能够停止 next

可以测试一下

          function *createIterator () {
            let flag = true;
            let i = 0;
            while (flag){
                i++;
                yield i;
                if (i > 3) {
                  return
                }
            }
            
          }
          
          i = createIterator();
image.png

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

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

          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)
          }

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

          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);
          }
          
          let process = createIterator();
image.png

可以理解为

  • 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);
            
            aaa = yield 3;
            console.log('d');
            aaa.next(aaa);
            
          }
          
          let process = createIterator();
image.png

不行, 逻辑上就有问题, 问题在于, 我的指针需要从1 到达2
但是在到达2之前,又开启了一个指针移动命令, 从逻辑上就有问题.

           function *createIterator () {
             let aaa;
             console.log('a');
            aaa = yield 1;
            setTimeout(function () {
              aaa.next(aaa);
                
            },0)
            console.log('b');
            aaa = yield 2;
            console.log('c');
            setTimeout(function () {
              aaa.next(aaa);
              
            },0)
            
            aaa = yield 3;
            console.log('d');
            setTimeout(function () {
              aaa.next(aaa);
              
            },0)
            
          }
          
          let process = createIterator();
          process.next(process);
          process.next(process);

用setTimeout 勉强能够实现这个内部自动指针向下走,


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

          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()}的时候, 尽管在字符串模板里, 他也正常停止了.
  • 丁老师秀的我确实感觉这就是另一门语言..
    疑问? 不是模板字符串, 而是普通字符是否也可以?
           function *createIterator () {
//          let result = `${yield first()}${yield second()}${yield third()}`;
            let result = '' + (yield first()) +  (yield second()) + (yield third());
            console.log(result);
          }
image.png

委托迭代器

          function *pro (arr,string) {
            yield * arr;
            yield * string;
          }
          let i = pro([1,2,3,4],'abcdef');
image.png
          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]的指针合在了一起?

相关文章

网友评论

      本文标题:7 es6 迭代器 iterator , 生成器 generat

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