第十五章 Iterator和for...of循環(huán)

Iterator(遍歷器)的概念

??javascript原有的表示"集合"的數(shù)據(jù)結(jié)構(gòu)主要是數(shù)組和對(duì)象,ES6又添加了Map和Set,這樣就需要一種統(tǒng)一的接口機(jī)制來(lái)處理所有不同的數(shù)據(jù)結(jié)構(gòu)。
??遍歷器就是這樣一種機(jī)制,它是一種接口,為各種不同的數(shù)據(jù)結(jié)構(gòu)提供統(tǒng)一的訪問(wèn)機(jī)制。任何數(shù)據(jù)結(jié)構(gòu),只要部署Iterator接口,就可以完成遍歷操作(即依次處理該數(shù)據(jù)結(jié)構(gòu)的所有成員)。
??Iterator的作用有3個(gè):一是為各種數(shù)據(jù)結(jié)構(gòu)訪問(wèn)自身成員提供統(tǒng)一的、簡(jiǎn)便的訪問(wèn)接口(方法);二是使得數(shù)據(jù)結(jié)構(gòu)的成員能夠按某種次序排列;三是ES6創(chuàng)造了一種新的遍歷命令---for...of循環(huán),Iterator接口主要用于for...of循環(huán)。

Iterator的遍歷過(guò)程如下

1.創(chuàng)建一個(gè)指針對(duì)象,指向當(dāng)前數(shù)據(jù)結(jié)構(gòu)的起始位置。也就是說(shuō),遍歷器對(duì)象本質(zhì)上就是一個(gè)指針對(duì)象。
2.第一次調(diào)用指針對(duì)象的next方法,可以將指針指向數(shù)據(jù)結(jié)構(gòu)的第一個(gè)成員。
3.第二次調(diào)用指針對(duì)象的next方法,指針就指向數(shù)據(jù)結(jié)構(gòu)的第二個(gè)成員。
4.不斷調(diào)用指針對(duì)象的next方法,直到它指向數(shù)據(jù)結(jié)構(gòu)的結(jié)束位置。
每次調(diào)用next方法都會(huì)返回?cái)?shù)據(jù)結(jié)構(gòu)的當(dāng)前成員信息。具體來(lái)說(shuō)就是返回一個(gè)包含value和done兩個(gè)屬性的對(duì)象。其中,value屬性是當(dāng)前成員的值,done屬性是一個(gè)布爾值,表示遍歷是否結(jié)束。
下面是一個(gè)模擬next方法返回值的例子。

let it = makeInterator(['a', 'b'])
function makeInterator(array) {
    let nextIndex = 0
    return {
        next: function () {
            return nextIndex < array.length ?
            {value: array[nextIndex++], done: false} :
            {value: undefined, done: true}
        }
    }
}
it.next()    // {value: 'a', done: false}
it.next()    // {value: 'b', done: false}
it.next()    // {value: undefined, done: true}

上面的代碼定義了一個(gè)makeInterator函數(shù),它是一個(gè)遍歷器生成函數(shù),作用就是返回一個(gè)遍歷器對(duì)象。對(duì)數(shù)組['a', 'b']執(zhí)行這個(gè)函數(shù),就會(huì)返回該數(shù)組的遍歷器對(duì)象(即指針對(duì)象)it。
指針對(duì)象的next方法用于移動(dòng)指針。不斷調(diào)用這個(gè)方法就會(huì)依次指向數(shù)組的成員,next方法返回一個(gè)對(duì)象,表示當(dāng)前數(shù)據(jù)成員的信息。總之,調(diào)用指針對(duì)象的next方法就可以遍歷事先給定的數(shù)據(jù)結(jié)構(gòu)。
由于Iterator只是把接口規(guī)格加到了數(shù)據(jù)結(jié)構(gòu)上,所以遍歷器與所遍歷的數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn)上是分開(kāi)的,完全可以寫(xiě)出沒(méi)有對(duì)應(yīng)數(shù)據(jù)結(jié)構(gòu)的遍歷器對(duì)象。

