ECMAScript6(14):iterator 迭代器

因爲 ES6 中引入了許多數據結構, 算上原有的包括Object, Array, TypedArray, DataView, buffer, Map, WeakMap, Set, WeakSet等等, 數組須要一個東西來管理他們, 這就是遍歷器(iterator)。jquery

for...of

遍歷器調用一般使用 for...of 循環, for...of 能夠遍歷具備 iterator 的對象, ES6中默認只有數組, Set, Map, String, Generator和一些類數組對象(arguments, DOM NodeList)帶有遍歷器, 其餘的數據結構須要本身定義遍歷器。數組

  • 數組

默認 for...of 遍歷器遍歷值安全

var arr = ["red", "green", "blue"];
for(let v of arr){    //至關於 for(let i in arr.values())
  console.log(v);     //依次輸出 "red", "green", "blue"
}
for(let i in arr){
  console.log(i);     //依次輸出 0, 1, 2
}
for(let [key, value] of arr.entries()){
  console.log(key + ": " + value);      //依次輸出 0: "red", 1: "green", 2: blue"
}
for(let key of arr.keys()){
  console.log(key);     //依次輸出 0, 1, 2
}

不難看出 for...of 默認獲得值, 而 for...in 只能獲得索引。固然數組的 for...of 只返回數字索引的屬性, 而 for...in 沒有限制:數據結構

var arr = ["red", "green", "blue"];
arr.name = "color";
for(let v of arr){
  console.log(v);     //依次輸出 "red", "green", "blue"
}
for(let i in arr){
  console.log(arr[i]);     //依次輸出 "red", "green", "blue", "color"
}
  • Set

默認 for...of 遍歷器遍歷值函數

var set = new Set(["red", "green", "blue"]);
for(let v of set){    //至關於 for(let i in arr.values())
  console.log(v);     //依次輸出 "red", "green", "blue"
}
for(let [key, value] of set.entries()){
  console.log(key + ": " + value);      //依次輸出 "red: red", "green: green", "blue: blue"
}
for(let key of set.keys()){
  console.log(key);     //依次輸出 "red", "green", "blue"
}
  • map

默認 for...of 遍歷器遍歷鍵值對this

var map = new Map();
map.set("red", "#ff0000");
map.set("green", "#00ff00");
map.set("blue", "#0000ff");
for(let [key, value] of map){    //至關於 for(let i in arr.entries())
  console.log(key + ": " + value);      //依次輸出 "red: #ff0000", "green: #00ff00", "blue: #0000ff"
}
for(let value of map.values()){
  console.log(value);     //次輸出 "#ff0000", "#00ff00", "#0000ff"
}
for(let key of map.keys()){
  console.log(key);     //次輸出 "red", "green", "blue"
}
  • 字符串

for...of能夠很好的處理區分32位 Unicode 字符串prototype

var str = "Hello";
for(let v of str){
  console.log(v);     //依次輸出 "H", "e", "l", "l", "o"
}
  • 類數組對象
// DOM NodeList
var lis = document.getElementById("li");
for(let li of lis){
  console.log(li.innerHTML);   //遍歷每一個節點
}

//arguments
function fun(){
  for(let arg of arguments){
    console.log(arg);          //遍歷每一個參數
  }
}

不是全部類數組對象都有 iterator, 若是沒有, 能夠先用Array.from()進行轉換:指針

var o = {0: "red", 1: "green", 2: "blue", length: 3};
var o_arr = Array.from(o);
for(let v of o_arr){
  console.log(v);        //依次輸出 "red", "green", "blue"
}
技巧1: 添加如下代碼, 使 for...of 能夠遍歷 jquery 對象:
$.fn[Symbol.iterator] = [][Symbol.iterator];
技巧2: 利用 Generator 從新包裝對象:
function* entries(obj){
  for(let key of Object.keys(obj)){
    yield [key, obj[key]];
  }
}
var obj = {
  red: "#ff0000",
  green: "#00ff00",
  blue: "#0000ff"
};
for(let [key, value] of entries(obj)){
  console.log(`${key}: ${value}`);        //依次輸出 "red: #ff0000", "green: #00ff00", "blue: #0000ff"
}

幾種遍歷方法的比較

  • for 循環: 書寫比較麻煩
  • forEach方法: 沒法終止遍歷
  • for...in: 僅遍歷索引, 使用不便捷; 會遍歷原型鏈上的屬性, 不安全; 會遍歷非數字索引的數組屬性;
  • for...of:

iterator 與 [Symbol.iterator]

iterator 遍歷過程是這樣的:code

  1. 建立一個指針對象, 指向當前數據結構的起始位置。即遍歷器的本質就是一個指針。
  2. 調用一次指針的 next 方法, 指針指向第一數據成員。以後每次調用 next 方法都會將以後向後移動一個數據。
  3. 知道遍歷結束。

咱們實現一個數組的遍歷器試試:對象

