在vue模板的列表渲染中,有這樣的語法:javascript
<li v-for="item in list"/> <li v-for="(item,idx) in list"/>
在迭代中獲取一個idx很方便,可是在js的for ... of...循環中,咱們能夠拿到數組中的一項,可是獲得索引卻要費一番功夫。而傳統的基於索引的循環,則要多寫一些代碼。那麼有沒有方法,讓咱們在for... of 循環中,既能拿到數組的項,又能拿到索引呢?以下所示:html
for (let [item, idx] of list) { ... }
答案是確定的,就是要藉助今天的主角,迭代協議了。vue
迭代器協議(iterator protocol) 定義了一種標準的方式來產生一個有限或無限序列的值,而且當全部的值都已經被迭代後,就會有一個默認的返回值。當一個對象只有知足下述條件纔會被認爲是一個迭代器,它實現了一個 next() 的方法,而next方法會返回這樣一個對象:{value, done},value表示本次迭代的值,done表示迭代是否結束。根據這個協議,咱們能夠寫一個迭代器:java
let iterator = { from: 1, to: 10, next() { if (this.from < 10) { return {done: false, value: this.from++} } return {done: true} } }
因而咱們能夠手動調用next()來迭代:數組
iterator.next() // {done: false, value: 1} iterator.next() // {done: false, value: 2} iterator.next() // {done: false, value: 3}
或者使用while循環來迭代:函數
while(true) { let next = iterator.next() if(next.done) break // 迭代結束 console.log(next.value) }
上面的代碼儘管實現了迭代,卻不能使用for .. of .. 或者 [...]
這樣的內置語法來迭代。要實現更天然的迭代方式,咱們還須要瞭解可迭代對象協議:爲了變成可迭代對象, 一個對象必須實現 @@iterator 方法, 意思是這個對象(或者它原型鏈 prototype chain 上的某個對象)必須有一個名字是 Symbol.iterator 的屬性this
這個也很好理解,由於for...of這樣的語法是爲可迭代對象設計的。那麼什麼是可迭代對象呢?打個比方,假如你想35歲之後跑滴滴,那麼首先你必須有一輛車,這時你就被稱爲可跑滴滴的人
。同理,若是一個對象有了Symbol.iterator 的方法,並且這個方法調用後會返回一個迭代器,這時,這個對象就成了一個可迭代對象。下面咱們來看看怎麼使一個普通對象變成可迭代的對象。prototype
// 普通對象 let obj = { a: 'x', b: 'y', c: 'z' }
實現Symbol.iterator就能夠將普通對象變成可迭代對象:設計
let obj = { a: 'x', b: 'y', c: 'z' } obj[Symbol.iterator] = function() { let _keys = Object.keys(this) let _idx = 0 return { next() { let key = _keys[_idx++] if (!key) { return {done: true} } return { value: [key, this[key]], done: false } } } }
這時就可使用 for...of... 來遍歷obj了😄:code
for (let [k, v] of obj) {console.log(k, v)}
若是你以爲爲了要使一個對象變的可迭代,要本身去理清楚什麼next,done很麻煩,那麼生成器函數就是爲你準備的。由於它會返回一個迭代器:
function* generator() { yield 1; } let iter = generator(); iter.next() // {value: 1, done: false} iter.next() // {value: undefined, done: true}
而後咱們把obj的Symbol.iterator
替換成一個生成器函數:
obj[Symbol.iterator] = function*() { let _keys = Object.keys(this) while(_keys.length > 0) { let key = _keys.shift() yield [key, this[key]] } }
這時,你同樣可使用 for...of...來迭代obj了:
for (let [k, v] of obj) { console.log(k, v) }
咱們知道可使用for...of來迭代數組,字符串這些對象,那是由於String, Array, TypedArray, Map, Set 都是可迭代對象,他們的原型都實現了 Symbol.iterator 方法。文章一開始,咱們但願對一個數組迭代時,除了拿到它的值之外,還能夠拿到他的索引,那咱們就須要對[Symbol.iterator]方法進行重載修改,咱們能夠實現一個reload方法來修改數組的Symbol.iterator方法:
function reload(arr) { if (Array.isArray(arr)) { arr[Symbol.iterator] = function*() { for (let i = 0; i < this.length; i++) { yield [this[i], i] } } } } var arr = ['x', 'y', 'z'] reload(arr) for (let [item, idx] of arr) { console.log(item, idx) }
最後一個問題:爲何直接修改Array.prototype[Symbol.iterator]方法呢?由於解構也用到了迭代協議,那不是咱們想要的結果😊。有興趣的同窗能夠試試直接修改原型上的方法
武漢加油!本文完。