1. 程式人生 > >ES6學習筆記13 Iterator 和 for...of 迴圈

ES6學習筆記13 Iterator 和 for...of 迴圈

Iterator介面簡介

遍歷器(Iterator)是一種介面,為各種不同的資料結構提供統一的訪問機制。任何資料結構只要部署 Iterator 介面,就可以完成遍歷操作(即依次處理該資料結構的所有成員)。遍歷器介面可供for...of使用,迴圈遍歷某種資料結構

Iterator 的遍歷過程:

(1)建立一個指標物件,指向當前資料結構的起始位置。也就是說,遍歷器物件本質上,就是一個指標物件。

(2)第一次呼叫指標物件的next方法,可以將指標指向資料結構的第一個成員。

(3)第二次呼叫指標物件的next方法,指標就指向資料結構的第二個成員。

(4)不斷呼叫指標物件的next方法,直到它指向資料結構的結束位置。

每一次呼叫next方法,都會返回資料結構的當前成員的資訊。具體來說,就是返回一個包含valuedone兩個屬性的物件。其中,value屬性是當前成員的值,done屬性是一個布林值,表示遍歷是否結束。

下面是模擬next方法返回值的例子

var it = makeIterator(['a', 'b']);

it.next() // { value: "a", done: false }
it.next() // { value: "b", done: false }
it.next() // { value: undefined, done: true }

function makeIterator
(array) {
var nextIndex = 0; return { //返回一個具有next方法的遍歷器物件 next: function() { return nextIndex < array.length ? {value: array[nextIndex++], done: false} : {value: undefined, done: true}; } }; }

ES6 規定,預設的 Iterator 介面部署在資料結構的Symbol.iterator屬性,或者說,一個數據結構只要具有Symbol.iterator

屬性,就可以認為是“可遍歷的”(iterable)。

Symbol.iterator屬性本身是一個函式,就是當前資料結構預設的遍歷器生成函式。執行這個函式,就會返回一個遍歷器。至於屬性名Symbol.iterator,它是一個表示式,返回Symbol物件的iterator屬性,這是一個預定義好的、型別為 Symbol 的特殊值,所以要放在方括號內

const obj = {
  [Symbol.iterator] : function () {
    return {
      next: function () {
        return {
          value: 1,
          done: true
        };
      }
    };
  }
};

原生具備了Iterator介面的資料結構有:Array,Map,Set,String,TypedArray,arguments物件,NodeList物件。除此之外,其他資料結構的Iterator介面都需要自己在Symbol.iterator屬性上面部署(或原型鏈上的物件具有該方法也可),這樣才會被for…of迴圈遍歷

下面是通過遍歷器實現指標結構的例子

function Obj(value) {
  this.value = value;
  this.next = null;    //用於指向下一個物件
}

Obj.prototype[Symbol.iterator] = function() {   //在原型物件上部署遍歷器介面
  var iterator = { next: next };     //遍歷器為一個包含next方法的物件

  var current = this;     //指向當前物件

  function next() {
    if (current) {      //如果當前物件存在,則返回值,並指向下一個物件
      var value = current.value;    
      current = current.next;
      return { done: false, value: value };
    } else {
      return { done: true };
    }
  }
  return iterator;
}

var one = new Obj(1);
var two = new Obj(2);
var three = new Obj(3);

one.next = two;
two.next = three;

for (var i of one){
  console.log(i); // 1, 2, 3
}

下面為物件部署Iterator介面

let obj = {
  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 };
        }
      }
    };
  }
};

如果Symbol.iterator方法對應的不是遍歷器生成函式(即會返回一個遍歷器物件),解釋引擎將會報錯。有了遍歷器介面,資料結構就可以用for...of迴圈遍歷

Symbol.iterator方法的最簡單實現,還是使用 Generator 函式。

let myIterable = {
  [Symbol.iterator]: function* () {
    yield 1;
    yield 2;
    yield 3;
  }
}
[...myIterable] // [1, 2, 3]

