JavaScript中的迭代器和可迭代對象與生成器
1. 什么是迭代器?
概念: 迭代器(iterator),是確使用戶可在容器對象(container,例如鏈表或數(shù)組)上遍訪的對象[1][2][3],設計人員使用此接口無需關心容器對象的內存分配的實現(xiàn)細節(jié)。
JS中的迭代器
- 其本質就是一個對象,符合迭代器協(xié)議(iterator protocol)
- 迭代器協(xié)議
其對象返回一個next函數(shù)
調用next函數(shù)返回一個對象,其對象中包含兩個屬性done
(完成),它的值為布爾類型,也就是true/false
。- 如果這個迭代器沒有迭代完成即返回
{done:false}
- 當這個迭代器完成了即返回
{done:true}
- 如果這個迭代器沒有迭代完成即返回
value
(值),它可以返回js中的任何值,TS中表示可為:value:any
類型
1.1 迭代器的基本實現(xiàn)
思考以下代碼:
let index = 0 const bears = ['ice', 'panda', 'grizzly'] let iterator = { next() { if (index < bears.length) { return { done: false, value: bears[index++] } } return { done: true, value: undefined } } } console.log(iterator.next()) //{ done: false, value: 'ice' } console.log(iterator.next()) //{ done: false, value: 'panda' } console.log(iterator.next()) //{ done: false, value: 'grizzly' } console.log(iterator.next()) //{ done: true, value: undefined }
- 是一個對象,實現(xiàn)了
next
方法,next
方法返回了一個對象,有done
屬性和value
屬性,且key
的值類型也為boolean
或any
,符合迭代器協(xié)議,是一個妥妥的迭代器沒跑了。 - 弊端
- 違背了高內聚思想,明明
index
和iterator
對象是屬于一個整體,我卻使用了全局變量,從V8引擎的GC,可達性(也就是標記清除)來看,如果bears = null
,不手動設置為null很有可能會造成內存泄漏,并且內聚性低。 - 假如我要創(chuàng)建一百個迭代器對象呢? 那我就自己定義一百遍嗎?肯定錯誤的,我們要把它封裝起來,這樣內聚性又高,又能進行復用,一舉兩得,一石二鳥,真的是
very beautiful
,very 優(yōu)雅。
- 違背了高內聚思想,明明
1.2 迭代器的封裝實現(xiàn)
思考一下代碼:
const bears = ['ice', 'panda', 'grizzly'] function createArrIterator(arr) { let index = 0 let _iterator = { next() { if (index < arr.length) { return { done: false, value: arr[index++] } } return { done: true, value: undefined } } } return _iterator } let iter = createArrIterator(bears) console.log(iter.next()) console.log(iter.next()) console.log(iter.next()) console.log(iter.next())
- 內聚性非常高,盡最大可能進行了復用,減少冗余代碼
2. 什么是可迭代對象
迭代器對象和可迭代對象是一個不同的東西,雖然它們存在關聯(lián),而且面試的時候經(jīng)常面這些概念,廢話不多說,我們直接進入主題。
- 首先就是一個對象,且符合可迭代對象協(xié)議(iterable protocol)
- 可迭代對象協(xié)議
實現(xiàn)了[Symbol.iterator]為key的方法,且這個方法返回了一個迭代器對象
- 繞了一大圈終于把概念搞明白了,那可迭代對象有什么好處呢? 有什么應用場景呢?
for of 的時候,其本質就是調用的這個函數(shù),也就是[Symbol.iterator]為key的方法
2.1 原生可迭代對象(JS內置)
- String
- Array
- Set
- NodeList 類數(shù)組對象
- Arguments 類數(shù)組對象
- Map
2.1.1 部分for of 演示
let str = 'The Three Bears' const bears = ['ice', 'panda', 'grizzly'] for( let text of str) { console.log(text) //字符串每個遍歷打印 } for( let bear of bears) { console.log(bear) } //ice panda grizzly
2.1.2 查看內置的[Symbol.iterator]方法
- 上面給大家舉例了很多可迭代對象,那它們必定是符合可迭代對象協(xié)議的,思考以下代碼
const bears = ['ice', 'panda', 'grizzly'] //數(shù)組的Symbol.iterator方法 const iter = bears[Symbol.iterator]() console.log(iter.next()) console.log(iter.next()) console.log(iter.next()) console.log(iter.next()) const nickName = 'ice' //字符串的Symbol.iterator方法 const strIter = nickName[Symbol.iterator]() console.log(strIter.next()) console.log(strIter.next()) console.log(strIter.next()) console.log(strIter.next())
2.2 可迭代對象的實現(xiàn)
let info = { bears: ['ice', 'panda', 'grizzly'], [Symbol.iterator]: function() { let index = 0 let _iterator = { //這里一定要箭頭函數(shù),或者手動保存上層作用域的this next: () => { if (index < this.bears.length) { return { done: false, value: this.bears[index++] } } return { done: true, value: undefined } } } return _iterator } } let iter = info[Symbol.iterator]() console.log(iter.next()) console.log(iter.next()) console.log(iter.next()) console.log(iter.next()) //符合可迭代對象協(xié)議 就可以利用 for of 遍歷 for (let bear of info) { console.log(bear) } //ice panda grizzly
- 符合可迭代對象協(xié)議,是一個對象,有
[Symbol.iterator]
方法,并且這個方法返回了一個迭代器對象。 - 當我利用for of 遍歷,就會自動的調用這個方法。
2.3 可迭代對象的應用
- for of
- 展開語法
- 解構語法
- promise.all(iterable)
- promise.race(iterable)
- Array.from(iterable)
- ...
2.4 自定義類迭代實現(xiàn)
class myInfo { constructor(name, age, friends) { this.name = name this.age = age this.friends = friends } [Symbol.iterator]() { let index = 0 let _iterator = { next: () => { const friends = this.friends if (index < friends.length) { return {done: false, value: friends[index++]} } return {done: true, value: undefined} } } return _iterator } } const info = new myInfo('ice', 22, ['panda','grizzly']) for (let bear of info) { console.log(bear) } //panda //grizzly
- 此案例只是簡單的對
friends
進行了迭代,你也可以迭代你想要的一切東西... - 記住此案例,后續(xù)我們會對這個案例進行重構,優(yōu)雅的會讓你不能用言語來形容。
3. 生成器函數(shù)
生成器是ES6新增的一種可以對函數(shù)控制的方案,能靈活的控制函數(shù)的暫停執(zhí)行,繼續(xù)執(zhí)行等。
生成器函數(shù)和普通函數(shù)的不同
- 定義: 普通函數(shù)
function
定義,生成器函數(shù)function*
,要在后面加*
- 生成器函數(shù)可以通過
yield
來控制函數(shù)的執(zhí)行 - 生成器函數(shù)返回一個生成器(generator),生成器是一個特殊的迭代器
3.1 生成器函數(shù)基本實現(xiàn)
function* bar() { console.log('fn run') } bar()
- 我們會發(fā)現(xiàn),這個函數(shù)竟然沒有執(zhí)行。我們前面說過,它是一個生成器函數(shù),它的返回值是一個生成器,同時也是一個特殊的迭代器,所以跟普通函數(shù)相比,好像暫停了,那如何讓他執(zhí)行呢?接下來我們進一步探討。
3.2 生成器函數(shù)單次執(zhí)行
function* bar() { console.log('fn run') } const generator = bar() console.log(generator.next()) //fn run //{ value: undefined, done: true }
- 返回了一個生成器,我們調用next方法就可以讓函數(shù)執(zhí)行,并且next方法是有返回值的,我們上面講迭代器的時候有探討過,而value沒有返回值那就是undefined。那上面說的yield關鍵字在哪,到底是如何控制函數(shù)的呢?是如何用的呢?
3.3 生成器函數(shù)多次執(zhí)行
function* bar() { console.log('fn run start') yield 100 console.log('fn run...') yield 200 console.log('fn run end') return 300 } const generator = bar() //1. 執(zhí)行到第一個yield,暫停之后,并且把yield的返回值 傳入到value中 console.log(generator.next()) //2. 執(zhí)行到第一個yield,暫停之后,并且把yield的返回值 傳入到value中 console.log(generator.next()) //3. 執(zhí)行剩余代碼 console.log(generator.next()) //打印結果: //fn run start //{done:false, value: 100} //fn run... //{done:false, value: 200} //fn run end //{done:true, value: 300}
- 現(xiàn)在我們恍然大悟,每當調用next方法的時候,代碼就會開始執(zhí)行,執(zhí)行到
yield x
,后就會暫停,等待下一次調用next繼續(xù)往下執(zhí)行,周而復始,沒有了yield
關鍵字,進行最后一次next調用返回done:true
。
3.4 生成器函數(shù)的分段傳參
我有一個需求,既然生成器能控制函數(shù)分段執(zhí)行,我要你實現(xiàn)一個分段傳參。
思考以下代碼:
function* bar(nickName) { const str1 = yield nickName const str2 = yield str1 + nickName return str2 + str1 + nickName } const generator = bar('ice') console.log(generator.next()) console.log(generator.next('panda ')) console.log(generator.next('grizzly ')) console.log(generator.next()) // { value: 'ice', done: false } // { value: 'panda ice', done: false } // { value: 'grizzly panda ice', done: true } // { value: undefined, done: true }
- 如果沒有接觸過這樣的代碼會比較奇怪
- 當我調用next函數(shù)的時候,yield的左側是可以接受參數(shù)的,也并不是所有的next方法的實參都能傳遞到生成器函數(shù)內部
- yield左側接收的,是第二次調用next傳入的實參,那第一次傳入的就沒有yield關鍵字接收,所有只有當我調用bar函數(shù)的時候傳入。
- 最后一次next調用,傳入的參數(shù)我也調用不了,因為沒有yield關鍵字可以接收了。
- 很多開發(fā)者會疑惑,這樣寫有什么用呢? 可讀性還差,但是在處理異步數(shù)據(jù)的時候就非常有用了,后續(xù)會在promise中文章中介紹。
3.5 生成器代替迭代器
前面我們講到,生成器是一個特殊的迭代器,那生成器必定是可以代替迭代器對象的,思考以下代碼。
let bears = ['ice','panda','grizzly'] function* createArrIterator(bears) { for (let bear of bears) { yield bear } } const generator = createArrIterator(bears) console.log(generator.next()) console.log(generator.next()) console.log(generator.next()) console.log(generator.next())
其實這里還有一種語法糖的寫法yield*
- yield* 依次迭代這個可迭代對象,相當于遍歷拿出每一項 yield item(偽代碼)
思考以下代碼:
let bears = ['ice','panda','grizzly'] function* createArrIterator(bears) { yield* bears } const generator = createArrIterator(bears) console.log(generator.next()) console.log(generator.next()) console.log(generator.next()) console.log(generator.next())
- 依次迭代這個可迭代對象,返回每個item值
4. 可迭代對象的終極封裝
class myInfo { constructor(name, age, friends) { this.name = name this.age = age this.friends = friends } *[Symbol.iterator]() { yield* this.friends } } const info = new myInfo('ice', 22, ['panda','grizzly']) for (let bear of info) { console.log(bear) } //panda //grizzly
- 回顧以下可迭代對象協(xié)議
- 是一個對象并且有[Symbol.iterator]方法
- 這個方法返回一個迭代器對象 生成器函數(shù)返回一個生成器,是一個特殊的迭代器
5. 總結
5.1 迭代器對象
- 本質就是一個對象,要符合迭代器協(xié)議
- 有自己對應的next方法,next方法則返回一組數(shù)據(jù)
{done:boolean, value:any}
5.2 可迭代對象
- 本質就是對象,要符合可迭代對象協(xié)議
- 有
[Symbol.iterator]
方法,并且調用這個方法返回一個迭代器
5.3 生成器函數(shù)
- 可以控制函數(shù)的暫停執(zhí)行和繼續(xù)執(zhí)行
- 通過
function* bar() {}
這種形式定義 - 不會立馬執(zhí)行,而是返回一個生成器,生成器是一個特殊的迭代器對象
yield
關鍵字可以控制函數(shù)分段執(zhí)行- 調用返回生成器的next方法進行執(zhí)行
到此這篇關于JavaScript中的迭代器和可迭代對象與生成器的文章就介紹到這了,更多相關JavaScript迭代器內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
javascript offsetX與layerX區(qū)別
FF沒有offsetX屬性,有個layerX屬性,只要將事件源的位置設置成相對定位(position:relative)或絕對定位(position:absolute),兩者結果就相等,表示事件源相對于父元素的X坐標。2010-03-03JavaScript詳解使用Promise處理回調地獄的兩種方法
這篇文章主要介紹了JavaScript詳解使用Promise處理回調地獄的兩種方法,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-11-11elementUI?Table?表格編輯數(shù)據(jù)后停留當前位置的示例代碼
這篇文章主要介紹了elementUI?Table?表格編輯數(shù)據(jù)后停留當前位置,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-04-04