欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

js迭代器与生成器

程序员文章站 2022-03-27 13:56:36
文章目录前言一、pandas是什么?二、使用步骤1.引入库2.读入数据总结前言es6规范新增了两个高级特性:迭代器和生成器,本节主要阐述迭代器提示:以下是本篇文章正文内容,下面案例可供参考一、pandas是什么?示例:pandas 是基于NumPy 的一种工具,该工具是为了解决数据分析任务而创建的。二、使用步骤1.引入库代码如下(示例):import numpy as npimport pandas as pdimport matplotlib.pyplot as pltimp....


前言

es6规范新增了两个高级特性:迭代器和生成器。迭代器的使用极大的简化了数据的操作, ECMAScript 较早的版本中,执行迭代必须使用循环或其他辅助结构。随着代码量增加,代码会变得越发混乱。很多语言都通过原生语言结构解决了这个问题,开发者无须事先知道如何迭代就能实现迭代操作。这个解决方案就是迭代器模式。Python、Java、C++,还有其他很多语言都对这个模式提供了完备的支持。JavaScript 在 ECMAScript 6 以后也支持了迭代器模式。


一、理解迭代

  1. 在 JavaScript 中,计数循环(for循环)就是一种最简单的迭代:
for (let i = 1; i <= 10; i++) { 
console.log(i); 
}
  1. 在迭代器出现之前,我们迭代对象一般是用迭代语句比如for循环,但是for循环有个弊端,它是通过递增索引,通过下标的方式访问数据,那么这在一定程度上限制了遍历的数据结构,我们在使用for循环的时候必须知道该遍历对象可以通过下标的方式访问值,比如数组和类数组(nodeList,arguments…)这种可以。但一般遍历的目的是为了操作里面的数据,它是什么数据解构我并不关心,我只想要里面的数据。而且总用for循环会显得代码臃肿,不利于维护。
  2. ES5 新增了 Array.prototype.forEach()方法,向通用迭代需求迈进了一步(但仍然不够理想): 这个方法解决了单独记录索引和通过数组对象取得值的问题。不过,没有办法标识迭代何时终止。因此这个方法只适用于数组,而且回调结构也比较笨拙。
let collection = ['foo', 'bar', 'baz']; 
collection.forEach((item) => console.log(item));
// foo 
// bar 
// baz

一、迭代器

1.什么是迭代器

  • 迭代器是一种特殊的对象,所有的迭代器对象都有一个next方法,调用该方法会返回一个结果对象,结果对象有两个属性value,done。迭代器对象还会保存一个内部指针,用来指向当前集合中的位置,迭代器每次调用Next方法都会返回一个可用的值。
  • 下面用ES5的语法创建一个迭代器
   let createIterator = function (data) {
            //i是当前集合中的位置
            let i = 0;
            return {
                next: function () {
                    let done = i >= data.length;
                    let value = done ? undefined : data[i++];
                    return {
                        done: done,
                        value: value
                    }
                }
            }
        }

        let iterator = createIterator([1, 2, 3]);
        console.log(iterator.next());//{done: false, value: 1}
        console.log(iterator.next());//{done: false, value: 2}
        console.log(iterator.next());//{done: false, value: 3}
        console.log(iterator.next());//{done: true, value: undefined}
        //后面的都是
        console.log(iterator.next());//{done: true, value: undefined}

2.可迭代协议

-实现 Iterable 接口(可迭代协议)要求同时具备两种能力:支持迭代的自我识别能力和创建实现Iterator 接口的对象的能力。在ECMAScirpt中,这意味着必须暴露一个属性作为“默认迭代器”,而且这个属性必须使用特殊的 Symbol.iterator 作为键。这个默认迭代器属性必须引用一个迭代器工厂函数(也可以叫生成器后面会提到),调用这个工厂函数必须返回一个新迭代器。
很多内置类型都实现了 Iterable 接口:

  • 字符串
  • 数组
  • 映射
  • 集合
  • arguments
  • NodeList等DOM集合类型
    检查是否存在默认迭代器属性,可以通过"对象[Symbol.iterator]"的方式
// 这两种类型没有实现迭代器工厂函数
console.log(num[Symbol.iterator]); // undefined 
console.log(obj[Symbol.iterator]); // undefined 
let str = 'abc'; 
let arr = ['a', 'b', 'c']; 
let map = new Map().set('a', 1).set('b', 2).set('c', 3); 
let set = new Set().add('a').add('b').add('c'); 
let els = document.querySelectorAll('div'); 
// 这些类型都实现了迭代器工厂函数
console.log(str[Symbol.iterator]); // f values() { [native code] } 
console.log(arr[Symbol.iterator]); // f values() { [native code] } 
console.log(map[Symbol.iterator]); // f values() { [native code] } 
console.log(set[Symbol.iterator]); // f values() { [native code] } 
console.log(els[Symbol.iterator]); // f values() { [native code] } 
// 调用这个工厂函数会生成一个迭代器
console.log(str[Symbol.iterator]()); // StringIterator {} 
console.log(arr[Symbol.iterator]()); // ArrayIterator {} 
console.log(map[Symbol.iterator]()); // MapIterator {} 
console.log(set[Symbol.iterator]()); // SetIterator {} 
console.log(els[Symbol.iterator]()); // ArrayIterator {}

在实际开发中我们不需要获取集合的迭代器来迭代对象,接收可迭代对象的原生语言特性包括:

  • for-of 循环
  • 数组解构
  • 扩展操作符
  • Array.from()
  • 创建集合
  • 创建映射
  • Promise.all()接收由期约组成的可迭代对象
  • Promise.race()接收由期约组成的可迭代对象
  • yield*操作符,在生成器中使用

