爲了方便集合數據的遍歷,在ES6中引入了一個iteration的概念。爲咱們提供了更加方便的數據遍歷的手段。node
一塊兒來學習一下吧。es6
iteration也稱爲遍歷,就是像數據庫的遊標同樣,一步一步的遍歷集合或者對象的數據。數據庫
根據ES6的定義,iteration主要由三部分組成:數組
先看下Iterable的定義:學習
interface Iterable { [Symbol.iterator]() : Iterator; }
Iterable表示這個對象裏面有可遍歷的數據,而且須要實現一個能夠生成Iterator的工廠方法。this
interface Iterator { next() : IteratorResult; }
能夠從Iterable中構建Iterator。Iterator是一個相似遊標的概念,能夠經過next訪問到IteratorResult。code
IteratorResult是每次調用next方法獲得的數據。對象
interface IteratorResult { value: any; done: boolean; }
IteratorResult中除了有一個value值表示要獲取到的數據以外,還有一個done,表示是否遍歷完成。教程
Iterable是一個接口,經過這個接口,咱們能夠鏈接數據提供者和數據消費者。接口
Iterable對象叫作數據提供者。對於數據消費者來講,除了能夠調用next方法來獲取數據以外,還可使用for-of 或者 ...擴展運算符來進行遍歷。
for-of的例子:
for (const x of ['a', 'b', 'c']) { console.log(x); }
...擴展運算符的例子:
const arr = [...new Set(['a', 'b', 'c'])];
ES6中,能夠被稱爲Iterable對象的有下面幾種:
先看一個Arrays的狀況,假如咱們有一個Arrays,能夠經過Symbol.iterator這個key來獲取到Iterator:
> const arr = ['a', 'b', 'c']; > const 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 }
更加簡單的辦法就是使用for-of:
for (const x of ['a', 'b']) { console.log(x); } // Output: // 'a' // 'b'
看一個遍歷String的狀況,String的遍歷是經過Unicode code points來區分的:
for (const x of 'a\uD83D\uDC0A') { console.log(x); } // Output: // 'a' // '\uD83D\uDC0A' (crocodile emoji)
上面的例子中,基礎類型的String在遍歷的時候,會自動轉換成爲String對象。
Maps是經過遍歷entries來實現的:
const map = new Map().set('a', 1).set('b', 2); for (const pair of map) { console.log(pair); } // Output: // ['a', 1] // ['b', 2]
還記得以前提到的WeakMaps嗎?
WeakMap,WeakSet和Map於Set的區別在於,WeakMap的key只能是Object對象,不能是基本類型。
爲何會有WeakMap呢?
對於JS中的Map來講,一般須要維護兩個數組,第一個數組中存儲key,第二個數組中存儲value。每次添加和刪除item的時候,都須要同時操做兩個數組。
這種實現有兩個缺點,第一個缺點是每次查找的時候都須要遍歷key的數組,而後找到對應的index,再經過index來從第二個數組中查找value。
第二個缺點就是key和value是強綁定的,即便key再也不被使用了,也不會被垃圾回收。
因此引入了WeakMap的概念,在WeakMap中,key和value沒有這樣的強綁定關係,key若是再也不被使用的話,能夠被垃圾回收器回收。
由於引用關係是weak的,因此weakMap不支持key的遍歷,若是你想遍歷key的話,請使用Map。
看下Set的遍歷:
const set = new Set().add('a').add('b'); for (const x of set) { console.log(x); } // Output: // 'a' // 'b'
咱們還能夠遍歷arguments對象:
function printArgs() { for (const x of arguments) { console.log(x); } } printArgs('a', 'b'); // Output: // 'a' // 'b'
對於大部分DOM來講,也是能夠遍歷的:
for (const node of document.querySelectorAll('div')) { ··· }
簡單對象就是經過字面量建立出來的對象,這些對象雖然也有key-value的內容,可是是不可遍歷的。
爲何呢?
由於可遍歷對象好比Array,Map,Set也是普通對象的一種特例。若是普通對象能夠遍歷了,那麼會致使能夠遍歷對象的一些遍歷中的衝突。
for (const x of {}) { // TypeError console.log(x); }
雖然不能直接遍歷普通對象,可是咱們能夠經過使用objectEntries方法來遍歷普通對象。
先看下objectEntries的實現:
function objectEntries(obj) { let iter = Reflect.ownKeys(obj)[Symbol.iterator](); return { [Symbol.iterator]() { return this; }, next() { let { done, value: key } = iter.next(); if (done) { return { done: true }; } return { value: [key, obj[key]] }; } }; }
咱們經過Reflect.ownKeys()反射拿到對象中的iterator.而後經過這個iterator來進行普通對象的遍歷。
看下具體的使用:
const obj = { first: 'Jane', last: 'Doe' }; for (const [key,value] of objectEntries(obj)) { console.log(`${key}: ${value}`); } // Output: // first: Jane // last: Doe
除了ES6中默認的iterables以外,咱們還能夠自定義iterables。
由於iterables是一個接口,咱們只須要實現它就能夠了。咱們看一個iterables的例子:
function iterateOver(...args) { let index = 0; const iterable = { [Symbol.iterator]() { const iterator = { next() { if (index < args.length) { return { value: args[index++] }; } else { return { done: true }; } } }; return iterator; } } return iterable; }
iterateOver方法會返回一個iterable對象。在這個對象中,咱們實現了Symbol.iterator爲key的方法。這個方法返回一個iterator,在iterator中,咱們實現了next方法。
上面的方法使用起來是下面的效果:
// Using `iterateOver()`: for (const x of iterateOver('fee', 'fi', 'fo', 'fum')) { console.log(x); } // Output: // fee // fi // fo // fum
上面的例子中,若是Symbol.iterator返回的對象是iterable自己,那麼iterable也是一個iterator。
function iterateOver(...args) { let index = 0; const iterable = { [Symbol.iterator]() { return this; }, next() { if (index < args.length) { return { value: args[index++] }; } else { return { done: true }; } }, }; return iterable; }
這樣作的好處就是,咱們可使用for-of同時遍歷iterables和iterators,以下所示:
const arr = ['a', 'b']; const iterator = arr[Symbol.iterator](); for (const x of iterator) { console.log(x); // a break; } // Continue with same iterator: for (const x of iterator) { console.log(x); // b }
若是咱們須要遍歷的過程當中,從iterators中返回該怎麼處理呢?
經過實現return方法,咱們能夠在程序中斷的時候(break,return,throw)調用iterators的return。
function createIterable() { let done = false; const iterable = { [Symbol.iterator]() { return this; }, next() { if (!done) { done = true; return { done: false, value: 'a' }; } else { return { done: true, value: undefined }; } }, return() { console.log('return() was called!'); }, }; return iterable; } for (const x of createIterable()) { console.log(x); break; } // Output: // a // return() was called!
上面例子中,咱們經過break來中斷遍歷,最終致使return方法的調用。
注意,return方法必需要返回一個對象,{ done: true, value: x }
上面就是ES6中引入的Iterables和iterators的一些概念。
本文做者:flydean程序那些事本文連接:http://www.flydean.com/es6-iterables-iterator/
本文來源:flydean的博客
歡迎關注個人公衆號:「程序那些事」最通俗的解讀,最深入的乾貨,最簡潔的教程,衆多你不知道的小技巧等你來發現!