var arr = [1, 3, 6, 5, 2];
var it = makeIterator(arr);
console.log(it.next());       //Object {value: 1, done: false}
console.log(it.next());       //Object {value: 3, done: false}
console.log(it.next());       //Object {value: 6, done: false}
console.log(it.next());       //Object {value: 5, done: false}
console.log(it.next());       //Object {value: 2, done: false}
console.log(it.next());       //Object {value: undefined, done: true}

function makeIterator(arr){
  var nextIndex = 0;
  return {
    next: function(){
      return nextIndex < arr.length ?
        {value: arr[nextIndex++], done: false} :
        {value: undefined, done: true}
    }
  };
}

由這個例子咱們能夠看出如下幾點:

  • 迭代器具備 next() 方法, 用來獲取下一元素
  • next() 方法具備返回值, 返回一個對象, 對象 value 屬性表明下一個值, done 屬性表示是否遍歷是否結束
  • 若是一個數據結構自己不具有遍歷器, 或者自帶的遍歷器不符合使用要求, 請按此例格式自定義一個遍歷器。

其實一個 id 生成器就很相似一個遍歷器:

function idGen(){
  var id = 0;
  return {
    next: function(){ return id++; }
  };
}
var id = idGen();
console.log(id.next());   //0
console.log(id.next());   //1
console.log(id.next());   //2
//...

對於大多數數據結構, 咱們不須要再像這樣寫遍歷器函數了。由於他們已經有遍歷器函數[Symbol.iterator], 好比Array.prototype[Symbol.iterator] 是數組結構的默認遍歷器。

下面定義一個不完整(僅包含add()方法)的鏈表結構的實例:

function Node(value){
  this.value = value;
  this.next = null;
}
function LinkedList(LLName){
  this.head = new Node(LLName);
  this.tail = this.head;
}
var proto = {
  add: function(value){
    var newNode = new Node(value);
    this.tail = this.tail.next = newNode;
    return this;
  }
}
LinkedList.prototype = proto;
LinkedList.prototype.constructor = LinkedList;
LinkedList.prototype[Symbol.iterator] = function(){
  var cur = this.head;
  var curValue;
  return {
    next: function(){
      if(cur !== null){
        curValue = cur.value;
        cur = cur.next;
        return {value: curValue, done: false}
      } else {
        return {value: undefined, done: true}
      }
    }
  };
}

var ll = new LinkedList("prime");
ll.add(1).add(2).add(3).add(5).add(7).add(11);
for(let val of ll){
  console.log(val);    //依次輸出 1, 2, 3, 5, 7, 11
}

注意, 若是遍歷器函數[Symbol.iterator]返回的不是如上例所示結構的對象, 會報錯。
固然, 若是不不喜歡用for...of(應該鮮有這樣的人吧), 能夠用 while 遍歷:

var arr = [1, 2, 3, 5, 7];
var it = arr[Symbol.iterator];
var cur = it.next();
while(!cur.done){
  console.log(cur.value);
  cur = it.next();
}

如下操做會在內部調用相應的 iterator:

  • 數組的解構賦值
  • 展開運算符
  • yield* 後面帶有一個可遍歷結構
  • for...of
  • Array.from() 將類數組對象轉換爲數組
  • Map(), Set(), WeakMap(), WeakSet() 等構造函數傳輸初始參數時
  • Promise.all()
  • Promise.race()

Generator 與遍歷器

iterator 使用 Generator 實現會更簡單:

var it = {};
it[Symbol.iterator] = function* (){
  var a = 1, b = 1;
  var n = 10;
  while(n){
    yield a;
    [a, b] = [b, a + b];
    n--;
  }
}
console.log([...it]); //1, 1, 2, 3, 5, 8, 13, 21, 34, 55]

固然, 以上代碼還能夠這樣寫:

var it = {
  *[Symbol.iterator](){
    var a = 1, b = 1;
    var n = 10;
    while(n){
      yield a;
      [a, b] = [b, a + b];
      n--;
    }
  }
}
console.log([...it]); //[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]

遍歷器對象的其餘方法

以上的遍歷器對象只提到了 next() 方法, 其實遍歷器還有 throw() 方法和 return() 方法:

  • 若是遍歷終止(break, continue, return或者出錯), 會調用 return() 方法
  • Generator 返回的遍歷器對象具throw() 方法, 通常的遍歷器用不到這個方法。具體在 Generator 中解釋。
function readlineSync(file){
  return {
    next(){
      if(file.isAtEndOfFile()){
        file.close();
        return {done: true};
      }
    },
    return(){
      file.close();
      return {done: true};
    }
  }
}

上面實現了一個讀取文件內數據的函數, 當讀取到文件結尾跳出循環, 可是當循環跳出後, 須要作一些事情(關閉文件), 以防內存泄露。這個和 C++ 中的析構函數十分相似, 後者是在對象刪除後作一些釋放內存的工做, 防止內存泄露。

相關文章
相關標籤/搜索