二、生成器

  • 生成器是返回是一种返回迭代器的函数,它是函数。
  • 生成器就是es6的Generator
  • 生成器通过function后的*来表示,函数中会用到新的关键字yieId
  • 生成器可以暂停函数执行,对异步编程意义重大
    function* createIterator() {
            yield 1;
            yield 2;
            yield 3;
        }

        let iterator = createIterator();
        console.log(iterator.next());//{done: false, value: 1}
        console.log(iterator.next());//{done: false, value: 2}
        console.log(iterator.next());//{done: false, value: 3}
        console.log(iterator.next());//{done: true,value: undefined}
  • 生成器的一个常用功能是生成状态机
 function* createIterator() {
           while (true) {
               yield A;
               yield B;
               yield C;
           }
       }

       let iterator = createIterator();
       console.log(iterator.next());//{done: false, value: A}
       console.log(iterator.next());//{done: false, value: B}
       console.log(iterator.next());//{done: false, value: C}
       console.log(iterator.next());//{done: false, value: A}
       console.log(iterator.next());//{done: false, value: B}
  • 可以往next方法传值,值为上次yield的返回值,给第一个next传值,第一次调用next()方法时无论传入什么参数都会被丢弃。因为第一次执行Next,没有上一个yield。

        function* createIterator() {
            let one = yield 1;
            let two = yield one + 2;
            let three = yield two + 3;
        }

        let iterator = createIterator();
        console.log(iterator.next(2));//{done: false, value: 1} 传入的参数2被忽略
        console.log(iterator.next(4));//{done: false, value: 6} 传入的值是上一次yield的返回值也就是One等于4
        console.log(iterator.next(8));//{done: false, value: 11} 同理
        console.log(iterator.next());//{done: true, value: undefined}
        console.log(iterator.next());//{done: true, value: undefined}
  • 委托生成器
    在某些时候我们需要将多个迭代器合成一个生成器,可以通过yield*的形式
  function* createIterator1() {
            yield 1;
            yield 2;
            yield 3;
        }

        function* createIterator2() {
            yield 4;
            yield 5;
        }

        function* createIterator() {
            yield* createIterator1();
            yield* createIterator2();
        }

        let iterator = createIterator();
        console.log(iterator.next());//{value: 1, done: false} 
        console.log(iterator.next());//{value: 2, done: false} 
        console.log(iterator.next());//{value: 3, done: false} 
        console.log(iterator.next());//{value: 4, done: false}
        console.log(iterator.next());//{value: 5, done: false}
        console.log(iterator.next());//{value: undefined, done: true}
        console.log(iterator.next());//{value: undefined, done: true}
  • 使用 yield实现递归算法:yield最有用的地方是实现递归操作,此时生成器可以产生自身。看下面的例子:
function* nTimes(n) { 
if (n > 0) { 
yield* nTimes(n - 1); 
yield n - 1; 
} 
} 
for (const x of nTimes(3)) { 
console.log(x); 
} 
// 0 
// 1 
// 2

三、探究

字符串

  • 有没有想过为什么可以用for循环遍历字符串
    js迭代器与生成器
    因为字符串是有下标的,也就是说Js中的字符串跟数组很像,可以通过下标的方式去访问其中的每一个值,而for循环就是通过递增下标,去对应的数据结构通过下标访问其中的值,但这里不推荐用for循环遍历字符串,如图:
    js迭代器与生成器
     由于该生僻字没有识别出来,所以双字节字符被视作两个独立的编码单元,从而最终在A与B之间打印出2个�
     所幸,ES6的目标是全面支持Unicode,并且我们可以通过改变字符串的默认迭代器来解决这个问题,使其操作字符而不是编码单元。现在,修改前一个示例中字符串的默认迭代器,让for-of循环输出正确的内容,因为js中的字符串默认实现了[Symbol.iterator]所以可以使用for of 想使用for of必须实现[Symbol.iterator]
     
    js迭代器与生成器
    前者一阵子改了一个bug也是字符集的事,添加了一个font-family才把生僻字显示出来
  • 为什么通过扩展运算符字符串可直接转为数组?就是因为字符串默认实现了迭代器
    js迭代器与生成器

对象

  • 前面也提到过了,js中的对象是没有默认实现迭代器的,所以不能使用for of循环,那我们手动实现一下迭代器是不是就可以用了呢?答案是是的。
let obj = {
            a: 1,
            b: 2,
            c: 10,
            d: 100,
            [Symbol.iterator]: function* () {
                const arr = Object.keys(obj);
                let len = arr.length;
                let i = 0;
                while (i < len) {
                    yield this[arr[i++]]
                }
            }
        };
        for (let item of obj) {
            console.log(item);
        }

        obj.e = 1000;
        obj.f = 100000;
        for (let item of obj) {
            console.log("第二次",item);
        }

js迭代器与生成器

  • 通过扩展运算符将对象转换成数组
 let obj = {
           a: 1,
           b: 2,
           c: 10,
           d: 100,
           [Symbol.iterator]: function* () {
               const arr = Object.keys(obj);
               let len = arr.length;
               let i = 0;
               while (i < len) {
                   yield this[arr[i++]];
               }
           }
       };
       let arrList = [...obj];
       console.log("arrList", arrList);

js迭代器与生成器

总结

暂时想到这么多,后续可能会补充。

本文地址:https://blog.csdn.net/weixin_46773434/article/details/110433173