Iterator 和 for...of 循環(huán)

一、Iterator(遍歷器)的概念

  • Iterator 是一種接口惹苗,為各種不同的數(shù)據結構提供統(tǒng)一的訪問機制殿较。
  • 任何數(shù)據結構只要部署 Iterator 接口,就可以完成遍歷操作(即依次處理該數(shù)據結構的所有成員)桩蓉。
  • 表示“集合”的數(shù)據結構:數(shù)組(Array)淋纲、對象(Object)、Map 和 Set院究。開發(fā)者還可以組合使用它們帚戳,定義自己的數(shù)據結構玷或,比如數(shù)組的成員是 Map,Map 的成員是對象片任。
Iterator 的作用:
  • 為各種數(shù)據結構提供一個統(tǒng)一的、簡便的訪問接口蔬胯;
  • 使得數(shù)據結構的成員能夠按某種次序排序对供;
  • ES6 創(chuàng)造了一種新的遍歷命令 for...of 循環(huán),Iterator 接口主要供 for...of 消費氛濒。
Iterator 的遍歷過程:
  • 創(chuàng)建一個指針對象产场,指向當前數(shù)據結構的起始位置。遍歷器對象本質上舞竿,就是一個指針對象京景。
  • 第一次調用指針對象的next方法,可以將指針指向數(shù)據結構的第一個成員骗奖。
  • 第二次調用指針對象的next方法确徙,指針就指向數(shù)據結構的第二個成員。
  • 不斷調用指針對象的next方法执桌,直到它指向數(shù)據結構的結束位置鄙皇。
function makeIterator(array) {
  var nextIndex = 0;
  return {
    next: function() {
      return nextIndex < array.length ?
        {value: array[nextIndex++], done: false} :
        {value: undefined, done: true};
    }
  };
}
var it = makeIterator(['a', 'b']);
console.log(it.next())  // { value: "a", done: false }
console.log(it.next())  // { value: "b", done: false }
console.log(it.next())  // { value: undefined, done: true }

說明:定義了一個 makeIterator 遍歷器生成函數(shù),它返回一個遍歷器對象仰挣。對數(shù)組執(zhí)行這個函數(shù)伴逸,返回數(shù)組的遍歷器對象 it

  • 其中膘壶,done:falsevalue:undefined 可以省略错蝴,上面的 makeIterator 方法可以改寫如下:
function makeIterator(array) {
  var nextIndex = 0;
  return {
    next: function() {
      return nextIndex < array.length ?
        {value: array[nextIndex++]} :
        {done: true};
    }
  };
}

注:Iterator 只是把接口規(guī)格加到數(shù)據結構之上,遍歷器與它所遍歷的那個數(shù)據結構颓芭,實際上是分開的顷锰;完全可以寫出沒有對應數(shù)據結構的遍歷器對象,或者說用遍歷器對象模擬出數(shù)據結構畜伐。

var it = sqrMaker();

console.log(it.next().value) // 0
console.log(it.next().value) // 1
console.log(it.next().value) // 4
console.log(it.next().value) // 9
// ...

function sqrMaker() {
  var index = 0;

  return {
    next: function() {
      return {value: index++ ** 2, done: false};
    }
  };
}

