理解ES6中的Iterator


一、為什么使用Iterator

       我們知道,在ES6中新增了很多的特性,包括Map,Set等新的數據結構,算上數組和對象已經有四種數據集合了,就像數組可以使用forEach,對象可以使用for...in.. 進行遍歷一樣,是不是隨着Map和Set的出現也應該給這兩種集合一個遍歷方法呢?如果這樣的話js的方法對象就太多了,既然這四種集合都是需要遍歷的,那么完全可以用一個統一的訪問機制。於是乎Iterator應運而生。

 

二、Iterator是什么

   Iterator是一個訪問機制,同時是一個接口,可以實現在不同的數據集合中,完成它的功能。

  Iterator本質是一個指針對象,其中包含一個next方法,這個方法可以改變指針的指向,並且返回一個包含value和done的對象,value為當前所指向的成員的值,done表示是否遍歷完成。通過這些描述,我們大致可以手寫一個Iterator,用於遍歷數組:

// 定義遍歷器對象
let Iterator = function (arr) {
  let index = 0;  // 當前指針
  return {
    next() {
      return index < arr.length ? {value: arr[index++],done: false} : {value: undefined, done: true};
    }
  }
};
let arr = [1, 2, 3];
let it = Iterator(arr);
console.log(it.next()); // { value: 1, done: false }
console.log(it.next()); // { value: 2, done: false }
console.log(it.next()); // { value: 3, done: false }
console.log(it.next()); // { value: undefined, done: true }

 

三、ES6中的Iterator接口的實現

  我們知道Iterator的接口就是提供了一個統一的訪問機制,如果我們像上面那樣,不斷的調用next()才能遍歷完成,如果Iterator像java那樣提供一個hasNext()方法的話,那么我們可以通過while進行遍歷,事實上js中是沒有的。之所以沒有是因為ES6使用for...of...實現了對具有Symbol.iterator(可遍歷)的數據結構的遍歷,也就是說只要是包含Symbol.iterator屬性的結構都可以使用for...of...進行遍歷。接下來我們看看我們直接輸出上面四種結構的Symbol.iterator屬性是什么樣子。

let a = [1,2,3];
let it_arr = a[Symbol.iterator]();
it_arr.next();  // { value: 1, done: false }
it_arr.next();  // { value: 2, done: false }
it_arr.next();  // { value: 3, done: false }
it_arr.next();  // { value: undefined, done: true }

let b = new Set(["a","b","c"]);
let it_set = b[Symbol.iterator]();
it_set.next(); // { value: 1, done: false }
it_set.next(); // { value: 2, done: false }
it_set.next(); // { value: 3, done: false }
it_set.next(); // { value: undefined, done: true }

let c = new Map([["a","1"]]);
let it_map =c[Symbol.iterator]();
it_map.next();  // { value: [ 'a', '1' ], done: false }
it_map.next();  // { value: undefined, done: true }

let d = new Object();
d.name = "Jyy";
console.log(d[Symbol.iterator]());  // TypeError: d[Symbol.iterator] is not a function

 

  上面是ES6中在四種數據集合中的Iterator的實現。可以看到ES6並沒有在對象中原生部署Symbol.iterator屬性,因此我們需要手動在對象中設置遍歷器屬性。下面就是一個簡單的在一個對象中設置Symbol.iterator屬性的例子。

function Demo(list){
  this.list = list;
  this[Symbol.iterator] = function(){
    let index = 0;
    return {
      next: function(){
        return index < list.length ? {value: list[index++], done: false} : {value: undefined, done: true};
      }
    }
  }
}
let demo = new Demo([1,2,3,4]);
for(let i of demo){
  console.log(i); // 1 2 3 4
}

  上面的代碼就是手動在Demo對象中添加了一個遍歷器對象,在next中實現遍歷的邏輯,然后就可以使用for...of...進行遍歷。當然我們也可以將遍歷器對象部署在Demo的原型對象,for...of...依然會自動調用。

  另外,對於類數組的對象(key值為數字索引,包含lenth屬性),我們可以直接將數組的遍歷器對象賦值給對象,代碼如下:

let obj = {
  0 : "a",
  1 : "b",
  2 : "c",
  length :3,
}
obj[Symbol.iterator] = Array.prototype[Symbol.iterator];
for(let i of obj){
  console.log(i); // a b c
}

  除此之外,String,函數的arguments對象都原生部署了遍歷器接口

// 字符串可用for...of...進行遍歷
let str = "123";
for(let s of str){
  console.log(s); // 1 2 3
}

// 函數中的arguments對象可以使用for...of...進行遍歷
function demo(){
  for(let i of arguments){
    console.log(i); // 1,2,3
  }
}
demo(1,2,3)

 

四、使用Iterator接口的場合

  除了for...of...,es6中還有哪些語法用到了iterator接口呢?

  1. 解構賦值

function Demo(list){
  this.list = list;
  this[Symbol.iterator] = function(){
    let index = 0;
    return {
      next: function(){
        index++;
        return index < list.length ? {value: list[index++], done: false} : {value: undefined, done: true};
      }
    }
  }
}
[a,...b] = new Demo([1,2,3,4]);
console.log(b);  // [2,3,4]

    如上代碼中每次返回的value都是1,我們在中括號(注意不是花括號)中使用結構賦值,發現b的值為[2,3,4]

  2.擴展運算符

    與解構賦值相反的擴展運算符,也使用了遍歷器。   

function Demo(list){
  this.list = list;
  this[Symbol.iterator] = function(){
    let index = 0;
    return {
      next: function(){
        return index < list.length ? {value: list[index++], done: false} : {value: undefined, done: true};
      }
    }
  }
}
let demo = new Demo([1,2,3,4]);
console.log([0, ...demo, 5]); //[ 0, 1, 2, 3, 4, 5 ]

 

五、與其他遍歷語法的比較

  在js中的遍歷語法有:for循環,for...in..., forEach

  1. for循環的缺點我們都知道,就是書寫起來太麻煩,如果有快捷鍵的方式的話還好,如果沒有,就要寫很多的代碼,而且看起來也不爽。

  2. forEach書寫起來比較簡單,但是它的問題是無法中途跳出循環,break、return、continue等都不能起作用

  3. for...in...是專門為遍歷對象而設計的,如果使用這種方法遍歷數組,key則會變為字符串,如下:

let arr = [1,2,3];
for(let a in arr){
  console.log(typeof a);  // string
}

  另外我們可能在開發中遇到的問題就是,使用for...in...的話,那么會遍歷原型鏈上的鍵

let person = {
  age :25
}
person.prototype={
  name : "jyy"
}
for(let i in Person){
  console.log(i); // age prototype
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM