JS Generator函數(shù)yield表達(dá)式示例詳解
什么是 Generator 函數(shù)
在Javascript中,一個函數(shù)一旦開始執(zhí)行,就會運(yùn)行到最后或遇到return時結(jié)束,運(yùn)行期間不會有其它代碼能夠打斷它,也不能從外部再傳入值到函數(shù)體內(nèi)
而Generator函數(shù)(生成器)的出現(xiàn)使得打破函數(shù)的完整運(yùn)行成為了可能,其語法行為與傳統(tǒng)函數(shù)完全不同
Generator函數(shù)是ES6提供的一種異步編程解決方案,形式上也是一個普通函數(shù),但有幾個顯著的特征:
- function關(guān)鍵字與函數(shù)名之間有一個星號 "*" (推薦緊挨著function關(guān)鍵字)
- 函數(shù)體內(nèi)使用 yield 表達(dá)式,定義不同的內(nèi)部狀態(tài) (可以有多個yield)
- 直接調(diào)用 Generator函數(shù)并不會執(zhí)行,也不會返回運(yùn)行結(jié)果,而是返回一個遍歷器對象(Iterator Object)
- 依次調(diào)用遍歷器對象的next方法,遍歷 Generator函數(shù)內(nèi)部的每一個狀態(tài)
// 傳統(tǒng)函數(shù) function foo() { return 'hello world' } foo() // 'hello world',一旦調(diào)用立即執(zhí)行 // Generator函數(shù) function* generator() { yield 'status one' // yield 表達(dá)式是暫停執(zhí)行的標(biāo)記 return 'hello world' } let iterator = generator() // 調(diào)用 Generator函數(shù),函數(shù)并沒有執(zhí)行,返回的是一個Iterator對象 iterator.next() // {value: "status one", done: false},value 表示返回值,done 表示遍歷還沒有結(jié)束 iterator.next() // {value: "hello world", done: true},value 表示返回值,done 表示遍歷結(jié)束
上面的代碼中可以看到傳統(tǒng)函數(shù)和Generator函數(shù)的運(yùn)行是完全不同的,傳統(tǒng)函數(shù)調(diào)用后立即執(zhí)行并輸出了返回值;Generator函數(shù)則沒有執(zhí)行而是返回一個Iterator對象,并通過調(diào)用Iterator對象的next方法來遍歷,函數(shù)體內(nèi)的執(zhí)行看起來更像是“被人踢一腳才動一下”的感覺
function* gen() { yield 'hello' yield 'world' return 'ending' } let it = gen() it.next() // {value: "hello", done: false} it.next() // {value: "world", done: false} it.next() // {value: "ending", done: true} it.next() // {value: undefined, done: true}
上面代碼中定義了一個 Generator函數(shù),其中包含兩個 yield 表達(dá)式和一個 return 語句(即產(chǎn)生了三個狀態(tài))
每次調(diào)用Iterator對象的next方法時,內(nèi)部的指針就會從函數(shù)的頭部或上一次停下來的地方開始執(zhí)行,直到遇到下一個 yield 表達(dá)式或return語句暫停。換句話說,Generator 函數(shù)是分段執(zhí)行的,yield表達(dá)式是暫停執(zhí)行的標(biāo)記,而 next方法可以恢復(fù)執(zhí)行
第四次調(diào)用next方法時,由于函數(shù)已經(jīng)遍歷運(yùn)行完畢,不再有其它狀態(tài),因此返回 {value: undefined, done: true}。如果繼續(xù)調(diào)用next方法,返回的也都是這個值
yield 表達(dá)式
yield 表達(dá)式只能用在 Generator 函數(shù)里面,用在其它地方都會報錯
function (){ yield 1; })() // SyntaxError: Unexpected number // 在一個普通函數(shù)中使用yield表達(dá)式,結(jié)果產(chǎn)生一個句法錯誤
yield 表達(dá)式如果用在另一個表達(dá)式中,必須放在圓括號里面
function* demo() { console.log('Hello' + yield); // SyntaxError console.log('Hello' + yield 123); // SyntaxError console.log('Hello' + (yield)); // OK console.log('Hello' + (yield 123)); // OK }
yield 表達(dá)式用作參數(shù)或放在賦值表達(dá)式的右邊,可以不加括號
function* demo() { foo(yield 'a', yield 'b'); // OK let input = yield; // OK }
yield 表達(dá)式和return語句的區(qū)別
相似:都能返回緊跟在語句后面的那個表達(dá)式的值
區(qū)別:
- 每次遇到 yield,函數(shù)就暫停執(zhí)行,下一次再從該位置繼續(xù)向后執(zhí)行;而 return 語句不具備記憶位置的功能
- 一個函數(shù)只能執(zhí)行一次 return 語句,而在 Generator 函數(shù)中可以有任意多個 yield
yield* 表達(dá)式
如果在 Generator 函數(shù)里面調(diào)用另一個 Generator 函數(shù),默認(rèn)情況下是沒有效果的
function* foo() { yield 'aaa' yield 'bbb' } function* bar() { foo() yield 'ccc' yield 'ddd' } let iterator = bar() for(let value of iterator) { console.log(value) } // ccc // ddd
上例中,使用 for...of 來遍歷函數(shù)bar的生成的遍歷器對象時,只返回了bar自身的兩個狀態(tài)值。此時,如果想要正確的在bar 里調(diào)用foo,就需要用到 yield* 表達(dá)式
yield* 表達(dá)式用來在一個 Generator 函數(shù)里面 執(zhí)行 另一個 Generator 函數(shù)
function* foo() { yield 'aaa' yield 'bbb' } function* bar() { yield* foo() // 在bar函數(shù)中 **執(zhí)行** foo函數(shù) yield 'ccc' yield 'ddd' } let iterator = bar() for(let value of iterator) { console.log(value) } // aaa // bbb // ccc // ddd
next() 方法的參數(shù)
yield表達(dá)式本身沒有返回值,或者說總是返回undefined。next方法可以帶一個參數(shù),該參數(shù)就會被當(dāng)作上一個yield表達(dá)式的返回值
function* gen(x) { let y = 2 * (yield (x + 1)) // 注意:yield 表達(dá)式如果用在另一個表達(dá)式中,必須放在圓括號里面 let z = yield (y / 3) return x + y + z } let it = gen(5) /*** 正確的結(jié)果在這里 ***/ console.log(it.next()) // 首次調(diào)用next,函數(shù)只會執(zhí)行到 “yield(5+1)” 暫停,并返回 {value: 6, done: false} console.log(it.next()) // 第二次調(diào)用next,沒有傳遞參數(shù),所以 y的值是undefined,那么 y/3 當(dāng)然是一個NaN,所以應(yīng)該返回 {value: NaN, done: false} console.log(it.next()) // 同樣的道理,z也是undefined,6 + undefined + undefined = NaN,返回 {value: NaN, done: true}
如果向next方法提供參數(shù),返回結(jié)果就完全不一樣了
{ function* gen(x) { let y = 2 * (yield (x + 1)) // 注意:yield 表達(dá)式如果用在另一個表達(dá)式中,必須放在圓括號里面 let z = yield (y / 3) return x + y + z } let it = gen(5) console.log(it.next()) // 正常的運(yùn)算應(yīng)該是先執(zhí)行圓括號內(nèi)的計算,再去乘以2,由于圓括號內(nèi)被 yield 返回 5 + 1 的結(jié)果并暫停,所以返回{value: 6, done: false} console.log(it.next(9)) // 上次是在圓括號內(nèi)部暫停的,所以第二次調(diào)用 next方法應(yīng)該從圓括號里面開始,就變成了 let y = 2 * (9),y被賦值為18,所以第二次返回的應(yīng)該是 18/3的結(jié)果 {value: 6, done: false} console.log(it.next(2)) // 參數(shù)2被賦值給了 z,最終 x + y + z = 5 + 18 + 2 = 25,返回 {value: 25, done: true} }
與 Iterator 接口的關(guān)系
ES6 規(guī)定,默認(rèn)的 Iterator 接口部署在數(shù)據(jù)結(jié)構(gòu)的Symbol.iterator屬性,或者說,一個數(shù)據(jù)結(jié)構(gòu)只要具有Symbol.iterator屬性,就可以認(rèn)為是“可遍歷的”(iterable)。
Symbol.iterator屬性本身是一個函數(shù),就是當(dāng)前數(shù)據(jù)結(jié)構(gòu)默認(rèn)的遍歷器生成函數(shù)。執(zhí)行這個函數(shù),就會返回一個遍歷器。
由于執(zhí)行 Generator 函數(shù)實際返回的是一個遍歷器,因此可以把 Generator 賦值給對象的Symbol.iterator屬性,從而使得該對象具有 Iterator 接口。
{ let obj = {} function* gen() { yield 4 yield 5 yield 6 } obj[Symbol.iterator] = gen for(let value of obj) { console.log(value) } // 4 // 5 // 6 console.log([...obj]) // [4, 5, 6] }
傳統(tǒng)對象沒有原生部署 Iterator接口,不能使用 for...of 和 擴(kuò)展運(yùn)算符,現(xiàn)在通過給對象添加Symbol.iterator
屬性和對應(yīng)的遍歷器生成函數(shù),就可以使用了
for...of 循環(huán)
由于 Generator 函數(shù)運(yùn)行時生成的是一個 Iterator 對象,因此,可以直接使用 for...of 循環(huán)遍歷,且此時無需再調(diào)用 next() 方法
這里需要注意,一旦 next() 方法的返回對象的 done 屬性為 true,for...of 循環(huán)就會終止,且不包含該返回對象
{ function* gen() { yield 1 yield 2 yield 3 yield 4 return 5 } for(let item of gen()) { console.log(item) } // 1 2 3 4 }
Generator.prototype.return()
Generator 函數(shù)返回的遍歷器對象,還有一個 return 方法,可以返回給定的值(若沒有提供參數(shù),則返回值的value屬性為 undefined),并且 終結(jié) 遍歷 Generator 函數(shù)
{ function* gen() { yield 1 yield 2 yield 3 } let it = gen() it.next() // {value: 1, done: false} it.return('ending') // {value: "ending", done: true} it.next() // {value: undefined, done: true} }
Generator 函數(shù)應(yīng)用舉例
應(yīng)用一:假定某公司的年會上有一個抽獎活動,總共6個人可以抽6次,每抽一次,抽獎機(jī)會就會遞減
按照常規(guī)做法就需要聲明一個全局的變量來保存剩余的可抽獎次數(shù),而全局變量會造成全局污染,指不定什么時候就被重新賦值了,所以往往并不被大家推薦
{ let count = 6 // 聲明一個全局變量 // 具體抽獎邏輯的方法 function draw() { // 執(zhí)行一段抽獎邏輯 // ... // 執(zhí)行完畢 console.log(`剩余${count}次`) } // 執(zhí)行抽獎的方法 function startDrawing(){ if(count > 0) { count-- draw(count) } } let btn = document.createElement('button') btn.id = 'start' btn.textContent = '開始抽獎' document.body.appendChild(btn) document.getElementById('start').addEventListener('click', function(){ startDrawing() }, false) }
事實上,抽獎通常是每個人自己來抽,每抽一次就調(diào)用一次抽獎方法,而不是點(diǎn)一次就一次性就全部運(yùn)行完,是可暫停的,這個不就是 Generator 函數(shù)的意義所在嗎?
// 具體抽獎邏輯的方法 function draw(count) { // 執(zhí)行一段抽獎邏輯 // ... console.log(`剩余${count}次`) } // 執(zhí)行抽獎的方法 function* remain(count) { while(count > 0) { count-- yield draw(count) } } let startDrawing = remain(6) let btn = document.createElement('button') btn.id = 'start' btn.textContent = '開始抽獎' document.body.appendChild(btn) document.getElementById('start').addEventListener('click', function(){ startDrawing.next() }, false)
應(yīng)用二:由于HTTP是一種無狀態(tài)協(xié)議,執(zhí)行一次請求后服務(wù)器無法記住是從哪個客戶端發(fā)起的請求,因此當(dāng)需要實時把服務(wù)器數(shù)據(jù)更新到客戶端時通常采用的方法是長輪詢和Websocket。這里也可以用 Generator 函數(shù)來實現(xiàn)長輪詢
{ // 請求的方法 function* ajax() { yield new Promise((resolve, reject) => { // 此處用一個定時器來模擬請求數(shù)據(jù)的耗時,并約定當(dāng)返回的json中code為0表示有新數(shù)據(jù)更新 setTimeout(() => { resolve({code: 0}) }, 200) }) } // 長輪詢的方法 function update() { let promise = ajax().next().value // 返回的對象的value屬性是一個 Promise 實例對象 promise.then(res => { if(res.code != 0) { setTimeout(() => { console.log('2秒后繼續(xù)查詢.....') update() }, 2000) } else{ console.log(res) } }) } update() }
以上就是JS Generator函數(shù)yield表達(dá)式示例詳解的詳細(xì)內(nèi)容,更多關(guān)于JS Generator函數(shù)yield表達(dá)式的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
js檢測IE8及以下瀏覽器版本并做出提示的函數(shù)代碼
這篇文章主要介紹了js檢測IE8及以下瀏覽器版本并做出提示的函數(shù)代碼,需要的朋友可以參考下2023-02-02僅9張思維導(dǎo)圖幫你輕松學(xué)習(xí)Javascript 就這么簡單
僅9張思維導(dǎo)圖幫你輕松學(xué)習(xí)Javascript,從javascript變量、javascript運(yùn)算符、javascript函數(shù)基礎(chǔ)等多方面了解Javascript,就這么簡單2016-06-06基于JavaScript實現(xiàn)定時跳轉(zhuǎn)到指定頁面
本篇文章給大家介紹基于javascript實現(xiàn)定時跳轉(zhuǎn)到指定頁面的相關(guān)知識,涉及到j(luò)s跳轉(zhuǎn)到指定頁面的相關(guān)內(nèi)容,對js跳轉(zhuǎn)到指定頁面相關(guān)知識感興趣的朋友一起學(xué)習(xí)吧2016-01-01Layui組件Table綁定行點(diǎn)擊事件和獲取行數(shù)據(jù)的方法
今天小編就為大家分享一篇Layui組件Table綁定行點(diǎn)擊事件和獲取行數(shù)據(jù)的方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-08-08JavaScript XML和string相互轉(zhuǎn)化實現(xiàn)代碼
兩個小function實現(xiàn)XML和string相互轉(zhuǎn)化,需要的朋友可以參考下。2011-07-07