ES6+ 迭代協(xié)議
1. 前言
上一節(jié)我們對 ES6 新增的 for...of
做了深入的講解,它可以用于字符串、數(shù)組、類數(shù)組、以及新增的數(shù)據(jù)結(jié)構(gòu) Map/Set 等進(jìn)行遍歷。但是這些能夠使用 for...of
進(jìn)行遍歷的都有一個共同的特性 —— 可迭代。那什么是可迭代呢?
ECMAScript 2015 在一組補充規(guī)范中規(guī)定了兩個協(xié)議:可迭代協(xié)議和迭代器協(xié)議。這兩個規(guī)定不是新的內(nèi)置實現(xiàn)或語法,而是協(xié)議。這些協(xié)議可以被任何數(shù)據(jù)結(jié)構(gòu)遵循,從而可以實現(xiàn)自定義的遍歷,而這些遵守迭代協(xié)議的數(shù)據(jù)結(jié)構(gòu)是可以被 for...of
遍歷的。有了這樣的一個規(guī)范,我們所定義的數(shù)據(jù)結(jié)構(gòu)就會更加豐富了,本節(jié)我們將深入 ES6 中的迭代。
2. 可迭代協(xié)議和迭代器協(xié)議
迭代協(xié)議包括兩方面的內(nèi)容 —— 可迭代協(xié)議和迭代器協(xié)議,下面我們就來看看這兩個協(xié)議都是什么。
2.1 可迭代協(xié)議
什么是可迭代協(xié)議?可以通過 JavaScript 對象定義或定制迭代行為,在 JavaScript 中有一些內(nèi)置類型并且滿足內(nèi)置的可迭代對象,具有迭代的行為。如 Array、Map、Set 等。還有一個比如字面量對象(Object)則沒有,如果要對 Object 進(jìn)行迭代的話需要使用 for...in
循環(huán),但是 for...in
在循環(huán)時需要判斷是否是自身屬性。所以很多時候如果想用 for...of
進(jìn)行迭代時就需要使用對象上的 Object.keys()
等方法提取對象中的 keys 后再去進(jìn)行遍歷操作。
在 ES6 中可迭代協(xié)議規(guī)定想要成為可迭代對象,這個對象必須實現(xiàn) @@iterator
方法。這意味著對象(或者它原型鏈上的某個對象)必須有一個鍵為 @@iterator
的屬性,可通過常量 Symbol.iterator
訪問該屬性。所以,一個對象滿足可迭代協(xié)議的關(guān)鍵在于實現(xiàn) Symbol.iterator
方法,這個方法的返回值是一個符合迭代器協(xié)議的對象,并且是一個無參數(shù)的函數(shù)。
2.2 迭代器協(xié)議
上面說到了在實現(xiàn) Symbol.iterator
方法時需要返回一個滿足迭代器協(xié)議的方法。那么迭代器協(xié)議又是什么呢?
迭代器協(xié)議定義了產(chǎn)生一系列值的一個標(biāo)準(zhǔn)方式,迭起協(xié)議規(guī)定需要返回一個帶 next()
方法的對象。 next()
可以被多次執(zhí)行,每次執(zhí)行都會返回一個對象,該對象包含兩個屬性,done
和 value
:
done
是一個 boolean,在沒有迭代完時返回 false,迭代完成后返回 true;value
就是被迭代的返回值,當(dāng)done
為 true 時可以省略。
實現(xiàn)了以上兩點才會滿足一個迭代器協(xié)議。一般來說可迭代協(xié)議和迭代器協(xié)議在實際的場景中是同時存在的。下面我來看看什么是迭代器?并且怎么使用可迭代協(xié)議和迭代協(xié)議去實現(xiàn)一個迭代器。
3. 迭代器
這里說的迭代器是遵循上面兩個協(xié)議來實現(xiàn)的,在滿足兩個協(xié)議時,我們可以顯式地通過不斷調(diào)用 next () 方法去進(jìn)行迭代。在迭代一個迭代器后,我們稱之為消耗了這個迭代器而,且每個迭代器只能執(zhí)行一次。下面我們來看看怎么實現(xiàn)一個迭代器:
var obj = {}
obj[Symbol.iterator] = function() {
let index = 1;
return {
next() {
if (index <= 10) {
return {value: index++, done: false}
} else {
return {done: true}
}
}
}
}
上面的代碼中根據(jù)可迭代協(xié)議給 obj 對象添加一個 Symbol.iterator
方法,再根據(jù)迭代器協(xié)議返回一個 next()
方法,在每次消耗 next()
時對 index 進(jìn)行加 1 操作。當(dāng) index 大于 10 的時候結(jié)束迭代行為,之后再消耗 next()
返回值不變。
根據(jù)上面的代碼,我們可以顯式的手動調(diào)用 next()
:
var iterator = obj[Symbol.iterator]();
var s = iterator.next();
while(!s.done) {
console.log(s.value);
s = iterator.next();
}
// 1
// 2
// ...
執(zhí)行上面的代碼,在瀏覽器的控制臺中,可以看到大于的結(jié)果是 1 到 10。上面是我們手動執(zhí)行消耗 next()
的方式,上面我們也說了,只要滿足迭代協(xié)議就可以被 for...of
循環(huán),那是不是真的是這樣的呢?下面我們就使用 for...of
對 obj 進(jìn)行循環(huán)。
for (let i of obj) {
console.log(i)
}
// 1
// 2
// ...
在控制臺中執(zhí)行上面的代碼,可以看到和我們使用手動調(diào)用 next()
方式返回打印的結(jié)果是一樣的。
4. 小結(jié)
本節(jié)我們主要學(xué)習(xí)了兩個協(xié)議 —— 可迭代協(xié)議和迭代器協(xié)議,并且通過這兩個協(xié)議實現(xiàn)了一個迭代器。通過這個迭代器我們知道,在滿足這兩個協(xié)議后就可以使用 for...of
進(jìn)行循環(huán),并且我們進(jìn)行顯示調(diào)用進(jìn)行了驗證。
插入案例