3、for…of使用

3.1 for…of使用

for...of 一种用于遍历数据结构的方法。它可遍历的对象包括数组,对象,字符串,set和map结构等具有iterator 接口的数据结构。

我们先来看看几种传统的遍历数组的方式以及它们的缺陷:

方式一:

方式一利用for循环来遍历数组的缺点就是:代码不够简洁。

方式二:

利用forEach循环代码量少了很多,写法更加简洁,缺点就是:无法中断停止整个循环。

方式三:

for...in循环更常用于对象的循环,如果用于数组的循环,那么就要注意了,上述代码中每次循环中得到的i是字符串类型,而不是预料中的数字类型,要想对它进行运算,那得先要进行类型转换,造成不方便。

那我们来看看for...of的是实现:

for...of的优势:

  1. 写法比for循环简洁很多;
  2. 可以用break来终止整个循环,或者continute来跳出当前循环,继续后面的循环;

结合keys( )获取到循环的索引,并且是数字类型,而不是字符串类型。

3.2 循环可以终止

以上案例:用break实现了终止整个循环,不会继续后面的遍历,所以打印结果为:1  2。

可以跳过当前循环:

用continue跳过当前循环,继续后面的循环,所以打印结果为:1  2  4  5。

3.3得到数字类型索引

使用数组的扩展keys( ),获取键名再遍历,得到的index是数字类型的。

此外,相比于for...in循环专门为对象设计,for...of循环的适用范围更广。

3.4遍历字符串

3.5 遍历DOM list

for...of支持类数组的遍历,例如DOM List。

for...of支持set和map解构的遍历。

Iterator遍历

4.1 for…of为什么不遍历object对象

我们讲了简单又实用的for...of,我们可以使用它来遍历数组,字符串,Set和Map结构,但是有没有发现,我们并没有说它可以遍历Object对象,为什么不试试用它来遍历Object对象呢?

//定义一个的Object对象
let obj = {"name":"前端君"};
//咱们来for...of一下
for(let v of obj){
    console.log(v);
}
//结果:报错
//错误提示:obj[Symbol.iterator]不是一个function

oh no,程序报错了,for...of根本不支持遍历普通的Object对象,还出现了错误提示:obj[Symbol.iterator]不是一个function,这是什么鬼?

为什么数组,Set和Map结构又可以支持for...of的遍历呢?

原来,要想能够被for...of正常遍历的,都需要实现一个遍历器Iterator。而数组,Set和Map结构,早就内置好了遍历器Iterator(又叫迭代器),它们的原型中都有一个Symbol.iterator方法;而Object对象并没有实现这个接口,使得它无法被for...of遍历。

那么,我们就亲自来验证一下,它们的原型中到底是不是有个叫Symbol.iterator的方法:

//数组
   Array.prototype[Symbol.iterator];
   //结果:function values(){...}    //字符串
   String.prototype[Symbol.iterator];
   //结果:function [Symbol.iterator](){...}    //Set结构
   Set.prototype[Symbol.iterator];
   //结果:function values(){...}    //Map结构
   Map.prototype[Symbol.iterator];
   //结果:function entries(){...}    //Object对象
   Object.prototype[Symbol.iterator];
   //结果:undefined

从上往下看,确实,唯独Object对象的原型上没有Symbol.iterator,返回了:undefined。其他的数据类型的原型上都含有一个名字叫Symbol.iterator的方法Function。

注意:Symbol.iterator 是Symbol 对象的 iterator 属性,是一个特殊的Symbol值,因此,当它作为prototype对象属性名的时候,获取它的时候需要使用[ ]的形式: prototype[Symbol.iterator],不能使用点形式获取:prototype.Symbol.iterator。

也就说,只要一个数据结构拥有一个叫[Symbol.iterator]()方法的数据结构,就可以被for...of遍历,我们称之为:可遍历对象。比如:数组,字符串,Set和Map结构。

