Iterator:訪問數據集合的統一接口

導語

遍歷器Iterator是ES6爲訪問數據集合提供的統一接口。任何內部部署了遍歷器接口的數據集合,對於用戶來講,均可以使用相同方式獲取到相應的數據結構。若是使用的是最新版Chrome瀏覽器,那麼你要知道——咱們所熟悉的數組小姐,已悄悄的打開了另外一扇可抵達她心扉的小徑。segmentfault

1 正題

某個數據集合部署了Iterator接口,是指其Symbol.iterator屬性指向一個能返回Iterator接口的函數。任何默認使用遍歷器訪問數據集合的方法,都會調用此屬性以獲得遍歷器對象,再按照設定的順序依次訪問該數據結構的成員(關於Symbol.iterator請看最後一節的延伸閱讀)。好比原生數組的遍歷器爲[][Symbol.iterator],也能夠直接經過其構造函數的原型獲取Array.prototype[Symbol.iterator]數組

1.1 基本行爲

調用Iterator接口會返回一個新的遍歷器對象(指針對象)。
對象中必然有next方法,用於訪問下一個數據成員。指針初始時指向當前數據結構的起始位置。瀏覽器

第一次調用對象的next方法,指針指向數據結構的第一個成員。
第二次調用對象的next方法,指針指向數據結構的第二個成員。
不斷的調用對象的next方法,直到它指向數據結構的結束位置。數據結構

每次調用next方法,都會返回相同的數據結構:{ value, done }
其中value表示當前指向成員的值,沒有則爲undefined
其中done是一個布爾值,表示遍歷是否結束,結束爲true,不然false函數

遍歷器接口的標準十分簡潔,不提供諸如:操做內部指針、判斷是否有值等等方法。只須要一直不斷的調用next方法,當donefalse時獲取當時的valuedonetrue時中止便可。第一次接觸遍歷器的行爲模式是在2016的冬天,那時底蘊不夠雞毛也沒長全,理解不了簡潔性的適用和強大。直到如今——在即將打包被迫離開公司的前夕才驀然的醒覺。多麼痛的領悟啊。this

let iterator = [1, 2, 3][Symbol.iterator]();

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: undefined, done: true}

1.2 簡單實現

面向不一樣的數據結構,有不一樣的遍歷器實現方法,咱們簡單的實現下數組的遍歷器方法。prototype

let res = null;
let iterator = myIterator([3, 7]);

console.log( iterator.next() ); // {value: 3, done: false}
console.log( iterator.next() ); // {value: 7, done: false}
console.log( iterator.next() ); // {value: undefined, done: true}

function myIterator(array = []) {
  let index = 0;
  return {
    next() {
      return index < array.length 
        ? { value: array[index++], done: false }
        : { value: undefined, done: true };
    }
  };
}

1.3 return & throw

除了爲遍歷器對象部署next方法,還能夠有returnthrow方法。其中return方法會在提早退出for of循環時(一般是由於出錯,或觸發了break語句)被調用。而throw方法主要是配合Generator函數使用,通常的遍歷器對象用不到這個方法,因此不予介紹。指針

let obj = {
  [Symbol.iterator]() {
    let index = 0;
    let array = [1, 2, 3];

    return {
      next() {
        return index < array.length 
          ? { value: array[index++], done: false }
          : { value: undefined, done: true };
      },
      return() {
        console.log('Trigger return.');
        return {};
      }
    };
  }
};

for (let v of obj) {
  console.log(v); // 打印出:1, 2, 3,沒觸發 return 函數。
}

for (let v of obj) {
  if (v === 2) break;
  console.log(v); // 打印出:1,以後觸發 return 函數。
}

for (let v of obj) {
  if (v === 3) break;
  console.log(v); // 打印出:1, 2,以後觸發 return 函數。
}

for (let v of obj) {
  if (v === 4) break;
  console.log(v); // 打印出:1, 2, 3,沒觸發 return 函數。
}

for (let v of obj) {
  if (v === 2) throw Error('error');
  console.log(v); // 打印出:1,以後觸發 return 函數,並報錯中止執行。
}