二馍惹、默認 Iterator 接口

  • ES6 規(guī)定,默認的 Iterator 接口部署在數(shù)據結構的Symbol.iterator屬性玛界,或者說万矾,一個數(shù)據結構只要具有Symbol.iterator屬性,就可以認為是“可遍歷的”(iterable)慎框。
  • Symbol.iterator屬性本身是一個函數(shù)良狈,就是當前數(shù)據結構默認的遍歷器生成函數(shù)。執(zhí)行這個函數(shù)笨枯,就會返回一個遍歷器薪丁。至于屬性名Symbol.iterator遇西,它是一個表達式,返回Symbol對象的iterator屬性严嗜,這是一個預定義好的粱檀、類型為 Symbol 的特殊值。
  • 有些數(shù)據結構原生具備 Iterator 接口(比如數(shù)組)漫玄,即不用任何處理茄蚯,就可以被 for...of 循環(huán)遍歷,因為這些數(shù)據結構原生部署了 Symbol.iterator 屬性睦优。
    • 原生具備 Iterator 接口的數(shù)據結構如下:

      1渗常、Array
      2、Map
      3汗盘、Set
      4皱碘、String
      5、TypedArray
      6隐孽、函數(shù)的 arguments 對象
      7癌椿、NodeList 對象

    • 舉例,數(shù)組的 Symbol.iterator 屬性:
          var arr = ['a','b','c','d'];
          var arrayIt = arr[Symbol.iterator]();
          console.log(arrayIt.next());  //{value: "a", done: false}
          console.log(arrayIt.next());  //{value: "b", done: false}
          console.log(arrayIt.next());  //{value: "c", done: false}
          console.log(arrayIt.next());  //{value: "d", done: false}
          console.log(arrayIt.next());  //{value: undefined, done: true}
      
    • 說明:

      1缓醋、變量 array 是一個數(shù)組如失,原生就具有遍歷器接口,部署在 array 的 Symbol.iterator 屬性上面送粱。所以褪贵,調用這個屬性,就得到遍歷器對象抗俄。
      2脆丁、對于原生部署 Iterator 接口的數(shù)據結構,不用自己寫遍歷器生成函數(shù)动雹,for...of循環(huán)會自動遍歷它們槽卫。除此之外,其他數(shù)據結構(主要是對象)的 Iterator 接口胰蝠,都需要自己在Symbol.iterator屬性上面部署歼培,這樣才會被for...of循環(huán)遍歷。

一個對象如果要具備可被for...of循環(huán)調用的 Iterator 接口茸塞,就必須在Symbol.iterator的屬性上部署遍歷器生成方法(原型鏈上的對象具有該方法也可):
  class RangeIterator {
    constructor(start, stop) {
      this.value = start;
      this.stop = stop;
    }

    [Symbol.iterator]() { return this; }

    next() {
      var value = this.value;
      if (value < this.stop) {
        this.value++;
        return {done: false, value: value};
      }
      return {done: true, value: undefined};
    }
  }

  function range(start, stop) {
    return new RangeIterator(start, stop);
  }

  for (var value of range(0, 3)) {
    console.log(value); // 0, 1, 2
  }
  • 說明:上面代碼是一個類部署 Iterator 接口的寫法躲庄。Symbol.iterator
    屬性對應一個函數(shù),執(zhí)行后返回當前對象的遍歷器對象钾虐。
為對象添加 Iterator 接口的例子:
    var objt = {
      data: [ 'hello', 'world' ],
      [Symbol.iterator]() {
        const self = this;
        let index = 0;
        return {
          next() {
            if (index < self.data.length) {
              return {
                value: self.data[index++],
                done: false
              };
            } else {
              return { value: undefined, done: true };
            }
          }
        };
      }
    };
    var o = objt[Symbol.iterator]()
    console.log(o.next())   // {value: "hello", done: false}
    console.log(o.next())   // {value: "world", done: false}
    console.log(o.next())   // {value: undefined, done: true}
類數(shù)組對象:
  • 存在數(shù)值鍵名和 length 屬性噪窘;
  • 部署 Iterator 接口,有一個簡便方法效扫,就是Symbol.iterator方法直接引用數(shù)組的 Iterator 接口倔监。
      NodeList.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];
      // 或者
      NodeList.prototype[Symbol.iterator] = [][Symbol.iterator];
    
      [...document.querySelectorAll('div')] // 可以執(zhí)行了
    
    • 說明:NodeList 對象是類似數(shù)組的對象直砂,本來就具有遍歷接口,可以直接遍歷浩习。上面代碼中静暂,我們將它的遍歷接口改成數(shù)組的Symbol.iterator屬性,可以看到沒有任何影響瘦锹。
