希沃ENOW大前端javascript
公司官網:CVTE(廣州視源股份)前端
團隊:CVTE旗下將來教育希沃軟件平臺中心enow團隊java
本文做者:程序員
遙想當初面試的時候,被問到經典問題ES6都有啥,年紀輕輕的我搜颳了滿腦子,除了水就是水。es6
那麼ES6都有啥呢,很快腦子裏就有let和const、模板字符串、解構賦值、展開運算符、箭頭函數等等。說了這些也許足夠讓面試官展開發揮了,可是優秀的掘金er怎麼可能止步於此?面試
爲了讓面試官眼前一亮,es6還有這些新特性:模塊化、Symbol、Set和Map數據結構、Proxy代理和Reflect反射、Generator等等,說完面試官估計眼睛開始有光了,那麼說了就要懂,懂了纔不怕被面試官問,雖然有點冷門,俗話說技多不壓身,今天就一塊兒來看看Generator吧!編程
generator即生成器,是ES6規範帶來的新內容,在generator可以讓咱們在函數執行時任意地方暫停,在後續遇到合適的時機須要使用這個函數時繼續執行。以往咱們遇到的函數都是一口氣執行到底,而generator的特色就是讓函數執行到中間「剎車」,在須要它的時候接着執行。下面從一個案例來看看generator的基本用法吧!markdown
常見的javascript函數:數據結構
function fun() {
console.log(1)
console.log(2)
console.log(3)
}
function run() {
console.log(4)
}
fun()
run()
// 結果:
1
2
3
4
複製代碼
使用generator函數:異步
function* funG() {
yield console.log(1)
yield console.log(2)
yield console.log(3)
}
function run() {
console.log(4)
}
const iter = funG()
iter.next()
run()
iter.next()
iter.next()
iter.next()
// 結果:
1
4
2
3
{value: undefined, done:true}
複製代碼
寫法上:
從打印結果上來看:
知道generator的基本用法了,但還不夠,爲了加深面試官的印象,咱們能夠從generator設計出發點聊聊協程!
既然要聊協程,首先得知道協程是什麼吧!
簡單來講協程就像單身程序員小王敲代碼,老大給了他一個項目A,小王收到立馬開碼;
小王項目A作到一半,老大說有個項目B時間趕,趕忙來幹項目B;
因而小王中止開發項目A,着手開幹項目B;
項目B開發一段時間後,小王回來接着幹項目A。
這就是協程,那麼項目B作完了?也許沒有。
看完了協程的案例,聰明的你應該想到了協程跟generator之間的關係!沒錯,generator就是協程在js上的實現。經過generator,咱們能夠在單線程的JavaScript裏使用協程!
generator自己做爲異步編程的解決方案,能夠用來解決異步任務。除此以外還能夠有更靈活的用法!那即是在函數執行過程當中傳入參數,以及獲取該段表達式輸出結果!
上文提到generator每次執行next()方法都會返回一個對象:{ value, done },經過value,咱們能夠獲取該段表達式的返回結果,另外,next還能夠接受參數,利用這一特性,咱們能夠隨時傳入參數!
function* run(name) {
let who = yield name + ' Allen';
return who
}
let flashMan = run('Barry')
flashMan.next() // { value: 'Barry Allen', done: false }
// 傳入參數
flashMan.next('Arrow') // { value: 'Allow', done: true }
複製代碼
在第一個next()調用時,不傳入參數,默認將name的參數值'Barry'與' Allen'組合字符串,這個值被yield返回。
在第二個next()調用時,傳入參數'Arrow',這個值被變量who接收,所以返回value屬性的值爲'Arrow',也就是who的值。
不只如此,generator還能夠在函數運行時,捕獲函數體外拋出的錯誤。
function* gen(x){
try {
var y = yield x + 2;
} catch (e){
console.log(e);
}
return y;
}
var g = gen(1);
g.next();
g.throw('error');
// error
複製代碼
generator的原理是轉化爲switch-case來實現的,先從一個簡單的案例入手。
function* funG() {
yield 1
yield 2
yield 3
}
const iter = funG()
iter.next() // {value: 1, done: false}
iter.next() // {value: 2, done: false}
iter.next() // {value: 3, done: false}
iter.next() // {value: undefined, done: true}
複製代碼
generator的實現須要一個函數,這個函數能夠屢次調用,每次返回一個結果,還能夠傳入參數,那麼switch-case就是很好的選擇。
function funGen(count) {
switch(count) {
case 1:
return {value: 1, done: false};
case 2:
return {value: 2, done: false};
case 3:
return {value: 3, done: false};
case 'done':
return {value: undefined, done: true}
}
}
funGen(1) // {value: 1, done: false};
funGen(2) // {value: 2, done: false};
funGen(3) // {value: 3, done: false};
funGen('done') // {value: undefined, done: true}
複製代碼
從結果上來看是咱們想要的結果,可是距離generator還有很大的差距,接下來建立一個函數,這個函數返回一個對象,經過調用這個對象來幫咱們執行14~17行的語句。
function funGen(count) {
switch(count) {
case 1:
return {value: 1, done: false};
case 2:
return {value: 2, done: false};
case 3:
return {value: 3, done: false};
case 'done':
return {value: undefined, done: true}
}
}
const gen = function() {
let count = 0
return {
next: function() {
++count
count = count > 3 ? 'done' : count
return funGen(count)
}
}
}
const test = gen()
test.next() // {value: 1, done: false}
test.next() // {value: 2, done: false}
test.next() // {value: 3, done: false}
test.next() // {value: undefined, done: true}
複製代碼
到目前爲止,都須要咱們手動處理函數上下文裏的count的值的變化來決定返回結果,因此咱們須要一個對象來保存函數上下文也就是count的值。
function example(context) {
while(1) {
context.pre = context.next
switch(context.pre) {
case 1:
context.next = 2
return 1;
case 2:
context.next = 3
return 2;
case 3:
context.next = 'done'
return 3;
case 'done':
return context.end()
}
}
}
const gen = function() {
return {
next: function() {
value = context.done ? undefined : funGen(context)
done = context.done
return {
value,
done
}
}
}
}
const context = {
pre: 1,
next: 1,
done: false,
end: function end() {
this.done = true
}
}
const test = gen()
test.next() // {value: 1, done: false}
test.next() // {value: 2, done: false}
test.next() // {value: 3, done: false}
test.next() // {value: undefined, done: false}
複製代碼
經過一個新對象context來記錄函數上下文的初始狀態pre,以及下一個狀態next,done記錄是否到最終點。而end即是修改done爲結束狀態true。
funGen每次運行next()結束後都會將運行狀態保存到context中,方便下次運行時獲取。
gernerator做爲es6的新特性,在後來被更方便好用的async await代替,可是generator獨特的特性可讓咱們在函數執行的過程當中傳遞參數獲取結果,使得函數調用變得更加靈活。做爲一個開發者,咱們有必要了解一下gernerator的基本使用以及簡單的實現的原理,方便在特殊的場景中解決問題。