2 原生支持

2.1 默認持有遍歷器

原生默認持有遍歷器接口的數據結構有:
基本類型:Array, Set, Map(四種基本數據集合:Array, Object, SetMap)。
類數組對象:arguments, NodeList, Stringcode

let iterator = '123'[Symbol.iterator]();

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: undefined, done: true}

遍歷器與先前的遍歷方法
一個數據集合擁有遍歷器接口,並不意味着全部遍歷它的方法都是使用此接口。實際上,只有ES6新增的幾種方式和某些方法會使用,下面會有介紹。以數組來講,對其使用forfor of雖然可訪問到相同的成員,可是實際的操做方式卻不一樣。對象

// 改變數組默認的遍歷器接口。
Array.prototype[Symbol.iterator] = function () {
  let index = 0;
  let array = this;

  console.log('Use iterator');

  return {
    next() {
      return index < array.length 
        ? { value: array[index++], done: false }
        : { value: undefined, done: true };
    }
  }
};

let arr = [1, 2];

for (let v of arr) {
  console.log(v); // 打印出 Use iterator, 1, 2。
}

for (let i = 0; i < arr.length; i++) {
  console.log(arr[i]); // 打印出 1, 2。
}

arr.forEach(d => {
  console.log(d); // 打印出 1, 2。
});

對象沒有默認的遍歷器接口
爲何對象沒有默認的遍歷器接口?這要從兩方面說明。一爲遍歷器是種線性處理結構,對於任何非線性的數據結構,部署了遍歷器接口,就等於部署一種線性轉換。二是對象原本就是一個無序的集合,若是但願其有序,可使用Map代替。這便是各有其長,各安其職。屎殼郎若是不滾糞球而去採蜜,那,呃,花妹妹可能就遭殃咯。

自行生成的類數組對象(擁有length屬性),不具有遍歷器接口。這與String等原生類數組對象不一樣,畢竟人家是親生的,一出生就含着金鑰匙(也不怕誤吞)。不過咱們能夠將數組的遍歷器接口直接應用於自行生成的類數組對象,簡單有效無反作用。

let obj = {
  0: 'a',
  1: 'b',
  length: 2,
  [Symbol.iterator]: Array.prototype[Symbol.iterator]
};

let iterator = obj[Symbol.iterator]();
console.log( iterator.next() ); // {value: "a", done: false}
console.log( iterator.next() ); // {value: "b", done: false}
console.log( iterator.next() ); // {value: undefined, done: true}

爲對象添加遍歷器接口,也不影響以前不使用遍歷器的方法,好比for in, Object.keys等等(二者不等同)。

let obj = {
  0: 'a',
  1: 'b',
  length: 2,
  [Symbol.iterator]: Array.prototype[Symbol.iterator]
};

console.log( Object.keys(obj) ); // ["0", "1", "length"]

for (let v of obj) {
  console.log(v); // 依次打印出:"a", "b"。
}

for (let k in obj) {
  console.log(k); // 依次打印出:"0", "1", "length"。
}

2.2 默認調用遍歷器

for of
for of是專門用來消費遍歷器的,其遍歷的是鍵值(for in遍歷的是鍵名)。

for (let v of [1, 2, 3])  {
  console.log(v); // 依次打印出:1, 2, 3。
}

擴展運算符
不管是解構賦值或擴展運算都是默認調用遍歷器的。

let [...a] = [3, 2, 1]; // [3, 2, 1]
let b = [...[3, 2, 1]]; // [3, 2, 1]

yield*
Generator函數中有yield*命令,若是其後面跟的是一個可遍歷的結構,它會調用該結構的遍歷器接口。

for (let v of G()) {
  console.log(v); // 依次打印出:1, 2, 3, 4, 5
}

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

其它場合
有些接受數組做爲參數的函數,會默認使用數組的遍歷器接口,因此也等同於默認調用。好比Array.from(), Promise.all()

延伸閱讀

關於ES6的Symbol連接

相關文章
相關標籤/搜索