最近的業餘時間在看 js 相關的書, 也在極客時間上買了前端相關的專欄, 對於一個非 jser 的人來講, 時時會有一種感受: js 社區是真的激進和浮燥, 這幫規則的制定者彷佛曆來也不知道剋制爲什麼物. 有不少時候固有的東西是能夠處理好的, 可是恰恰喜歡人爲製造一些概念和語法糖, 人爲的建起一座又一座的高山, 彷佛你沒跨過就是個 "菜雞"前端
請原諒個人惡毒, 看《js 語言精粹》的時候, 這種感受很是的強烈. 做者是業內的大牛, 還制定了 json, 可是每一章還在最開頭引一句莎翁的話, "彷佛可有可無又暗藏哲理". 書中不少內容要表達的意思總有一種: 明明能夠將話說成 10 分讓一個普通人都能看得明白的, 卻偏不, 只說 6 分, 剩下的本身去悟, 有不少規則性的東西並不是是靠悟, 而是靠幾句話說清楚其本質就豁然開朗的.面試
換成之前, 會以爲這人是一座好大的高山要進行膜拜, 這幾年雖然本身技術依然不那麼好, 可是仍是喜歡去思考一些內在的東西, 更在努力一點一點把內心的權威崇拜給去掉, 再看到這些的時候, "..." 這幾個標點符號很容易印在腦子裏. 感受這不僅是一兩我的這樣, 極有多是整個 js 圈子都是這樣chrome
說回標題上來, 除了看書, 看專欄, 找資料, 好久都仍是沒有把 generator 和 async/await 給理解透, 因而本身試着整個梳理了一下運行的流程json
我先試着在 yield 後面不跟任何的東西, 能夠直接複製到控制檯輸出異步
function *f0(param) { console.log('n: ' + param); yield; console.log('i'); let l = yield; console.log('l: ' + l); } let v0 = f0('p'); console.log(v0.next(1)); // 輸出 n: p 和 {value: undefined, done: false} console.log('----'); console.log(v0.next(2)); // 輸出 i 和 {value: undefined, done: false} console.log('----'); console.log(v0.next(3)); // 輸出 l: 3 和 {value: undefined, done: true} console.log('----'); console.log(v0.next(4)); // 輸出 {value: undefined, done: true} console.log('----'); console.log(v0.next(5)); // 輸出 {value: undefined, done: true}
在上面的基礎上給方法 return 值async
function *f1() { console.log('n'); yield; console.log('i'); let l = yield; console.log('l: ' + l); return '?'; } let v1 = f1(); console.log(v1.next(1)); // 輸出 n 和 {value: undefined, done: false} console.log('----'); console.log(v1.next(11)); // 輸出 i 和 {value: undefined, done: false} console.log('----'); console.log(v1.next(111)); // 輸出 l: 111 和 {value: '?', done: true} console.log('----'); console.log(v1.next(1111)); // 輸出 {value: undefined, done: true} console.log('----'); console.log(v1.next(11111)); // 輸出 {value: undefined, done: true}
而後我試着在 yield 的後面加上內容firefox
function *f2(param) { console.log('0: ' + param); let f = yield 1; console.log('1: ' + f); let s = yield f + 2; console.log('2: ' + s); let t = yield (s + 3); console.log('3: ' + t); let fo = (yield s) + 4; console.log('4: ' + fo); } let v2 = f2('p'); console.log(v2.next('N')); // 輸出 0: p 和 {value: 1, done: false} console.log('----'); console.log(v2.next('I')); // 輸出 1: I 和 {value: "I2", done: false} console.log('----'); console.log(v2.next('L')); // 輸出 2: L 和 {value: "L3", done: false} console.log('----'); console.log(v2.next('S')); // 輸出 3: S 和 {value: "L", done: false} console.log('----'); console.log(v2.next('H')); // 輸出 4: H4 和 {value: undefined, done: true} console.log('----'); console.log(v2.next('I')); // 輸出 {value: undefined, done: true} console.log('----'); console.log(v2.next('T')); // 輸出 {value: undefined, done: true}
最後, 在上面的基礎上給方法 return 值code
function *f3() { console.log('0'); let y1 = yield 1; console.log('1: ' + y1); let y2 = yield y1 + 2; console.log('2: ' + y2); let y3 = yield (y2 + 3); console.log('3: ' + y3); let y4 = (yield y3) + 4; console.log('4: ' + y4); return '??'; } let v3 = f3(); console.log(v3.next('N')); // 輸出 0 和 {value: 1, done: false} console.log('----'); console.log(v3.next('I')); // 輸出 1: I 和 {value: "I2", done: false} console.log('----'); console.log(v3.next('L')); // 輸出 2: L 和 {value: "L3", done: false} console.log('----'); console.log(v3.next('S')); // 輸出 3: S 和 {value: "S", done: false} console.log('----'); console.log(v3.next('H')); // 輸出 4: H4 和 {value: "??", done: true} console.log('----'); console.log(v3.next('I')); // 輸出 {value: undefined, done: true} console.log('----'); console.log(v3.next('T')); // 輸出 {value: undefined, done: true}
大體上就清楚 yield 的運行邏輯了, 以上面的 f3 爲例, 對照上面的輸出來看, 它實際上是將一個方法分紅了這樣幾段來執行隊列
// 下面 五行一塊兒的豎線(|) 用一個大括號表示出來會更直觀一點 function *f3() { // 調用 next('N') 時運行的代碼 console.log('0'); let y1 = yield 1; return 1; // | 封裝成 {value: 1, done: false} 返回 // | // | 這兩行等同於 let y1 = yield 1; // 調用 next('I') 時運行的代碼 // | let y1 = 'I'; // | console.log('1: ' + y1); return y1 + 2; // | 封裝成 {value: "I2", done: false} 返回 // | // | 這兩行等同於 let y2 = yield y1 + 2; // 調用 next('L') 時運行的代碼 // | let y2 = 'L'; // | console.log('2: ' + y2); return (y2 + 3); // | 封裝成 {value: "L3", done: false} 返回 // | // | 這兩行等同於 let y3 = yield (y2 + 3); // 調用 next('S') 時運行的代碼 // | let y3 = 'S'; // | console.log('3: ' + y3); return (y3); // | 封裝成 {value: "S", done: false} 返回 // | // | 這兩行等同於 let y4 = (yield y3) + 4; // 調用 next('H') 時運行的代碼 // | let y4 = ('H') + 4; // | console.log('4: ' + y4); return '??'; // 封裝成 {value: "??", done: true} 返回 // done 爲 true 以後, 再 next() 就都返回那一個了 }
再回頭想想就知道了, 第一次運行 next('N') 的時候, 傳進去的 N 是會被忽略的, 由於第一次 next() 傳的值沒有 yield 前面來接收. 再去看書也好, 看查到的文章也好, 第一次 next() 都是沒有傳過參數generator
感受 yield 就是爲了迭代而生的, 迭代徹底能夠就用 for 啊, 可是卻繞成這樣, 也不知道這是爲哪般! 就這還能新弄一個 for of 玩出花來, 由於每執行 next() 纔會執行那一段段, 還美其名曰 "我們終於能夠異步了"
再是 es7 開始有的這倆關鍵字, 看了一個大廠的面試題, 本身爲了加深對這兩個關鍵字的理解改了一下成下面這樣
async function async1() { console.log('A'); console.log(await async2()); return 'B'; } async function async2() { console.log('C'); return 'D'; } console.log('E'); setTimeout(function() { console.log('F'); }, 0); async1().then(function(r) { console.log(r); }); new Promise(function(resolve, reject) { console.log('G'); resolve(); }).then(function() { console.log('H'); }); console.log('I');
在 chrome 73.0.3683.75 底下的輸出是:
// 這個 undefined 的意思應該主要是用來分隔宏任務的, 也就是前面的主線和任務隊列是在一塊兒的 E A C G I D H B undefined F
在 firefox 60.5.1 底下的輸出
// 這個 undefined 的意思應該只是用來分隔主線的, 任務隊列和宏任務在一塊兒了 E A C G I undefined H D B F
在 opera 58.0.3135.107 底下的輸出是:
// 這個 undefined 應該跟 chrome 裏面是同樣的 E A C G I H D B undefined F
明顯 D H B 是比較合理的. 在 firefox 和 opera 的實現中明顯是有問題的, 想像獲得, 低版本一點的 chrome 也多是後面的結果
還有像 var let const 這種一個簡單的賦值都能玩出這麼多花樣(固然, 這能夠說是歷史遺留問題致使的)
老實說, 我以爲這更可能是爲了: "別的語言有, 我們這麼前衛的語言固然應該要有!"
...
就這麼樣一門語言, 竟然能夠流行成如今這樣, 只能說這個世界是真奇妙