編譯器的求值策略一般分爲傳值調用以及傳名調用,Thunk
函數是應用於編譯器的傳名調用實現,每每是將參數放到一個臨時函數之中,再將這個臨時函數傳入函數體,這個臨時函數就叫作Thunk
函數。javascript
編譯器的求值策略一般分爲傳值調用以及傳名調用,在下面的例子中,將一個表達式做爲參數進行傳遞,傳值調用以及傳名調用中實現的方式有所不一樣。html
var x = 1; function s(y){ console.log(y + 1); // 3 } s(x + 1);
在上述的例子中,不管是使用傳值調用仍是使用傳名調用,執行的結果都是同樣的,可是其調用過程不一樣:java
x + 1
,而後將計算結果2
傳遞到s
函數,即至關於調用s(2)
。x + 1
表達式傳遞給y
,使用時再計算x + 1
,即至關於計算(x + 1) + 1
。傳值調用與傳名調用各有利弊,傳值調用比較簡單,可是對參數求值的時候,實際上還沒用到這個參數,有可能形成沒有必要的計算。傳名調用能夠解決這個問題,可是實現相對來講比較複雜。git
var x = 1; function s(y){ console.log(y + 1); // 3 } s(x + 1, x + 2);
在上面這個例子中,函數s
並無用到x + 2
這個表達式求得的值,使用傳名調用的話只將表達式傳入而並未計算,只要在函數中沒有用到x + 2
這個表達式就不會計算,使用傳值調用的話就會首先將x + 2
的值計算而後傳入,若是沒有用到這個值,那麼就多了一次沒有必要的計算。Thunk
函數就是做爲傳名調用的實現而構建的,每每是將參數放到一個臨時函數之中,再將這個臨時函數傳入函數體,這個臨時函數就叫作Thunk
函數。github
var x = 1; function s(y){ console.log(y + 1); // 3 } s(x + 1); // 等同於 var x = 1; function s(thunk){ console.log(thunk() + 1); // 3 } var thunk = function(){ return x + 1; } s(thunk);
Js
中的求值策略是是傳值調用,在Js
中使用Thunk
函數須要手動進行實現且含義有所不一樣,在Js
中,Thunk
函數替換的不是表達式,而是多參數函數,將其替換成單參數的版本,且只接受回調函數做爲參數。segmentfault
// 假設一個延時函數須要傳遞一些參數 // 一般使用的版本以下 var delayAsync = function(time, callback, ...args){ setTimeout(() => callback(...args), time); } var callback = function(x, y, z){ console.log(x, y, z); } delayAsync(1000, callback, 1, 2, 3); // 使用Thunk函數 var thunk = function(time, ...args){ return function(callback){ setTimeout(() => callback(...args), time); } } var callback = function(x, y, z){ console.log(x, y, z); } var delayAsyncThunk = thunk(1000, 1, 2, 3); delayAsyncThunk(callback);
實現一個簡單的Thunk
函數轉換器,對於任何函數,只要參數有回調函數,就能寫成Thunk
函數的形式。網絡
var convertToThunk = function(funct){ return function (...args){ return function (callback){ return funct.apply(this, args); } }; }; var callback = function(x, y, z){ console.log(x, y, z); } var delayAsyncThunk = convertToThunk(function(time, ...args){ setTimeout(() => callback(...args), time); }); thunkFunct = delayAsyncThunk(1000, 1, 2, 3); thunkFunct(callback);
Thunk
函數在ES6
以前可能應用比較少,可是在ES6
以後,出現了Generator
函數,經過使用Thunk
函數就能夠能夠用於Generator
函數的自動流程管理。首先是關於Generator
函數的基本使用,調用一個生成器函數並不會立刻執行它裏面的語句,而是返回一個這個生成器的迭代器iterator
對象,他是一個指向內部狀態對象的指針。當這個迭代器的next()
方法被首次(後續)調用時,其內的語句會執行到第一個(後續)出現yield
的位置爲止,yield
後緊跟迭代器要返回的值,也就是指針就會從函數頭部或者上一次停下來的地方開始執行到下一個yield
。或者若是用的是yield*
,則表示將執行權移交給另外一個生成器函數(當前生成器暫停執行)。app
function* f(x) { yield x + 10; yield x + 20; return x + 30; } var g = f(1); console.log(g); // f {<suspended>} console.log(g.next()); // {value: 11, done: false} console.log(g.next()); // {value: 21, done: false} console.log(g.next()); // {value: 31, done: true} console.log(g.next()); // {value: undefined, done: true} // 能夠無限next(),可是value總爲undefined,done總爲true
因爲Generator
函數可以將函數的執行暫時掛起,那麼他就徹底能夠操做一個異步任務,當上一個任務完成以後再繼續下一個任務,下面這個例子就是將一個異步任務同步化表達,當上一個延時定時器完成以後纔會進行下一個定時器任務,能夠經過這種方式解決一個異步嵌套的問題,例如利用回調的方式須要在一個網絡請求以後加入一次回調進行下一次請求,很容易形成回調地獄,而經過Generator
函數就能夠解決這個問題,事實上async/await
就是利用的Generator
函數以及Promise
實現的異步解決方案。dom
var it = null; function f(){ var rand = Math.random() * 2; setTimeout(function(){ if(it) it.next(rand); },1000) } function* g(){ var r1 = yield f(); console.log(r1); var r2 = yield f(); console.log(r2); var r3 = yield f(); console.log(r3); } it = g(); it.next();
雖然上邊的例子可以自動執行,可是不夠方便,如今實現一個Thunk
函數的自動流程管理,其自動幫咱們進行回調函數的處理,只須要在Thunk
函數中傳遞一些函數執行所須要的參數好比例子中的index
,而後就能夠編寫Generator
函數的函數體,經過左邊的變量接收Thunk
函數中funct
執行的參數,在使用Thunk
函數進行自動流程管理時,必須保證yield
後是一個Thunk
函數。
關於自動流程管理run
函數,首先須要知道在調用next()
方法時,若是傳入了參數,那麼這個參數會傳給上一條執行的yield
語句左邊的變量,在這個函數中,第一次執行next
時並未傳遞參數,並且在第一個yield
上邊也並不存在接收變量的語句,無需傳遞參數,接下來就是判斷是否執行完這個生成器函數,在這裏並無執行完,那麼將自定義的next
函數傳入res.value
中,這裏須要注意res.value
是一個函數,能夠在下邊的例子中將註釋的那一行執行,而後就能夠看到這個值是f(funct){...}
,此時咱們將自定義的next
函數傳遞後,就將next
的執行權限交予了f
這個函數,在這個函數執行完異步任務後,會執行回調函數,在這個回調函數中會觸發生成器的下一個next
方法,而且這個next
方法是傳遞了參數的,上文提到傳入參數後會將其傳遞給上一條執行的yield
語句左邊的變量,那麼在這一次執行中會將這個參數值傳遞給r1
,而後在繼續執行next
,不斷往復,直到生成器函數結束運行,這樣就實現了流程的自動管理。異步
function thunkFunct(index){ return function f(funct){ var rand = Math.random() * 2; setTimeout(() => funct({rand:rand, index: index}), 1000) } } function* g(){ var r1 = yield thunkFunct(1); console.log(r1.index, r1.rand); var r2 = yield thunkFunct(2); console.log(r2.index, r2.rand); var r3 = yield thunkFunct(3); console.log(r3.index, r3.rand); } function run(generator){ var g = generator(); var next = function(data){ var res = g.next(data); if(res.done) return ; // console.log(res.value); res.value(next); } next(); } run(g);
https://github.com/WindrunnerMax/EveryDay
https://www.jianshu.com/p/9302a1d01113 https://segmentfault.com/a/1190000017211798 http://www.ruanyifeng.com/blog/2015/05/thunk.html