现在你该知道为什么文章第一个案例会出现错误提示:obj[Symbol.iterator]不是一个function 了吧,因为Object对象的原型上压根就没有[Symbol.iterator]() 方法啊。

4.2Iterator原理

当可遍历对象被for...of遍历的时候,[Symbol.iterator]()就会被调用,返回一个iterator对象。其中还有一个很重要的方法:next( );

  //数组:一个可遍历对象
   let arr = ['a','b','c'];
   //调用数组的Symbol.iterator()方法
   let iter = arr[Symbol.iterator]();    iter.next();
   //结果:{value: "a", done: false}    iter.next();
   //结果:{value: "b", done: false}    iter.next();
   //结果:{value: "c", done: false}    iter.next();
   //结果:{value: undefined, done: true}

第1次调用next( )方法:返回数组的第1个元素:“a”,以及done的值为fasle,表示循环没有结束,继续遍历。

第2次调用next( )方法:返回数组的第2个元素:“b”,以及done的值还是为fasle,表示循环没有结束,继续遍历。

第3次调用next( )方法:返回数组的第3个元素:“c”,以及done的值依然为fasle,表示循环没有结束,继续遍历。

第4次调用next( )方法:返回的value值为undefined,以及done的值变成了true,表示遍历结束。

原来,for...of的原理就是:先调用可遍历对象的[Symbol.iterator]( )方法,得到一个iterator遍历器对象,然后就在遍历器上不断调用next( )方法,直到done的值为true的时候,就表示遍历完成结束了。

4.3 自定义Iterator遍历

既然有了[Symbol.iterator]()方法就算是可遍历对象,那么我给Object对象手动加上一个[Symbol.iterator]()方法,那么它是不是可以被for...of遍历了?

那我们就试试看,给一个Object对象加一个[Symbol.iterator]( )方法,看它是不是就能被for...of遍历了?

 //定义一个的Object对象
   let obj = {
        0:"我是0",
        1:"我是1",
        2:"我是2",
        length:3,
        //添加[Symbol.iterator]方法
       [Symbol.iterator] : function() {
            let _this = this;
            let index = 0;
            return {
                next:() => {
                    let value = _this[index];
                    let done = (index >= _this.length);
                    index++;
                    return {value,done}
                }
            }
        }
   };    //咱们来for...of一下
   for(let v of obj){
        console.log(v);
   }
   //结果:"我是0"
   //      "我是1"
   //      "我是2"

上面这个案例也许你看了觉得很复杂,没看懂,没关系,我一起来分析它的结构就够了!

我们定义了一个Object对象,同时给它添加了[Symbol.iterator]()方法,并在[Symbol.iterator]()方法实现了next( )方法,next( )方法返回的对象包含了value属性和done属性。

具体细节如果不理解没关系,我们确实看到了给Object对象加上了[Symbol.iterator]()方法后,最后确实能被for...of遍历了。

这就是说,我们可以创建一个可遍历的对象,并且自定义它的遍历行为。或者说可以通过添加[Symbol.iterator]()方法,把一个不可遍历的Object对象,变成可遍历的对象。

4.4Iterator遍历价值

新特性for...of之所以能够遍历各种不同的数据结构,正是因为这个数据结构都实现了Iterator遍历器接口,供for...of遍历。如果没有实现Iterator接口,则该数据结构无法被for...of遍历,比如:普通的Object对象。

Generator函数

5.1 声明Generator函数

Generator函数,又称生成器函数,是ES6的一个重要的新特性。

     //声明一个Hello的Generator函数
   function* Hello(name) {
       yield `hello ${name}`;
       yield `how are you`;
       yield `bye`;
   }

上面这个就是Generator函数,乍一看,是不是跟普通的函数没什么两样?确实很像,但是我们要知道它有两个重要的区别:

  1. 普通函数用function来声明,Generator函数用function*声明。

Generator函数函数内部有新的关键字:yield( 产出),普通函数没有。