默認(rèn)Iterator接口

Iterator接口的目的是為所有數(shù)據(jù)結(jié)構(gòu)提供一種統(tǒng)一的訪問(wèn)機(jī)制,即具有Iterator接口的數(shù)據(jù)都可以被for...of循環(huán)。當(dāng)使用for...of循環(huán)遍歷某種數(shù)據(jù)結(jié)構(gòu)時(shí),該循環(huán)會(huì)自動(dòng)去尋找Iterator接口。數(shù)據(jù)結(jié)構(gòu)只要部署了Iterator接口,我們就稱這種數(shù)據(jù)結(jié)構(gòu)為"可遍歷"(iterable)的。
ES6規(guī)定,默認(rèn)的Iterator接口部署在數(shù)據(jù)結(jié)構(gòu)的Symbol.iterator屬性,或者說(shuō),一個(gè)數(shù)據(jù)結(jié)構(gòu)只要具有Symbol.iterator屬性,就可以認(rèn)為是"可遍歷"(iterable)。調(diào)用Symbol.iterator方法,我們就會(huì)得到當(dāng)前數(shù)據(jù)結(jié)構(gòu)默認(rèn)的遍歷器生成函數(shù)。Symbol.iterator本身是一個(gè)表達(dá)式,返回Symbol對(duì)象的iterator屬性,這是一個(gè)預(yù)定義好的、類(lèi)型為Symbol的特殊值,所以要放在方括號(hào)中。用for...of遍歷數(shù)據(jù),實(shí)際上就是調(diào)用Symbol.iterator屬性。

const obj = {
    [Symbol.iterator]: function () {
        return {
           next: function () {
                return {
                   value: 1,
                   done: true
                }
           }
        }
    }
}

上面的代碼中,對(duì)象obj是可遍歷的,因?yàn)槠渚哂蠸ymbol.iterator屬性。執(zhí)行這個(gè)屬性會(huì)返回一個(gè)遍歷器對(duì)象。
ES6的有些數(shù)據(jù)結(jié)構(gòu)原生具備Iterator接口,即不用任何處理就可以被for...of循環(huán)遍歷。原因在于,這些數(shù)據(jù)結(jié)構(gòu)原生部署了Symbol.iterator屬性,原生具備Iterator接口的數(shù)據(jù)結(jié)構(gòu)如下:

  • Array
  • Map
  • Set
  • String
  • 函數(shù)的arguments對(duì)象
  • DOM和NodeList對(duì)象
    下面的例子是數(shù)組的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}

其他數(shù)據(jù)結(jié)構(gòu)(主要是對(duì)象)的Iterator接口都需要自己在Symbol.iterator屬性上面部署,對(duì)象之所以沒(méi)有默認(rèn)部署Iterator接口,是因?yàn)閷?duì)象屬性的遍歷先后順序是不確定的,需要開(kāi)發(fā)者手動(dòng)指定。對(duì)象部署遍歷器并不是很必要,因?yàn)檫@時(shí)對(duì)象可以用Map替代。
對(duì)于類(lèi)數(shù)組的對(duì)象(存在數(shù)值鍵名和length屬性),部署Iterator接口有一個(gè)簡(jiǎn)便方法,即使用Symbol.iterator方法直接引用數(shù)組的Iterator接口。

NodeList.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator]
// 或者
NodeList.prototype[Symbol.iterator] = [].prototype[Symbol.iterator]
// 或者
[...document.querySelectorAll('div')]

下面是另一組類(lèi)數(shù)組的對(duì)象調(diào)用數(shù)組的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

注意,普通對(duì)象部署數(shù)組的Symbol.iterator方法并無(wú)效果。

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

