Javascript的迭代器和迭代接口詳解
1,什么是迭代器
每一個可迭代對象都對應(yīng)著一個可迭代接口[Symbol.iterator];
[Symbol.iterator]
接口并不是迭代器,他是一個迭代器工廠函數(shù),調(diào)用該迭代接口即可返回一個待執(zhí)行狀態(tài)的迭代器;
不同的原生全局對象都對應(yīng)著不同的迭代器;
const arr = new Array() const map = new Map() const set = new Set() console.log(arr[Symbol.iterator]()) //Array Iterator {} console.log(map[Symbol.iterator]()) //MapIterator {} console.log(set[Symbol.iterator]()) //SetIterator {}
將迭代器狀態(tài)從待執(zhí)行狀態(tài)變?yōu)檎嬲膱?zhí)行:調(diào)用迭代器對象的 .next()方法;而其的返回值,就是next()方法的返回值對象:
const iterator = new Array(1, 2, 3, 4)[Symbol.iterator]() console.log(iterator.next()) //{value: 1, done: false} console.log(iterator.next()) //{value: 2, done: false} console.log(iterator.next()) //{value: 3, done: false} console.log(iterator.next()) //{value: 4, done: false} console.log(iterator.next()) //{value: undefined, done: true}
可以看到,當(dāng)我執(zhí)行第四次的時候,也就是對應(yīng)著arr[3],但此時返回的對象中,done屬性依舊是false,而執(zhí)行第五次時,value變成了undefined,done變成了true,為什么會出現(xiàn)這種情況呢
在解答這個問題之前,我們需要在重新認(rèn)識一下迭代器:
本質(zhì)上來說,迭代器對象就是實(shí)現(xiàn)了next()方法的對象
const myIterator = { next() { if (length) { return { value: 1, done: false } } else { return { value: undefined, done: true } } }, }
如上述,就是一個最簡單的迭代器。調(diào)用next(),會執(zhí)行迭代,返回done為false的迭代器生成對象,直到符合某種條件,返回done為true的迭代器生成對象。
你可以簡單的把Array迭代器原理看作如下所示:
const myIterator = { next() { if (length) { return { value: 1, done: false } } else { return { value: undefined, done: true } } }, }
輸出的結(jié)果也是一樣的:
const iterator = new MyArray(1, 2, 3, 4)[Symbol.iterator]()
console.log(iterator.next()) //{value: 1, done: false}
console.log(iterator.next()) //{value: 2, done: false}
console.log(iterator.next()) //{value: 3, done: false}
console.log(iterator.next()) //{value: 4, done: false}
console.log(iterator.next()) //{value: undefined, done: true}
那么,之前的問題就迎刃而解了。
或許你已經(jīng)發(fā)現(xiàn)了,我自定義了一個類MyArray,并且我手動改寫了他的迭代器接口。那么是否只要為某個不可迭代的對象,實(shí)現(xiàn)了[Symbol.iterator],就可以把它變成一個可迭代對象呢?
答案是肯定的。只要你想,你可以為任何對象加上可迭代協(xié)議,并把它變成可迭代對象。因?yàn)榈鷮ο蟮亩x便是:實(shí)現(xiàn)迭代接口的對象。
2,自定義迭代接口
按照以上思路,我們就可以自己手動實(shí)現(xiàn)一個可迭代的Object對象了:
const prototype = { [Symbol.iterator]() { const entries = Object.entries(this) const { length } = entries let index = 0 return { next() { return index < length ? { value: { key: entries[index][0], value: entries[index++][1], }, done: false, } : { value: undefined, done: true } }, } }, } const obj = Object.create(prototype) obj.name = 'zhang san' obj.age = 28 const objIterator = obj[Symbol.iterator]() console.log(objIterator.next()) //{value: {key:'name',value:'zhang san'}, done: false} console.log(objIterator.next()) //{value: {key:'age',value:28}, done: false} console.log(objIterator.next()) //{value: undefined, done: true}
首先,我們聲明了一個改寫了迭代接口的對象,接著用Obejct.create()創(chuàng)建了以此對象為原型的obj。
該對象實(shí)例本身是沒有迭代接口的,但是會沿著原型鏈去尋找prototype對象是否存在迭代接口。只要能在其原型鏈上找到迭代接口,那么就代表其是一個可迭代對象。如:
const objSon = Object.create(obj, { name: { enumerable: true, writable: true, value: 'zhang xiao san', }, age: { enumerable: true, writable: false, value: 2, }, secret: { enumerable: false, writable: true, value: 'secret', }, }) const sonIterator = objSon[Symbol.iterator]() console.log(sonIterator.next()) //{value: {key:'name',value:'zhang xiao san'}, done: false} console.log(sonIterator.next()) //{value: {key:'age',value:2}, done: false} console.log(sonIterator.next()) //{value: undefined, done: true}
objSon依舊是一個可迭代對象。
那么現(xiàn)在,我們就通過改造迭代接口[Symbol.iterator]的方式,把一個原本不是迭代類型的對象,變成了可迭代對象。
我們可以用該迭代接口來遍歷任何enumerable的屬性。但如果你想將enumrable為false的secret屬性也遍歷出來,那么只需要將迭代接口中的entries改造一下即可,一切皆由你想:
// const entries = Object.entries(this) const ownKeys = Object.getOwnPropertyNames(this) const entries = ownKeys.reduce( (result, key) => [...result, [key, this[key]]], [] )
3,原生語言的迭代
以for - of 為例
for (const item of obj) { console.log(item) } //{key:'name',value:'zhang san'} //{key:'age',value:28} for (const item of objSon) { console.log(item) } //{key:'name',value:'zhang xiao san'} //{key:'age',value:2}
可以看到,無論是obj,還是objSon,都可以正常用for of 循環(huán),并且返回值為迭代器生成對象value屬性的值。
你可以這么理解for - of 的機(jī)制:后臺調(diào)用提供的可迭代對象的工廠函數(shù)[Symbol.iterator],從而創(chuàng)建一個迭代器,然后自動調(diào)用迭代器next執(zhí)行。done為false,則將迭代器生成對象value賦值給item;done為true,則跳出循環(huán):
const objIterator = obj[Symbol.iterator]() { const { value: item,done } = objIterator.next() if(done) break console.log(item) //{key:'name',value:'zhang san'} } { const { value: item,done } = objIterator.next() if(done) break console.log(item) //{key:'age',value:28} } { const { value: item,done } = objIterator.next() if(done) break console.log(item) }
不僅僅是for - of,原生語言的迭代機(jī)制,都與此類似。數(shù)組解構(gòu),拓展操作符,Array.from,new Set(),new Map(),Promise.all(),Promise.race(),yield * 操作符等,都屬于原生迭代語言。
了解了for - of 循環(huán)的機(jī)制之后,大家可以觀察一下下面的例子:
const arr = [1, 2, 3, 4, 5, 6, 7, 8] for (const item of arr) { console.log(item) if (item > 3) break } for (const item of arr) { console.log(item) } //輸出結(jié)果:1,2,3,4 | 1,2,3,4,5,6,7,8 const arrIterator = arr[Symbol.iterator]() for (const item of arrIterator) { console.log(item) if (item > 3) break } for (const item of arrIterator) { console.log(item) } //輸出結(jié)果:1,2,3,4 | 5,6,7,8
循環(huán)arr的輸出結(jié)果與循環(huán)arr迭代器arrIterator的結(jié)果明顯的不同。為什么會出現(xiàn)這種現(xiàn)象呢?
在迭代器對象中,還有一個很重要的知識點(diǎn):迭代器對象是一個一次性的,不可逆的對象。
因此,在迭代中某個地方終止,那么只能接著上一次終止的位置繼續(xù)執(zhí)行,而不會從頭開始。
那么為什么對于arr本身使用for - of,卻沒有接著執(zhí)行而是從頭開始呢?可以回到介紹for - of 循環(huán)機(jī)制的那部分,其中有一句話:后臺調(diào)用提供的可迭代對象的工廠函數(shù)[Symbol.iterator],從而創(chuàng)建一個迭代器。
也就是說,每調(diào)用一次for - of循環(huán),都會創(chuàng)建一個新的迭代器對象,而該迭代器對象,在循環(huán)結(jié)束時就會被當(dāng)作垃圾對象被回收。
雖然arr連續(xù)調(diào)用了兩次for - of循環(huán),但是在循環(huán)體的內(nèi)部,并不是同一個迭代器對象。因此,即使上一個迭代器在item>3這個條件處中止了,但是下一次循環(huán)的迭代器對象,是一個全新的,還沒有執(zhí)行過的迭代器對象。
那么對于調(diào)用arr[Symbol.iterator]接口生成的的迭代器對象arrIterator,為什么會出現(xiàn)繼續(xù)上次執(zhí)行的情況呢,換句話說,為什么arrIterator的兩次for循環(huán),沒有產(chǎn)生兩次迭代器對象?
其實(shí),迭代器對象本身,也實(shí)現(xiàn)了迭代器接口,也就是說。arr有一個迭代器接口[Symbol.iterator],而迭代器對象arrIterator也有一個迭代器接口[Symbol.iterator],并且,調(diào)用該迭代器對象,返回其本身:
console.log(arrIterator[Symbol.iterator]() === arrIterator) // true
所以雖然每次執(zhí)行for-of循環(huán)都會同樣的調(diào)用迭代器接口,但是該迭代器接口返回的對象就是arr的迭代器對象本身,而迭代器對象是一個一次性,不可逆的對象。因此,為什么會出現(xiàn)上述現(xiàn)象,也就顯而易見了。
細(xì)心的朋友可能會想到一個問題:我們在之前手動實(shí)現(xiàn)的可迭代對象,其迭代器對象是否支持了迭代接口?是的,他不能。
for (const item of objIterator) { console.log(item) } //Uncaught TypeError: objIterator is not iterable
但其實(shí)我們要優(yōu)化也很簡單,只要將該迭代器對象也實(shí)現(xiàn)一個迭代接口,并且該迭代接口工廠函數(shù)返回其本身,即可。
const prototype = { [Symbol.iterator]() { // const entries = Object.entries(this) const ownKeys = Object.getOwnPropertyNames(this) const entries = ownKeys.reduce( (result, key) => [...result, [key, this[key]]], [] ) const { length } = entries let index = 0 return { next() { return index < length ? { value: { key: entries[index][0], value: entries[index++][1], }, done: false, } : { value: undefined, done: true } }, [Symbol.iterator]() { return this }, } }, } const objIterator = obj[Symbol.iterator]() for (const item of objIterator) { console.log(item) if (item.key === 'name') break } for (const item of objIterator) { console.log(item) } // {key: 'name', value: 'zhang san'} // {key: 'age', value: 28}
現(xiàn)在,你可以用文中所介紹的方法,將任何對象變成一個可迭代對象。
總結(jié)
本篇文章就到這里了,希望能夠給你帶來幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!
相關(guān)文章
Javascript實(shí)例教程(19) 使用HoTMetal(7)
Javascript實(shí)例教程(19) 使用HoTMetal(7)...2006-12-12JavaScript italics方法入門實(shí)例(把字符串顯示為斜體)
這篇文章主要介紹了JavaScript italics方法入門實(shí)例,italics方法用于把字符串顯示為斜體,需要的朋友可以參考下2014-10-10javascript unicode與GBK2312(中文)編碼轉(zhuǎn)換方法
本文提供了一個javascript的unicode與GBK2312編碼相互轉(zhuǎn)換的方法,大家可以參考使用,實(shí)用的小實(shí)例2013-11-11