js--迭代器總結

丶Serendipity丶發表於2021-11-28

前言

  我們已經熟練使用set.map.array幾種集合型別了,掌握了map(),for..of..,filter()等迭代集合的方法,你是否思考過,js引擎是怎麼迭代的,怎麼判斷迭代是否結束,本文來總結一下 js 新增的迭代器相關在知識點。

正文

  1 、迭代器的產生、定義和模擬

  (1) for 迴圈的弊端

  普通的for迴圈的弊端因為陣列有已知的長度,且陣列每一項都可以通過索引獲取,所以整個陣列可以通過遞增索引來遍歷。由於如下原因,通過這種迴圈來執行例程並不理想。

  a、 迭代之前需要事先知道如何使用資料結構。陣列中的每一項都只能先通過引用取得陣列物件,然後再通過 [] 操作符取得特定索引位置上的項。這種情況並不適用於所有資料結構。

  b、 遍歷順序並不是資料結構固有的。通過遞增索引來訪問資料是特定於陣列型別的方式,並不適用於其他具有隱式順序的資料結構。

  (2) 迭代器的產生

迭代器是被設計專用於迭代的物件,帶有特定的介面,迭代器持有一個指向集合位置的內部指標,每當呼叫了next()方法,迭代器就會返回一個結果 IteratorResult 物件,該結果物件有兩個屬性,對應下一個值的 value 以及一個布林型別的 done,在最後一個值再呼叫next()則返回 done 屬性值為 true(標識沒有更多值供使用),並且value屬性會是迭代器自身的返回值. 該返回值不是原始資料集的一部分,卻成為相關資料的最後一部分,或在迭代器未提供返回值的時候使用undefined ,迭代器的自身返回值類似於函式的返回值,是向呼叫者返回資訊的最後手段。

  (3) 迭代器的模擬

  根據上面的定義,手動實現迭代器函式

    // createIterator 函式返回一個帶有next()方法的物件,作為迭代器,每次呼叫next()方法,返回具體的IteratorResult 物件值
    function createIterator(items) {
      var i = 0;
      return {
        next() {
          var done = (i >= items.length);
          var value = !done ? items[i++] : undefined;
          return {
            done, value
          }
        }
      }
    }
    var iterator = createIterator([11, 22, 33])
    console.log(iterator.next());//{done: false, value: 11}
    console.log(iterator.next());//{done: false, value: 22}
    console.log(iterator.next());//{done: false, value: 33}
    console.log(iterator.next());//{done: true, value: undefined}

  2、 迭代器模式和可迭代物件

  迭代器模式(特別是在 ECMAScript 這個語境下)描述了一個方案,即可以把有些結構稱為“可迭代物件”(iterable),因為它們實現了正式的 Iterable 介面,而且可以通過迭代器 Iterator 消費。

  實現 Iterable 介面(可迭代協議)要求同時具備兩種能力:支援迭代的自我識別能力和建立實現 Iterator 介面的物件的能力。在 ECMAScript 中,這意味著必須暴露一個屬性作為“預設迭代器”,而且這個屬性必須使用特殊的 Symbol.iterator 作為鍵。這個預設迭代器屬性必須引用一個迭代器工廠函式,呼叫這個工廠函式必須返回一個新迭代器。

  (1)訪問預設迭代器 symbol.iterator

        let values = [1, 2, 3, 4]
        let iterator = values[Symbol.iterator]()
        console.log(iterator.next());//{value:1,deno:false}
        console.log(iterator.next());//{value:2,deno:false}
        console.log(iterator.next());//{value:3,deno:false}
        console.log(iterator.next());//{value:4,deno:false}
        console.log(iterator.next());//{value:undefiend,deno:true}

  (2) 檢測一個物件是否可以用來迭代

        let num = 1;
        let obj = {};
        // 這兩種型別沒有實現迭代器工廠函式
        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 {}

  總結:檢測一個物件是否可以用來迭代的方式:typeof object[Symbol.iterator] === "function",同時可以得出很多內建型別都實現了 Iterable 介面:字串、 陣列、對映、集合、arguments 物件、NodeList 等 DOM 集合型別等。

 注意:

         a、 每個迭代器都表示對可迭代物件的一次性有序遍歷。不同迭代器的例項相互之間沒有聯絡,只會獨立地遍歷可迭代物件。

         b、迭代器並不與可迭代物件某個時刻的快照繫結,而僅僅是使用遊標來記錄遍歷可迭代物件的歷程。如果可迭代物件在迭代期間被修改了,那麼迭代器也會反映相應的變化。如下:

        let arr = ['foo', 'baz'];
        let iter = arr[Symbol.iterator]();
        let iter2 = arr[Symbol.iterator]();
        console.log(iter.next()); // { done: false, value: 'foo' }
        console.log(iter2.next()); // { done: false, value: 'foo' }
        // 在陣列中間插入值
        arr.splice(1, 0, 'bar');
        console.log(iter.next()); // { done: false, value: 'bar' }
        console.log(iter.next()); // { done: false, value: 'baz' }
        console.log(iter.next()); // { done: true, value: undefined }

  3 、開發中使用的迭代器

  實際寫程式碼過程中,不需要顯式呼叫這個工廠函式來生成迭代器。實現可迭代協議的所有型別都會自動相容接收可迭代物件的任何語言特性。接收可迭代物件的原生語言特性包括:
            a、 for-of 迴圈
            b、 陣列解構
            c、 擴充套件操作符
            d、 Array.from()
            e、 建立集合
            f、 建立對映
            g、 Promise.all() 接收由期約組成的可迭代物件
            h、 Promise.race() 接收由期約組成的可迭代物件
            i、 yield* 操作符,在生成器中使用
  這些原生語言結構會在後臺呼叫提供的可迭代物件的這個工廠函式,從而建立一個迭代器。

  (1) 集合的內建迭代器

   es6 具有三種集合物件型別:陣列、map、set ,三種型別都擁有如下迭代器
          a、entries()返回一個包含鍵值對的迭代器
          b、values() 返回一個包含集合中的值的迭代器
          c、keys() 返回一個包含集合中的鍵的迭代器,對於set來說鍵和值是相同的,對於map來說,迭代器返回每個不重複的鍵
    /*
    entries()迭代器會在每次next()被呼叫時返回一個雙項陣列,此陣列代表了集合中每個元素的鍵與值,
    對於陣列來說,第一項是數值索引,
    對於set,第一項也是值,因為它的值也會被視為鍵,
    對於map來說,第一項就是鍵
    */
    let arr = [1, 2, 3]
    console.log(arr.entries());//Array Iterator []
    for (const item of arr.entries()) {
      console.log(item);
    }// [0:1],[1:2],[2:3]

    let set = new Set([1, 2, 3])
    console.log(set.entries());//SetIterator  {[1=>1],[2=>2],[3=>3]}
    for (const item of set.entries()) {
      console.log(item);
    }//  [1:1],[2:2],[3:3]
    
    let map = new Map()
    map.set("first", "firseetValue")
    map.set("second", "seondValue")
    map.set("third", "thirdValue")
    console.log(map.entries());//MapIterator  {["first"=>"firseetValue"],["second"=>"seondValue"],["third"=>"thirdValue"]}
    for (const item of map.entries()) {
      console.log(item);
    }// ["first":"firseetValue"],["second":"seondValue"],["third":"thirdValue"]

  (2)字串的迭代器

    // 訪問字串中的字元可以通過下標的形式 
    let message = "a  b"
    console.log(message[0])// 'a'
    let info = "a b"
    for (const c of info) {
      console.log(c);
    }//a, ,b   

  4、自定義函式實現迭代器

        // 與 Iterable 介面類似,任何實現 Iterator 介面的物件都可以作為迭代器使用。
        class Counter {
            constructor(limit) {
                this.limit = limit;
            }
            [Symbol.iterator]() {
                let count = 1,// 把計數器變數放到閉包裡,然後通過閉包返回迭代器,讓一個可迭代物件能夠建立多個迭代器,且每建立一個迭代器就對應一個新計數器
                    limit = this.limit;
                return {
                    next() {
                        if (count <= limit) {
                            return { done: false, value: count++ };
                        } else {
                            return { done: true, value: undefined };
                        }
                    },
                    // 可選的 return() 方法用於指定在迭代器提前關閉時執行的邏輯
                    return() {
                        console.log('Exiting early');
                        return { done: true };
                    }
                };
            }
        }
        let counter = new Counter(3);
        for (let i of counter) { console.log(i); }
        // 1
        // 2
        // 3
        for (let i of counter) { console.log(i); }
        // 1
        // 2
        // 3

        let counter2 = new Counter(5);
        try {
            for (let i of counter2) {
                if (i > 2) {
                    throw 'err';
                }
                console.log(i);
            }
        } catch (e) { }
        // 1
        // 2
        // Exiting early

寫在最後

  以上就是本文的全部內容,希望給讀者帶來些許的幫助和進步,方便的話點個關注,小白的成長之路會持續更新一些工作中常見的問題和技術點。

 

 

相關文章