// 或者採用下面的簡潔寫法

let obj = {
  * [Symbol.iterator]() {
    yield 'hello';
    yield 'world';
  }
};

for (let x of obj) {
  console.log(x);
}
// "hello"
// "world"

呼叫Iterator介面的場合

(1)解構賦值
對陣列和 Set 結構進行解構賦值時,會預設呼叫Symbol.iterator方法。

(2)擴充套件運算子
擴充套件運算子(…)也會呼叫預設的 Iterator 介面,可以將任何部署了 Iterator 介面的資料結構,轉為陣列。

(3)yield*
yield*後面跟的是一個可遍歷的結構,它會呼叫該結構的遍歷器介面

let generator = function* () {
  yield 1;
  yield* [2,3,4];
  yield 5;
};

var iterator = generator();

iterator.next() // { value: 1, done: false }
iterator.next() // { value: 2, done: false }
iterator.next() // { value: 3, done: false }
iterator.next() // { value: 4, done: false }
iterator.next() // { value: 5, done: false }
iterator.next() // { value: undefined, done: true }

(4)任何接受陣列作為引數的場合

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

遍歷器物件的 return() 和 throw()

遍歷器物件除了具有next方法,還可以具有return方法和throw方法。在自己部署的遍歷器物件生成函式中,next方法是必須,而return方法和throw方法是可選的

return方法的使用場合是,如果for...of迴圈提前退出(通常是因為出錯,或者有break語句),就會呼叫return方法。如果一個物件在完成遍歷前,需要清理或釋放資源,就可以部署return方法。

function readLinesSync(file) {
  return {
    [Symbol.iterator]() {
      return {
        next() {
          return { done: false };
        },
        return() {   //關閉檔案
          file.close();
          return { done: true };
        }
      };
    },
  };
}

return方法必須返回一個物件,這是 Generator 規格決定的。

遍歷器的throw方法用於丟擲錯誤,主要配合 Generator 函式內部捕獲錯誤來使用,一般遍歷器物件用不到這個方法

for…of 迴圈

for...of迴圈可以使用的範圍包括陣列、Set 和 Map 結構、某些類似陣列的物件(比如arguments物件、DOM NodeList 物件)、後文的 Generator 物件,以及字串等任何部署了Symbol.iterator屬性的資料結構。

還可以使用其遍歷計算生成的資料結構,例如keys()values()entries()

let arr = ['a', 'b', 'c'];
for (let pair of arr.entries()) {
  console.log(pair);
}
// [0, 'a']
// [1, 'b']
// [2, 'c']

for...of迴圈還有一個特點,就是會正確識別 32 位 UTF-16 字元。

for (let x of 'a\uD83D\uDC0A') {
  console.log(x);
}
// 'a'
// '\uD83D\uDC0A'

對於沒有Iterator介面的類似陣列物件結構,可以使用Array.from方法將其轉為陣列,在使用for...of遍歷

let arrayLike = { length: 2, 0: 'a', 1: 'b' };

// 報錯
for (let x of arrayLike) {
  console.log(x);
}

// 正確
for (let x of Array.from(arrayLike)) {
  console.log(x);
}

對於普通的物件, 可以使用Object.keys()方法將物件的鍵名生成一個數組,然後再遍歷

for (var key of Object.keys(someObject)) {
  console.log(key + ': ' + someObject[key]);
}

for...of與其他遍歷語法比較

  • 陣列提供內建的forEach方法可以遍歷陣列的成員,但是該方法中途無法跳出,breakreturn命令都無法湊效
myArray.forEach(function (value) {
  console.log(value);
});
  • for...in可以用來遍歷陣列和物件的鍵名,其無法直接遍歷鍵值,對於陣列來說,遍歷返回的值是索引的字串形式,例如”1”,”2”,”3”。除此之外其會遍歷手動新增的鍵名以及原型鏈上的鍵。在某些情況下,for...in還會以任意順序遍歷鍵名
  • -