5.12调用Generator函数

带着这两个疑问我们往下看,我们试着就调用一下这个名字叫Hello的Generator函数,看看会发生什么:

 //声明一个Hello的Generator函数
   function* Hello(name) {
       yield `hello ${name}`;
        yield `how are you`;
        yield `bye`;
   }    //调用Hello函数
   let ite = Hello('前端君');
   //结果:[object Generator]    ite.next();
   //{value: "hello 前端君", done: false}    ite.next();
   //{value: "how are you", done: false}    ite.next();
   //{value: "bye", done: false}    ite.next();
   //{value: undefined, done: true}

看到这里,估计你也看到了一个熟悉的面孔:next()方法。一开始,我们调用Hello(“前端君”),函数执行后,返回了一个:[object Genrator]生成器对象,我们把它赋值到变量ite中,仅此而已,并没有做太多的事情。

接着,第1次调用生成器对象ite的next( )方法,返回了一个对象:

 {value: "hello 前端君", done: false}

第2次调用生成器对象ite的next( )方法,同样得到了一个对象:

  {value: "how are you", done: false}

第3次调用生成器对象ite的next( )方法,又得到了一个对象:

  {value: "bye", done: false}

直到,第4次调用生成器对象ite的next( )方法,返回的对象:

  {value: undefined, done: true}

看到这里有没有发现,这里生成器的next( )方法的和遍历器iterator的next( )方法的返回结果是不是一样?

没错,你可以把Generator函数被调用后得到的生成器理解成一个遍历器iterator,用于遍历函数内部的状态。

5.3 Generator函数行为

通过上面的案例,我们知道了:Generator函数被调用后并不会一直执行到最后,它是先回返回一个生成器对象,然后hold住不动,等到生成器对象的next( )方法被调用后,函数才会继续执行,直到遇到关键字yield后,又会停止执行,并返回一个Object对象,然后继续等待,直到next( )再一次被调用的时候,才会继续接着往下执行,直到done的值为true。

5.4 yield语句的使用

而yield在这里起到了十分重要的作用,就相当于暂停执行并且返回信息。有点像传统函数的return的作用,但不同的是普通函数只能return一次,但是Generator函数可以有很多个yield。而return代表的是终止执行,yield代表的是暂停执行,后续通过调用生成器的next( )方法,可以恢复执行。

5.5 next方法接收参数

此外,next( )方法还可以接受一个参数,它的参数会作为上一个yield的返回值,我们来看一下:

     //声明一个Hello的Generator函数
   function* Hello() {
        let res = yield `hello`;
       yield res;
   }
   
   let iterator = Hello();
   //结果:一个生成器对象    iterator.next();
   //结果:{value: "hello", done: false}    iterator.next("前端君");
   //结果:{value: "前端君", done: false}

注意函数体内的第一个yield关键字,我们把它的返回值赋值给了一个变量res。

再看2次next方法的调用:

第1次调用next( )方法,返回的对象属性value值为“hello”,属性done值为:fasle,并暂停执行。

第2次next( )方法,传入参数:字符串“前端君”。此时,第二个yield关键字紧跟着的是变量res,而变量res的值正是上一个关键字yield的返回值。也就是说这个值正是我们传入的参数:“前端君”。因为:next( )的参数会作为上一个yield的返回值。

5.6 关键字‘yield*

在一个Generator函数里面,如果我们想调用另一个Generator函数,就需要用到的关键字是:yield*。

我们来看看怎么玩,代码有点长,但是很好理解:

     //声明Generator函数:gen1   
   function* gen1() {
        yield "gen1 start";
        yield "gen1 end";
   }    //声明Generator函数:gen2
   function* gen2() {
        yield "gen2 start";
        yield "gen2 end";
   }    //声明Generator函数:start
   function* start() {
        yield "start";
        yield* gen1();
        yield* gen2();
        yield "end";
   }    //调用start函数
   var ite = start();
   //创建一个生成器
   
   ite.next();
   //{value: "start", done: false}    ite.next();
   //{value: "gen1 start", done: false}    ite.next();
   //{value: "gen1 end", done: false}    ite.next();
   //{value: "gen2 start", done: false}    ite.next();
   //{value: "gen2 end", done: false}    ite.next();
   //{value: "end", done: false}
   

