Javascript的迭代器和迭代接口詳解
1,什么是迭代器
每一個(gè)可迭代對象都對應(yīng)著一個(gè)可迭代接口[Symbol.iterator];
[Symbol.iterator]接口并不是迭代器,他是一個(gè)迭代器工廠函數(shù),調(diào)用該迭代接口即可返回一個(gè)待執(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í)行第四次的時(shí)候,也就是對應(yīng)著arr[3],但此時(shí)返回的對象中,done屬性依舊是false,而執(zhí)行第五次時(shí),value變成了undefined,done變成了true,為什么會(huì)出現(xiàn)這種情況呢
在解答這個(gè)問題之前,我們需要在重新認(rèn)識(shí)一下迭代器:
本質(zhì)上來說,迭代器對象就是實(shí)現(xiàn)了next()方法的對象
const myIterator = {
next() {
if (length) {
return { value: 1, done: false }
} else {
return { value: undefined, done: true }
}
},
}如上述,就是一個(gè)最簡單的迭代器。調(diào)用next(),會(huì)執(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)了,我自定義了一個(gè)類MyArray,并且我手動(dòng)改寫了他的迭代器接口。那么是否只要為某個(gè)不可迭代的對象,實(shí)現(xiàn)了[Symbol.iterator],就可以把它變成一個(gè)可迭代對象呢?
答案是肯定的。只要你想,你可以為任何對象加上可迭代協(xié)議,并把它變成可迭代對象。因?yàn)榈鷮ο蟮亩x便是:實(shí)現(xiàn)迭代接口的對象。
2,自定義迭代接口
按照以上思路,我們就可以自己手動(dòng)實(shí)現(xiàn)一個(gè)可迭代的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}首先,我們聲明了一個(gè)改寫了迭代接口的對象,接著用Obejct.create()創(chuàng)建了以此對象為原型的obj。
該對象實(shí)例本身是沒有迭代接口的,但是會(huì)沿著原型鏈去尋找prototype對象是否存在迭代接口。只要能在其原型鏈上找到迭代接口,那么就代表其是一個(gè)可迭代對象。如:
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依舊是一個(gè)可迭代對象。
那么現(xiàn)在,我們就通過改造迭代接口[Symbol.iterator]的方式,把一個(gè)原本不是迭代類型的對象,變成了可迭代對象。
我們可以用該迭代接口來遍歷任何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ī)制:后臺(tái)調(diào)用提供的可迭代對象的工廠函數(shù)[Symbol.iterator],從而創(chuàng)建一個(gè)迭代器,然后自動(dò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é)果明顯的不同。為什么會(huì)出現(xiàn)這種現(xiàn)象呢?
在迭代器對象中,還有一個(gè)很重要的知識(shí)點(diǎn):迭代器對象是一個(gè)一次性的,不可逆的對象。
因此,在迭代中某個(gè)地方終止,那么只能接著上一次終止的位置繼續(xù)執(zhí)行,而不會(huì)從頭開始。
那么為什么對于arr本身使用for - of,卻沒有接著執(zhí)行而是從頭開始呢?可以回到介紹for - of 循環(huán)機(jī)制的那部分,其中有一句話:后臺(tái)調(diào)用提供的可迭代對象的工廠函數(shù)[Symbol.iterator],從而創(chuàng)建一個(gè)迭代器。
也就是說,每調(diào)用一次for - of循環(huán),都會(huì)創(chuàng)建一個(gè)新的迭代器對象,而該迭代器對象,在循環(huán)結(jié)束時(shí)就會(huì)被當(dāng)作垃圾對象被回收。
雖然arr連續(xù)調(diào)用了兩次for - of循環(huán),但是在循環(huán)體的內(nèi)部,并不是同一個(gè)迭代器對象。因此,即使上一個(gè)迭代器在item>3這個(gè)條件處中止了,但是下一次循環(huán)的迭代器對象,是一個(gè)全新的,還沒有執(zhí)行過的迭代器對象。
那么對于調(diào)用arr[Symbol.iterator]接口生成的的迭代器對象arrIterator,為什么會(huì)出現(xiàn)繼續(xù)上次執(zhí)行的情況呢,換句話說,為什么arrIterator的兩次for循環(huán),沒有產(chǎn)生兩次迭代器對象?
其實(shí),迭代器對象本身,也實(shí)現(xiàn)了迭代器接口,也就是說。arr有一個(gè)迭代器接口[Symbol.iterator],而迭代器對象arrIterator也有一個(gè)迭代器接口[Symbol.iterator],并且,調(diào)用該迭代器對象,返回其本身:
console.log(arrIterator[Symbol.iterator]() === arrIterator) // true
所以雖然每次執(zhí)行for-of循環(huán)都會(huì)同樣的調(diào)用迭代器接口,但是該迭代器接口返回的對象就是arr的迭代器對象本身,而迭代器對象是一個(gè)一次性,不可逆的對象。因此,為什么會(huì)出現(xiàn)上述現(xiàn)象,也就顯而易見了。
細(xì)心的朋友可能會(huì)想到一個(gè)問題:我們在之前手動(dòng)實(shí)現(xiàn)的可迭代對象,其迭代器對象是否支持了迭代接口?是的,他不能。
for (const item of objIterator) {
console.log(item)
}
//Uncaught TypeError: objIterator is not iterable但其實(shí)我們要優(yōu)化也很簡單,只要將該迭代器對象也實(shí)現(xiàn)一個(gè)迭代接口,并且該迭代接口工廠函數(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)在,你可以用文中所介紹的方法,將任何對象變成一個(gè)可迭代對象。
總結(jié)
本篇文章就到這里了,希望能夠給你帶來幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!
相關(guān)文章
JavaScript中的toDateString()方法使用詳解
這篇文章主要介紹了JavaScript中的toDateString()方法使用詳解,是JS入門學(xué)習(xí)中的基礎(chǔ)知識(shí),需要的朋友可以參考下2015-06-06
javascrpt綁定事件之匿名函數(shù)無法解除綁定問題
經(jīng)常聽到有人說,匿名函數(shù)綁定事件不好控制啊,無法解除綁定啊,本文將介紹詳細(xì)的解決方法,需要的朋友可以參考下2012-12-12
關(guān)于JavaScript限制字?jǐn)?shù)的輸入框的那些事
這篇文章主要介紹了關(guān)于JavaScript限制字?jǐn)?shù)的輸入框在項(xiàng)目過程中容易遇到的各種坑的匯總,非常的詳細(xì),有需要的小伙伴可以參考下2016-08-08
javascript 學(xué)習(xí)筆記(四) 倒計(jì)時(shí)程序代碼
javascript 學(xué)習(xí)筆記(四) 倒計(jì)時(shí)程序代碼,需要的朋友可以參考下。2011-04-04
JavaScript高級程序設(shè)計(jì)(第3版)學(xué)習(xí)筆記13 ECMAScript5新特性
通常而言,JavaScript由ECMAScript核心、BOM和DOM三部分構(gòu)成,前面的文章將ECMAScript核心部分粗略的過了一篇2012-10-10