上面代碼中因?yàn)閕terable不是一個(gè)類(lèi)數(shù)組對(duì)象,所以用Array.prototype的Symbol.iterator方法沒(méi)有效果。
遍歷器就是一個(gè)遍歷器對(duì)象,里面是一個(gè)指針?lè)椒ǎ粩嗾{(diào)用這個(gè)方法就會(huì)依次指向當(dāng)前數(shù)據(jù)結(jié)構(gòu)的所有成員

調(diào)用Iterator接口的場(chǎng)合

除了for...of,有一些地方會(huì)默認(rèn)調(diào)用Iterator接口(即Symbol.iterator方法)。

解構(gòu)賦值

對(duì)數(shù)組和Set結(jié)構(gòu)進(jìn)行解構(gòu)賦值時(shí),會(huì)默認(rèn)調(diào)用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']
擴(kuò)展運(yùn)算符

擴(kuò)展運(yùn)算符也會(huì)默認(rèn)調(diào)用Iterator接口

let str = 'hello'
[...str]   // ['h', 'e', 'l', 'l', 'o']

let arr = ['b', 'c']
console.log(['a', ...arr, 'd'])
// ['a', 'b', 'c', 'd']

只要某個(gè)數(shù)據(jù)結(jié)構(gòu)部署了Iterator接口,就可以將其轉(zhuǎn)為數(shù)組。

yield*

yield*后面跟的是一個(gè)可遍歷的結(jié)構(gòu),它會(huì)調(diào)用該結(jié)構(gòu)的遍歷器接口。

let generator = function* () {
    yield 1;
    yield* [2, 3, 4];
    yield 5;
} 
let 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}
其他場(chǎng)合

由于數(shù)組的遍歷會(huì)調(diào)用遍歷器接口,所以任何接受數(shù)組作為參數(shù)的場(chǎng)合其實(shí)都調(diào)用了遍歷器接口。下面是一些例子:

  • for...of
  • Array.from()
  • Map()、Set()、WeakMap()和WeakSet()(比如new Map([['a', 1], ['b', 2]]))
  • Promise.all()
  • Promise.race()

字符串的Iterator接口

字符串是一個(gè)類(lèi)數(shù)組的對(duì)象,也具有原生Iterator接口。

let someString = 'hi'
let iterator = someString[Symbol.iterator]()
iterator.next()     // {value: 'h', done: false}
iterator.next()    // {value: 'i', done: false}
iterator.next()    // {value: undefined, done: true}

可以覆蓋原生的Symbol.iterator方法達(dá)到修改遍歷器行為的目的。

Iterator接口與Generator函數(shù)

遍歷器對(duì)象的return()、throw()

遍歷器對(duì)象除了具有next方法,還可以具有return方法和throw方法。如果自己寫(xiě)遍歷器對(duì)象生成函數(shù),那么next方法是必須部署的,return方法和throw方法則是可選部署的。
return方法的使用場(chǎng)合是,如果for...of循環(huán)提前退出(通常是因?yàn)槌鲥e(cuò)或者有break語(yǔ)句或continue語(yǔ)句),就會(huì)調(diào)用return方法;如果一個(gè)對(duì)象在完成遍歷前需要清理或釋放資源,就可以部署return方法。

function readLinesSync (file) {
    return {
       next() {
           return {done: true}
       },
      return () {
          file.close()
          return {done: true}
      }
    }
}

上面代碼中遍歷器除了部署了next方法還部署了return方法,當(dāng)循環(huán)因?yàn)槌鲥e(cuò)或者有break語(yǔ)句或continue語(yǔ)句就會(huì)觸發(fā)return方法。
注意:return方法必須返回一個(gè)對(duì)象,這是Generator規(guī)格決定的。
throw方法主要配合Generator函數(shù)使用,一般的遍歷器對(duì)象用不到這個(gè)方法。

for...of循環(huán)

