詳解JS前端使用迭代器和生成器原理及示例
正文
生成器和迭代器這兩個(gè)東西平時(shí)作為一個(gè)切圖仔,一直都沒(méi)有使用到。感覺(jué)是只有在面試之前才會(huì)的東西。面試過(guò)不了幾天,再次看這兩個(gè)詞一陣恍惚。
記憶力退化成這樣了么?最大的原因一定是用得少了。然后呢?就是沒(méi)有真正的理解它們。我對(duì)于它們的認(rèn)知常常有下面這些:
1. 我常常把迭代器和生成器理解成完全不同的東西。
2. 我常常把for、forEach、map、reduce
和for of
混為一談
3. 我常常把數(shù)組、類數(shù)組認(rèn)為是可迭代對(duì)象
想來(lái)要真正的記住它,增加自己的武器庫(kù),必須要弄明白這些東西才行。
我們首先是要搞明白什么for of是干什么用的。
業(yè)務(wù)代碼確實(shí)使用不上,但是如果不理解的話,等真到了可以使用的場(chǎng)景的時(shí)候,又是否真的能夠運(yùn)用起來(lái),甚至記起來(lái)呢?
for of 是干什么用的
所有人都知道一些概念for、forEach、map、reduce
這些是可以遍歷數(shù)組的,for of
是用于遍歷迭代對(duì)象的。如下:
const arr = [1, 2, 3] arr.forEach((item, index) => { console.log(item) // 1, 2, 3 console.log(index) // 0, 1, 2 })
而巧合的是for of
也可以遍歷數(shù)組
for (let key of arr) { console.log(key) // 1 2 3 }
將arr改變?yōu)?code>const obj = { a: 1, b: 2, c: 3 }的時(shí)候,兩者都沒(méi)有遍歷出結(jié)果。
前者是沒(méi)有反應(yīng),后者會(huì)直接報(bào)錯(cuò):TypeError: obj is not iterable
。翻譯一下,類型錯(cuò)誤:obj 不是一個(gè)可迭代對(duì)象。
那么什么是可迭代對(duì)象呢?
可迭代對(duì)象是什么?
我們先來(lái)看看下面這個(gè)例子:
const itemLi1 = document.getElementByTagName('li') const itemLi2 = document.querySelectorAll('li') for(let key of itemLi1) { console.log(item) } for(let key of itemLi2) { console.log(item) }
也就是說(shuō)HTMLCollection
和NodeList
是可以迭代對(duì)象。其他的可迭代對(duì)象有Array、map、set、string
等等。如果說(shuō)類數(shù)組的話,是不是迭代對(duì)象呢?
const arrLike = { 0: 1, 1: 2, 2: 3, lenght: 3 } for (let i = 0; i < arrLike.length; i++) { console.log(arrLike[i]) // 1, 2, 3 } for (let of arrLike) { console.log(key) // uncachh TypeError: obj is not iterable }
for循環(huán)打印出了對(duì)應(yīng)的結(jié)果,而for of 報(bào)錯(cuò)了。類數(shù)組不是可迭代的的對(duì)象。這又是為什么呢?我們將類數(shù)組和HTMLCollection類型打印出來(lái)比較一下。
而類數(shù)組如下:
它們有一個(gè)明顯的不同,可迭代對(duì)象的原型鏈上面是包括Symbol.iterator
的。而這個(gè)就是讓數(shù)組變成可迭代的根本原因。
也就是說(shuō),當(dāng)目的對(duì)象的原型鏈上面包括Symbol.iterator
的時(shí)候,它才是可迭代對(duì)象。
對(duì)象是無(wú)序的,無(wú)序的東西自然不可以迭代
這里使用到了Symbol類型,它在MDN上面的解釋就是用于生成全局唯一的變量。而可迭代對(duì)象就是它的使用場(chǎng)景。受它的啟發(fā),我們?cè)跇I(yè)務(wù)當(dāng)中,如果需要前端來(lái)生成一個(gè)唯一ID的時(shí)候,再次之前,通常都是創(chuàng)建一個(gè)UUID的函數(shù)來(lái)生成一個(gè)唯一ID。Symbol不用這么麻煩,直接使用就可以了。
由此可知,Array.prototype[Symbol.iterater]
這個(gè)函數(shù)封裝了一些東西,使得for of
可以將對(duì)象的元素給打印出來(lái)。
換一句話來(lái)說(shuō),就是Array.prototype[Symbol.iterater] = function() {}
的執(zhí)行生成一個(gè)迭代器對(duì)象。
也就是說(shuō),當(dāng)Object.prototype
也有[Symbol.iterater]
的方法的時(shí)候,for of
也能夠遍歷它呢?我們來(lái)試試看吧。
Object.ptotoype[Symbol.iterator] = function value() {}
這不就是生成器的作用么?
生成器和迭代器的關(guān)系。
ES6給我提高了一個(gè)生成器的函數(shù)。既然叫做生成器,它生成的東西就是迭代器。
表現(xiàn)形式如下:
function * generation(iterableObject) { for(let i = 0; i < iterableObject; i++) { yield iterableObject[i] } }
由*
符號(hào)和yield
關(guān)鍵字組成。
當(dāng)const iterator = generation([1, 2, 3])
, 其執(zhí)行流程如下:
iterator.next() ==> { value: 1, done: false }
iterator.next() ==> { value: 2, done: false }
iterator.next() ==> { value: 3, done: false }
iterator.next() ==> { value: undefined, done: true }
到了第四次,value為undefined
的時(shí)候,done為true(也就是說(shuō),當(dāng)done為true的時(shí)候,value一定為undefined)。所以說(shuō),yield
的作用有兩個(gè):
- 生成一個(gè)值,將該值封裝成一個(gè)對(duì)象,而這個(gè)對(duì)象是
{ value: .., done: flase/true }
這樣的形式。 - 停下來(lái)
可以明顯的看出來(lái),生成器有一個(gè)作用,通過(guò)next這個(gè)接口,可以看到迭代的過(guò)程。
既然說(shuō)生成器生成了一個(gè)迭代器,那么是不是說(shuō)生成器執(zhí)行后的結(jié)果就是一個(gè)迭代器呢?既然是迭代器,自然就可以被for of
給遍歷。
for (const key of generation([1, 2, 3]) { console.log(key) // 1, 2, 3 }
果然可以。
經(jīng)典面試題: 自己實(shí)現(xiàn)一個(gè)next
這樣的接口呢?
上面已經(jīng)有了實(shí)現(xiàn)的思路。通過(guò)一個(gè)標(biāo)識(shí)符和一個(gè)判斷就能夠使用ES5來(lái)使用,如下代碼片段。
function generation(iterableObj) { let nextIndex = 0 function next() {} return { next: () => { return nextIndex < iterableObj.length ? { value: iterableObj[nextIndex++], done: false } : { value: undefined, done: true } } } }
當(dāng)nextIndex下于數(shù)組長(zhǎng)度的時(shí)候,沒(méi)有迭代完畢。
注意:nextIndex++
是先跑nextIndex
,再自增。
何為接口,后臺(tái)給你一個(gè)url地址,這個(gè)是網(wǎng)絡(luò)接口。next是設(shè)計(jì)師給你封裝的一個(gè)方法,你通過(guò)這個(gè)方法來(lái)達(dá)到上吧yield
的兩個(gè)作用,所以next()也是一個(gè)接口,前端接口。簡(jiǎn)單來(lái)說(shuō),一個(gè)封裝好的方法就是一個(gè)接口。
讓非迭代對(duì)象也可以使用for of 進(jìn)行遍歷
正如第一節(jié)所說(shuō),Symbol.iterator
的方法是迭代器的關(guān)鍵。那么我們也可以給Object
掛載上該方法。既然該方法可以讓對(duì)象變成迭代器,就可以直接使用上面ES5實(shí)現(xiàn)next
方法的代碼片段。
const obj = { a: 1, b: 2, c: 3 } Object.prototype[Symbol.iterator] = function value() { const keys = Object.keys(Object(this)) let nextIndex = 0 function next() { return nextIndex < keys.length ? { value: [keys[nextIndex], obj[keys[nextIndex ++]]], done: false } : { value: undefined, done: true } } return { next } } for (const [key, value] in obj) { console.log(key) }
for循環(huán)和for in的關(guān)系
for循環(huán)和for in 看著很像,其實(shí)只是共用了for
這個(gè)關(guān)鍵字,它們都是JS引擎底層實(shí)現(xiàn)的東西。和forEach、map
這些是基于for循環(huán)的API不同,它們是在實(shí)現(xiàn)在for循環(huán)之上的。
總結(jié)
- 生成器generator執(zhí)行的結(jié)果就是一個(gè)迭代器
- 生成器可以是也是由ES5實(shí)現(xiàn)的,不是基于底層API
- 是否是迭代器的關(guān)鍵是
Symbol.iterator
方法
以上就是詳解JS前端使用迭代器和生成器原理及示例的詳細(xì)內(nèi)容,更多關(guān)于JS前端迭代器生成器的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
微信小程序 image組件binderror使用例子與js中的onerror區(qū)別
這篇文章主要介紹了微信小程序 image組件binderror使用例子與js中的onerror區(qū)別的相關(guān)資料,需要的朋友可以參考下2017-02-02JavaScript原型鏈中函數(shù)和對(duì)象的理解
這篇文章主要為大家介紹了JavaScript原型鏈中函數(shù)和對(duì)象的理解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-06-06JS觸摸屏網(wǎng)頁(yè)版仿app彈窗型滾動(dòng)列表選擇器/日期選擇器
這篇文章主要介紹了觸摸屏網(wǎng)頁(yè)版仿app彈窗型滾動(dòng)列表選擇器/日期選擇器的相關(guān)資料,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2016-10-10