我们主要看start( )这个Generator函数,其中有两句代码:

     yield* gen1();
   yield* gen2();

这里使用了关键字yield*来实现调用另外两个Generator函数。从后面的多个next( )方法得到的结果看,我们可以知道:

如果一个Generator函数A执行过程中,进入(调用)了另一个Generator函数B,那么会一直等到Generator函数B全部执行完毕后,才会返回Generator函数A继续执行。

5.7 Generator函数用途

以上就是对Generator函数的讲解介绍,它是ES6的一个很重要的新特性。它可以控制函数的内部状态,依次遍历每个状态;可以根据需要,轻松地让函数暂停执行或者继续执行。

根据这个特点,我们可以利用Generator函数来实现异步操作的效果。

原理是:利用Generator函数暂停执行的作用,可以将异步操作的语句写到yield后面,通过执行next方法进行回调。

随机推荐

  1. 数据可视化之DAX篇(十七)Power BI表格总计行错误的终极解决方案

    https://zhuanlan.zhihu.com/p/68183990 我在知识星球收到的问题中,关于表格和矩阵(以下统称表格)总计行错误算是常见的问题之一了,不少初学者甚为不解,在Excel透视 ...

  2. Flask 基础组件(九):请求扩展

    #!/usr/bin/env python # -*- coding:utf-8 -*- from flask import Flask, Request, render_template app = ...

  3. 数据可视化之DAX篇(二十八)Power BI时间序列分析用到的度量值,一次全给你

    https://zhuanlan.zhihu.com/p/88528732 在各种经营分析报告中,我们常常会看到YTD,YOY这样的统计指标,这样的数据计算并不难,尤其是在PowerBI中,因为有时间 ...

  4. bzoj1745[Usaco2005 oct]Flying Right 飞行航班*

    bzoj1745[Usaco2005 oct]Flying Right 飞行航班 题意: n个农场,有k群牛要从一个农场到另一个农场(每群由一只或几只奶牛组成)飞机白天从农场1到农场n,晚上从农场n到 ...

  5. P2060 马步距离(洛谷)

    我们无论遇到什么困难,都不要拖,微笑着面对他,战胜拖延的最好方法就是面对拖延. 今天又拖延了…… 早晨听完老师讲课,本想做一道题练练手的,结果因为懒,瘫了一上午.最后在固定的刷题时间去面对了这道题,然 ...

  6. 搞定 CompletableFuture,并发异步编程和编写串行程序还有什么区别?你们要的多图长文

    你有一个思想,我有一个思想,我们交换后,一个人就有两个思想 If you can NOT explain it simply, you do NOT understand it well enough ...

  7. 利用74HC595实现的流水灯 Arduino

    int big = 2; int push = 3; int datain = 4; void setup() { Serial.begin(9600); pinMode(big, OUTPUT); ...

  8. SSM框架整合的最新打开方式(打造最详细的SSM整合教程)

    SSM整合 文章已托管到GitHub,大家可以去GitHub查看阅读,欢迎老板们前来Star!搜索关注微信公众号 [码出Offer] 领取各种学习资料! SSM 一.创建一个Maven项目 File ...

  9. selenium 破解登录滑块验证码mu

    from selenium import webdriverfrom PIL import Imagefrom selenium.webdriver import ChromeOptionsfrom ...

  10. sql数据管理语句

    一.数据管理 1.增加数据 INSERT INTO student VALUES(1,'张三','男',20); -- 插入所有字段.一定依次按顺序插入 -- 注意不能少或多字段值 如只需要插入部分字 ...