一個(gè)數(shù)據(jù)結(jié)構(gòu)只要部署了Symbol.iterator屬性,就被視為具有Iterator接口,就可以用for...of循環(huán)遍歷它的成員。也就是說(shuō),for...of循環(huán)內(nèi)部調(diào)用的是數(shù)據(jù)結(jié)構(gòu)的Symbol.iterator方法。
for...of循環(huán)可以使用的范圍包括數(shù)組、Set和Map結(jié)構(gòu)、某些類(lèi)似數(shù)據(jù)的對(duì)象(比如arguments對(duì)象、DOM NodeList對(duì)象)、后文的Generator對(duì)象以及字符串。

類(lèi)似數(shù)組的對(duì)象

不是所有類(lèi)似數(shù)組的對(duì)象都具有Iterator接口,一個(gè)簡(jiǎn)便的解決方法就是使用Array.from方法將其轉(zhuǎn)為數(shù)組。

let arrayLike = {length: 2, 0: 'a', 1: 'b'}
for (let x of arrayLike) {
   console.log(x)
}
// 報(bào)錯(cuò)

for (let x of Array.from(arrayLike)) {
    console.log(x)
}
// 正確

總結(jié):這一篇講了遍歷器,為了解決不同數(shù)據(jù)結(jié)構(gòu)可以用統(tǒng)一的方法去遍歷,如果每種數(shù)據(jù)結(jié)構(gòu)都需要不同的方法去處理,就會(huì)造成代碼復(fù)雜度上升,大量代碼不可復(fù)用,不利于維護(hù)和開(kāi)發(fā)。解決方案就是在某些數(shù)據(jù)結(jié)構(gòu)的原型上添加一個(gè)Symbol.iterator屬性,某些數(shù)據(jù)結(jié)構(gòu)在用某些方法循環(huán)遍歷的時(shí)候,該循環(huán)會(huì)自動(dòng)尋找Iterator接口。

const obj = {
    [Symbol.iterator]: function () {
        return {
           next: function () {
                return {
                   value: 1,
                   done: true
                }
           }
        }
    }
}

上面代碼中obj是可遍歷的數(shù)據(jù)結(jié)構(gòu),具有Iterator接口的數(shù)據(jù)結(jié)構(gòu)原型上都有一個(gè)特殊屬性Symbol.iterator,右邊的函數(shù)是Iterator接口(遍歷器生成函數(shù)),里面的對(duì)象是遍歷器對(duì)象(指針對(duì)象),next方法用于移動(dòng)指針,指針應(yīng)該是下標(biāo),最里面的對(duì)象是成員信息。
內(nèi)容:遍歷器的概念還有遍歷器的使用場(chǎng)景以及如何修改遍歷器。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Iterator(遍歷器)的概念 JavaScript原有的表示“集合”的數(shù)據(jù)結(jié)構(gòu),主要是數(shù)組和對(duì)象,ES6又添加...
    oWSQo閱讀 665評(píng)論 0 1
  • Iterator(遍歷器)的概念 默認(rèn) Iterator 接口 調(diào)用 Iterator 接口的場(chǎng)合 字符串的 It...
    Android_馮星閱讀 359評(píng)論 0 0
  • Iterator(遍歷器)的概念 JavaScript原有的表示“集合”的數(shù)據(jù)結(jié)構(gòu),主要是數(shù)組(Array)和對(duì)象...
    呼呼哥閱讀 4,541評(píng)論 0 2
  • 收到很多雨天晴,很感動(dòng),在這個(gè)環(huán)境里,這些和煦用心的話最能打動(dòng)人,從雨天晴里能看到很多,很多平時(shí)聯(lián)系很少的人給你寫(xiě)...
    Hi_張閱讀 280評(píng)論 0 0
  • 這一次痛苦了太久,又一次幾乎將我淹沒(méi)。 準(zhǔn)備到圖書(shū)館了這幾天。找自己喜歡的書(shū)看,轉(zhuǎn)移自己的注意力。多接觸一些美好的...
    秋子的追尋閱讀 166評(píng)論 0 0

友情鏈接更多精彩內(nèi)容