for…of使用

3、for…of使用

3.1 for…of使用

for…of 一種用於遍曆數據結構的方法。它可遍歷的對象包括數組,對象,字符串,setmap結構等具有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支持setmap解構的遍歷。

 

Iterator遍歷

 

4.1 for…of為什麼不遍歷object對象

 

我們講了簡單又實用的for…of,我們可以使用它來遍曆數組,字符串,SetMap結構,但是有沒有發現,我們並沒有說它可以遍歷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,這是什麼鬼?

 為什麼數組,SetMap結構又可以支持for…of的遍歷呢?

原來,要想能夠被for…of正常遍歷的,都需要實現一個遍歷器Iterator。而數組,SetMap結構,早就內置好了遍歷器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遍歷,我們稱之為:可遍歷對象。比如:數組,字符串,SetMap結構。

        現在你該知道為什麼文章第一個案例會出現錯誤提示: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遍歷了?

 

 1 //定義一個的Object對象
 2     let obj = {
 3         0:"我是0",
 4         1:"我是1",
 5         2:"我是2",
 6         length:3,
 7         //添加[Symbol.iterator]方法
 8         [Symbol.iterator] : function() {
 9             let _this = this;
10             let index = 0;
11             return {
12                 next:() => {
13                     let value = _this[index];
14                     let done = (index >= _this.length);
15                     index++;
16                     return {value,done}
17                 }
18             }
19         }
20     };
21 
22     //咱們來for...of一下
23     for(let v of obj){
24         console.log(v);
25     }
26     //結果:"我是0"
27     //      "我是1"
28     //      "我是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的一個重要的新特性。

 

1     //聲明一個Hello的Generator函數
2     function* Hello(name) {
3         yield `hello ${name}`;
4         yield `how are you`;
5         yield `bye`;
6     }

 

上面這個就是Generator函數,乍一看,是不是跟普通的函數沒什麼兩樣?確實很像,但是我們要知道它有兩個重要的區別:

  1. 普通函數用function來聲明,Generator函數用function*聲明。

Generator函數函數內部有新的關鍵字:yield 產出),普通函數沒有。

5.12調用Generator函數

帶着這兩個疑問我們往下看,我們試着就調用一下這個名字叫HelloGenerator函數,看看會發生什麼:

 

 1 //聲明一個Hello的Generator函數
 2     function* Hello(name) {
 3         yield `hello ${name}`;
 4         yield `how are you`;
 5         yield `bye`;
 6     }
 7 
 8     //調用Hello函數
 9     let ite = Hello('前端君');
10     //結果:[object Generator]
11 
12     ite.next();
13     //{value: "hello 前端君", done: false}
14 
15     ite.next();
16     //{value: "how are you", done: false}
17 
18     ite.next();
19     //{value: "bye", done: false}
20 
21     ite.next();
22     //{value: undefined, done: true}

 

看到這裡,估計你也看到了一個熟悉的面孔:next()方法。一開始,我們調用Hello(「前端君」),函數執行後,返回了一個:[object Genrator]生成器對象,我們把它賦值到變量ite中,僅此而已,並沒有做太多的事情。

        接着,第1次調用生成器對象itenext( )方法,返回了一個對象:

 

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

 

  2次調用生成器對象itenext( )方法,同樣得到了一個對象:

 

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

 

3次調用生成器對象itenext( )方法,又得到了一個對象:

 

1  {value: "bye", done: false}

 

 直到,第4次調用生成器對象itenext( )方法,返回的對象:

 

1  {value: undefined, done: true}

 

看到這裡有沒有發現,這裡生成器的next( )方法的和遍歷器iteratornext( )方法的返回結果是不是一樣?

 

        沒錯,你可以把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的返回值,我們來看一下:

 1     //聲明一個Hello的Generator函數
 2     function* Hello() {
 3         let res = yield `hello`;
 4         yield res;
 5     }
 6     
 7     let iterator = Hello();
 8     //結果:一個生成器對象
 9 
10     iterator.next();
11     //結果:{value: "hello", done: false}
12 
13     iterator.next("前端君");
14     //結果:{value: "前端君", done: false}

        注意函數體內的第一個yield關鍵字,我們把它的返回值賦值給了一個變量res

        再看2next方法的調用:

        1次調用next( )方法,返回的對象屬性value值為「hello」,屬性done值為:fasle,並暫停執行。

        2next( )方法,傳入參數:字符串「前端君」。此時,第二個yield關鍵字緊跟着的是變量res,而變量res的值正是上一個關鍵字yield的返回值。也就是說這個值正是我們傳入的參數:「前端君」。因為:next( )的參數會作為上一個yield的返回值。

5.6 關鍵字『yield*

在一個Generator函數裏面,如果我們想調用另一個Generator函數,就需要用到的關鍵字是:yield*

        我們來看看怎麼玩,代碼有點長,但是很好理解:

 

 1     //聲明Generator函數:gen1   
 2     function* gen1() {
 3         yield "gen1 start";
 4         yield "gen1 end";
 5     }
 6 
 7     //聲明Generator函數:gen2
 8     function* gen2() {
 9         yield "gen2 start";
10         yield "gen2 end";
11     }
12 
13     //聲明Generator函數:start
14     function* start() {
15         yield "start";
16         yield* gen1();
17         yield* gen2();
18         yield "end";
19     }
20 
21     //調用start函數
22     var ite = start();
23     //創建一個生成器
24     
25     ite.next();
26     //{value: "start", done: false}
27 
28     ite.next();
29     //{value: "gen1 start", done: false}
30 
31     ite.next();
32     //{value: "gen1 end", done: false}
33 
34     ite.next();
35     //{value: "gen2 start", done: false}
36 
37     ite.next();
38     //{value: "gen2 end", done: false}
39 
40     ite.next();
41     //{value: "end", done: false}
42     

 

我們主要看start( )這個Generator函數,其中有兩句代碼:

 

1     yield* gen1();
2     yield* gen2();

 

        這裡使用了關鍵字yield*來實現調用另外兩個Generator函數。從後面的多個next( )方法得到的結果看,我們可以知道:

        如果一個Generator函數A執行過程中,進入(調用)了另一個Generator函數B,那麼會一直等到Generator函數B全部執行完畢後,才會返回Generator函數A繼續執行。

5.7 Generator函數用途

以上就是對Generator函數的講解介紹,它是ES6的一個很重要的新特性。它可以控制函數的內部狀態,依次遍歷每個狀態;可以根據需要,輕鬆地讓函數暫停執行或者繼續執行。

        根據這個特點,我們可以利用Generator函數來實現異步操作的效果。

        原理是:利用Generator函數暫停執行的作用,可以將異步操作的語句寫到yield後面,通過執行next方法進行回調。