類似數(shù)組的對象調用數(shù)組的 Symbol.iterator 方法的例子:
    let iterable = {
      0: 'a',
      1: 'b',
      2: 'c',
      length: 3,
      [Symbol.iterator]: Array.prototype[Symbol.iterator]
    };
    for (let item of iterable) {
      console.log(item); // 'a', 'b', 'c'
    }
  • 普通對象部署數(shù)組的Symbol.iterator方法籍嘹,并無效果:
      let iterable2 = {
        a: 'a',
        b: 'b',
        c: 'c',
        length: 3,
        [Symbol.iterator]: Array.prototype[Symbol.iterator]
      };
      for (let item of iterable2) {
        console.log(item); // undefined, undefined, undefined
      }
    
  • 如果 Symbol.iterator 方法對應的不是遍歷器生成函數(shù)(即會返回一個遍歷器對象),解釋引擎將會報錯:
      var obj2 = {};
      obj2[Symbol.iterator] = () => 1;
      [...obj2] 
      // 編輯器 ----> TypeError: obj2[Symbol.iterator] is not a function
      // 瀏覽器 ----> TypeError: Result of the Symbol.iterator method is not an object
    
  • 有了遍歷器接口弯院,數(shù)據結構就可以用 for...of 循環(huán)遍歷,也可以使用 while 循環(huán)遍歷:
      var $iterator = ['x', 'y', 'z'][Symbol.iterator]();
      var $result = $iterator.next();
      while (!$result.done) {
        var x = $result.value;
        console.log('yjw ------- ', x);
        $result = $iterator.next();
      }
    

三泪掀、調用 Iterator 接口的場景

解構賦值:
  • 對數(shù)組和 Set 結構進行解構賦值時仔掸,會默認調用 Symbol.iterator 方法:
    var set = new Set().add('a').add('b').add('c');
    var [x,y] = set;
    console.log('x = ', x, ' ; y = ', y);   
    // x =  a  ; y =  b
    var [first, ...rest] = set;
    console.log('first = ', first, ' ; rest = ', rest);   
    // first =  a  ; rest =  [ 'b', 'c' ]
    
    var arr = ['b', 'd', 'e'];
    Array.prototype[Symbol.iterator] = function(){
        const self = this;
        let index = 0;
        return {
          next() {
            if (index < self.length) {
                console.log('yjw ----------> ', index)
              return {
                value: self[index++],
                done: false
              };
            } else {
              return { value: undefined, done: true };
            }
          }
        };
    }
    var [n1,n2] = arr
    // yjw ---------->  0
    // yjw ---------->  1
    
擴展運算符:
  // 例一
  var str = 'hello';
  [...str] //  ['h','e','l','l','o']

  // 例二
  let arr = ['b', 'c'];
  ['a', ...arr, 'd']
  // ['a', 'b', 'c', 'd']

補充:只要某個數(shù)據結構部署了 Iterator 接口蝗碎,就可以對它使用擴展運算符,將其轉為數(shù)組。

yield *
  • yield* 后面跟的是一個可遍歷的結構项戴,它會調用該結構的遍歷器接口
      var generator = function* () {
        yield 1;
        yield* [2,3,4];
        yield 5;
      };
    
      var iterator = generator();
    
      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 }
    
  • 其它場合:
    • 由于數(shù)組的遍歷會調用遍歷器接口,所以任何接受數(shù)組作為參數(shù)的場合欠橘,其實都調用了遍歷器接口:

    for...of
    Array.from()
    Map(), Set(), WeakMap(), WeakSet()(比如new Map([['a',1],['b',2]]))
    Promise.all()
    Promise.race()

