遍歷器(Iterator)是一種接口,爲各類不一樣的數據結構提供統一的訪問機制。任何數據結構只要部署Iterator接口,就能夠完成遍歷操做(即依次處理該數據結構的全部成員)。數組
Iterator 的做用有三個:數據結構
Iterator 的遍歷過程是這樣的:函數
每一次調用next方法,都會返回數據結構的當前成員的信息。具體來講,就是返回一個包含value和done兩個屬性的對象。其中,value屬性是當前成員的值,done屬性是一個布爾值,表示遍歷是否結束。this
下面是一個模擬next方法返回值的例子:prototype
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: function() { return nextIndex < array.length ? {value: array[nextIndex++], done: false} : {value: undefined, done: true}; } }; }
上面代碼定義了一個makeIterator函數,它是一個遍歷器生成函數,做用就是返回一個遍歷器對象。對數組['a', 'b']執行這個函數,就會返回該數組的遍歷器對象(即指針對象)it。指針
因爲 Iterator 只是把接口規格加到數據結構之上,因此,遍歷器與它所遍歷的那個數據結構,其實是分開的,徹底能夠寫出沒有對應數據結構的遍歷器對象,或者說用遍歷器對象模擬出數據結構。下面是一個無限運行的遍歷器對象的例子:rest
var it = idMaker(); it.next().value // '0' it.next().value // '1' it.next().value // '2' // ... function idMaker() { var index = 0; return { next: function() { return {value: index++, done: false}; } }; }
上面的例子中,遍歷器生成函數idMaker,返回一個遍歷器對象(即指針對象)。可是並無對應的數據結構,或者說,遍歷器對象本身描述了一個數據結構出來code
若是使用 TypeScript 的寫法,遍歷器接口(Iterable)、指針對象(Iterator)和next方法返回值的規格能夠描述以下:對象
interface Iterable { [Symbol.iterator]() : Iterator, } interface Iterator { next(value?: any) : IterationResult, } interface IterationResult { value: any, done: boolean, }
當使用for...of循環遍歷某種數據結構時,該循環會自動去尋找 Iterator 接口。一種數據結構只要部署了Iterator接口,咱們就稱這種數據結構是」可遍歷的「(iterable)。接口
ES6 規定,默認的 Iterator接口部署在數據結構的Symbol.iterator屬性,或者說,一個數據結構只要具備Symbol.iterator屬性,就能夠認爲是「可遍歷的」。Symbol.iterator屬性自己是一個函數,就是當前數據結構默認的遍歷器生成函數。執行這個函數,就會返回一個遍歷器。
const obj = { [Symbol.iterator] : function () { return { next: function () { return { value: 1, done: true }; } }; } };
上面代碼中,對象obj是可遍歷的(iterable),由於具備Symbol.iterator屬性。執行這個屬性,會返回一個遍歷器對象。該對象的根本特徵就是具備next方法。每次調用next方法,都會返回一個表明當前成員的信息對象,具備value和done兩個屬性。
ES6 的有些數據結構原生具有Iterator接口(好比數組),即不用任何處理,就能夠被for...of循環遍歷。緣由在於,這些數據結構原生部署了Symbol.iterator屬性(詳見下文),另一些數據結構沒有(好比對象)。凡是部署了Symbol.iterator屬性的數據結構,就稱爲部署了遍歷器接口。調用這個接口,就會返回一個遍歷器對象。
原生具有 Iterator 接口的數據結構以下:
下面的例子是數組的Symbol.iterator屬性:
let arr = ['a', 'b', 'c']; let iter = arr[Symbol.iterator](); iter.next() // { value: 'a', done: false } iter.next() // { value: 'b', done: false } iter.next() // { value: 'c', done: false } iter.next() // { value: undefined, done: true }
上面代碼中,變量arr是一個數組,原生就具備遍歷器接口,部署在arr的Symbol.iterator屬性上面。因此,調用這個屬性,就獲得遍歷器對象。
一個對象若是要具有可被for...of循環調用的 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屬性對應一個函數,執行後返回當前對象的遍歷器對象。
對於相似數組的對象(存在數值鍵名和length屬性),部署 Iterator 接口,有一個簡便方法,就是Symbol.iterator方法直接引用數組的 Iterator 接口:
NodeList.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator]; // 或者 NodeList.prototype[Symbol.iterator] = [][Symbol.iterator]; [...document.querySelectorAll('div')] // 能夠執行了
NodeList 對象是相似數組的對象,原本就具備遍歷接口,能夠直接遍歷。上面代碼中,咱們將它的遍歷接口改爲數組的Symbol.iterator屬性,能夠看到沒有任何影響。
下面是另外一個相似數組的對象調用數組的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' }
注意,普通對象部署數組的Symbol.iterator方法,並沒有效果:
let iterable = { a: 'a', b: 'b', c: 'c', length: 3, [Symbol.iterator]: Array.prototype[Symbol.iterator] }; for (let item of iterable) { console.log(item); // undefined, undefined, undefined }
若是Symbol.iterator方法對應的不是遍歷器生成函數(即會返回一個遍歷器對象),解釋引擎將會報錯:
var obj = {}; obj[Symbol.iterator] = () => 1; [...obj] // TypeError: [] is not a function
對數組和 Set 結構進行解構賦值時,會默認調用Symbol.iterator方法
let set = new Set().add('a').add('b').add('c'); let [x,y] = set; // x='a'; y='b' let [first, ...rest] = set; // first='a'; rest=['b','c'];
擴展運算符(...)也會調用默認的 Iterator 接口
// 例一 var str = 'hello'; [...str] // ['h','e','l','l','o'] // 例二 let arr = ['b', 'c']; ['a', ...arr, 'd'] // ['a', 'b', 'c', 'd']
實際上,這提供了一種簡便機制,只要某個數據結構部署了Iterator接口,就能夠對它使用擴展運算符,將其轉爲數組。
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 }
因爲數組的遍歷會調用遍歷器接口,因此任何接受數組做爲參數的場合,其實都調用了遍歷器接口。下面是一些例子:
字符串是一個相似數組的對象,也原生具備 Iterator 接口
var someString = "hi"; typeof someString[Symbol.iterator] // "function" var iterator = someString[Symbol.iterator](); iterator.next() // { value: "h", done: false } iterator.next() // { value: "i", done: false } iterator.next() // { value: undefined, done: true }
Symbol.iterator方法的最簡單實現:
var myIterable = {}; 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
遍歷器對象除了具備next方法,還能夠具備return方法和throw方法。若是你本身寫遍歷器對象生成函數,那麼next方法是必須部署的,return方法和throw方法是否部署是可選的。
return方法的使用場合是,若是for...of循環提早退出(一般是由於出錯,或者有break語句或continue語句),就會調用return方法。若是一個對象在完成遍歷前,須要清理或釋放資源,就能夠部署return方法。
function readLinesSync(file) { return { next() { return { done: false }; }, return() { file.close(); return { done: true }; }, }; }
上面代碼中,函數readLinesSync接受一個文件對象做爲參數,返回一個遍歷器對象,其中除了next方法,還部署了return方法。下面的三種狀況,都會觸發執行return方法:
// 狀況一 for (let line of readLinesSync(fileName)) { console.log(line); break; } // 狀況二 for (let line of readLinesSync(fileName)) { console.log(line); continue; } // 狀況三 for (let line of readLinesSync(fileName)) { console.log(line); throw new Error(); }
上面代碼中,狀況一輸出文件的第一行之後,就會執行return方法,關閉這個文件;狀況二輸出全部行之後,執行return方法,關閉該文件;狀況三會在執行return方法關閉文件以後,再拋出錯誤。
注意,return方法必須返回一個對象,這是 Generator 規格決定的。
throw方法主要是配合 Generator 函數使用,通常的遍歷器對象用不到這個方法。