有趣的迭代協議

使用迭代協議咱們能夠作什麼

在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]方法呢?由於解構也用到了迭代協議,那不是咱們想要的結果😊。有興趣的同窗能夠試試直接修改原型上的方法

武漢加油!本文完。

相關文章
相關標籤/搜索