四涛目、字符串的 Iterator 接口

  • 字符串是一個類似數(shù)組的對象,原生具有 Iterator 接口
      var someString = "hi";
      console.log(typeof someString[Symbol.iterator])
      // "function"
    
      var iterator = someString[Symbol.iterator]();
      console.log(iterator.next())  // { value: "h", done: false }
      console.log(iterator.next())  // { value: "i", done: false }
      console.log(iterator.next())  // { value: undefined, done: true }
    
  • 覆蓋字符串原生的 Symbol.iterator 方法靠抑,修改遍歷器行為
      var str = new String("hi");
    
      console.log([...str]) // ["h", "i"]
    
      str[Symbol.iterator] = function() {
        return {
          next: function() {
            if (this._first) {
              this._first = false;
              console.log('============= yjw ============')
              return { value: "bye", done: false };
            } else {
              return { done: true };
            }
          },
          _first: true
        };
      };
    
      console.log([...str]) // ["bye"]
      console.log(str) // "hi"
    

五量九、Iterator 接口與 Generator 函數(shù)

  • Symbol.iterator方法的最簡單實現(xiàn):
      var myIterable2 = {
        [Symbol.iterator]: function* () {
          yield 1;
          yield 2;
          yield 3;
        }
      };
      console.log([...myIterable2]) // [1, 2, 3]
    
      // 或者采用下面的簡潔寫法
    
      var obj2 = {
        * [Symbol.iterator]() {
          yield 'hello';
          yield 'world';
        }
      };
    
      for (let x of obj2) {
        console.log(x);
      }
      // "hello"
      // "world"
    
遍歷器對象構成條件:
  • next:必須要部署該方法;
  • return:可選的部署方法颂碧;
  • throw:主要配合 generator(下一章) 函數(shù)使用荠列,一般的遍歷對象用不到這個方法。

六载城、for...of 循環(huán)

  • 一個數(shù)據結構只要部署了 Symbol.iterator 屬性肌似,就被視為具有 iterator 接口,就可以用 for...of 循環(huán)遍歷它的成員诉瓦;

  • 使用范圍:數(shù)組川队、Set 和 Map 結構、某些類似數(shù)組的對象(比如arguments對象睬澡、DOM NodeList 對象)固额、后文的 Generator 對象,以及字符串猴贰。

      var testArr = ['x', 'y', 'z'];
      for (let v of testArr){
          console.log(v)  // x  y   z
      }
    
      var obj3 = {};
      obj3[Symbol.iterator] = testArr[Symbol.iterator].bind(testArr);
    
      for(let v of obj3) {
        console.log(v); // x  y   z
      }
    
  • 可以替代 forEach 方法对雪;

  • for...in 缺點:

    • 只能獲取鍵名,不能獲取鍵值米绕;
    • 數(shù)組的鍵名是數(shù)字瑟捣,但是這里的鍵名是字符串馋艺;
    • 可以返回非數(shù)值鍵名;
    • 優(yōu)點:可以遍歷普通對象迈套。
    • 結論:for...in 主要是為遍歷對象設計的捐祠,不適用于遍歷數(shù)組。
      var testArr = ['x', 'y', 'z'];
      testArr.foo = 'abc'
    
      for (let v of testArr){
          console.log('of ---> ', v)
      }
      
      for (let v in testArr){
          console.log('in ---> ', v)
      }
    
  • 能正確識別 32 為 UTF-16 字符:

      for (let x of 'a\uD83D\uDC0A') {
        console.log(x);
      }
      // 'a'
      // '\uD83D\uDC0A'
    
  • 有些類數(shù)組的對象沒有 Iterator 接口桑李,可以使用 Array.from 方法將其轉為數(shù)組:

      var arrayLike = { 0: 'a', 1: 'b', length: 2};
    
      // 報錯
      for (let x of arrayLike) {
        console.log(x);
      }
    
      // 正確
      for (let x of Array.from(arrayLike)) {
        console.log(x);
      }
      // a  b
    
  • 不能遍歷普通的對象踱蛀,必須部署了 Iterator 接口后才能使用。其它的解決方法:

    // 方案一:
    var someObject = {a: 'aa', b: 'bb'};
    for (var key of Object.keys(someObject)) {
      console.log(key + ': ' + someObject[key]);
    }
    // a: aa
    // b: bb
    
    // 方案二:
    function* entries(obj) {
      for (let key of Object.keys(obj)) {
        yield [key, obj[key]];
      }
    }
    
    var obj = {name: 'yijiang', age: '18'};
    for (let [key, value] of entries(obj)) {
      console.log(key, '->', value);
    }
    // name -> yijiang
    // age -> 18
    
和其它遍歷語法的比較:
  • 原始的 for 循環(huán):麻煩贵白;
      var array = [1,2,3,4,5,6,7,8,9]
      for(let i=0; i < array.length; i++){
          console.log(array[i]);
          if(i>5) break;
      }
      // 1 2 3 4 5 6 7
    
  • forEach 方法:無法中途跳出
      var array = [1,2,3,4,5,6,7,8,9]
      array.forEach(v => {
        console.log(v)
        if(v > 5) return;  
      })
    
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末率拒,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子禁荒,更是在濱河造成了極大的恐慌猬膨,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,591評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件呛伴,死亡現(xiàn)場離奇詭異勃痴,居然都是意外死亡,警方通過查閱死者的電腦和手機热康,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評論 3 392
  • 文/潘曉璐 我一進店門沛申,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人姐军,你說我怎么就攤上這事铁材。” “怎么了庶弃?”我有些...
    開封第一講書人閱讀 162,823評論 0 353
  • 文/不壞的土叔 我叫張陵衫贬,是天一觀的道長。 經常有香客問我歇攻,道長固惯,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,204評論 1 292
  • 正文 為了忘掉前任缴守,我火速辦了婚禮葬毫,結果婚禮上,老公的妹妹穿的比我還像新娘屡穗。我一直安慰自己贴捡,他們只是感情好,可當我...
    茶點故事閱讀 67,228評論 6 388
  • 文/花漫 我一把揭開白布村砂。 她就那樣靜靜地躺著烂斋,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上汛骂,一...
    開封第一講書人閱讀 51,190評論 1 299
  • 那天罕模,我揣著相機與錄音,去河邊找鬼帘瞭。 笑死淑掌,一個胖子當著我的面吹牛,可吹牛的內容都是我干的蝶念。 我是一名探鬼主播抛腕,決...
    沈念sama閱讀 40,078評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼媒殉!你這毒婦竟也來了担敌?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 38,923評論 0 274
  • 序言:老撾萬榮一對情侶失蹤廷蓉,失蹤者是張志新(化名)和其女友劉穎柄错,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體苦酱,經...
    沈念sama閱讀 45,334評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,550評論 2 333
  • 正文 我和宋清朗相戀三年给猾,在試婚紗的時候發(fā)現(xiàn)自己被綠了疫萤。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,727評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡敢伸,死狀恐怖扯饶,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情池颈,我是刑警寧澤尾序,帶...
    沈念sama閱讀 35,428評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站躯砰,受9級特大地震影響每币,放射性物質發(fā)生泄漏。R本人自食惡果不足惜琢歇,卻給世界環(huán)境...
    茶點故事閱讀 41,022評論 3 326
  • 文/蒙蒙 一兰怠、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧李茫,春花似錦揭保、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,672評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春味榛,著一層夾襖步出監(jiān)牢的瞬間椭坚,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,826評論 1 269
  • 我被黑心中介騙來泰國打工励负, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留藕溅,地道東北人。 一個月前我還...
    沈念sama閱讀 47,734評論 2 368
  • 正文 我出身青樓继榆,卻偏偏與公主長得像巾表,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子略吨,可洞房花燭夜當晚...
    茶點故事閱讀 44,619評論 2 354

推薦閱讀更多精彩內容