問題發現
我們在平常的開發過程中可能會寫出以下型別的程式碼:
...fun(x+1,2);
//引數是一個表示式的情況...複製程式碼
諸如此類的引數是一個表示式,那我們時候考慮過一個問題,這個引數究竟是在什麼時候求的值呢?
兩種情況
我們通過下面的這個函式來講解兩種情況
var a = 1;
function double(argument){
return argument * 2;
}double(a+1);
複製程式碼
傳值呼叫(call by value)
就是在引數進入函式體之前就把表示式的值計算出來在代入函式體。類似於C語言
double(a+1);
//當使用傳值呼叫時,上面的語句等同於double(2);
複製程式碼
傳名呼叫(call by name)
直接把表示式傳入函式體,當需要用到它的時候再運算它求值。類似於Scala
double(a+1);
//當使用傳名呼叫時,上面的語句等同於(a+1) * 2;
複製程式碼
利弊分析
傳值呼叫 相比較於 傳名呼叫 比較簡單,但是對引數求值的時候如果我們實際並沒有用到這個引數可能會造成效能的損失。
function fun(a,b){
if(a == 1){
return b;
}else{
return a;
}
}var x = 100;
fun(0,x * 3 * 10 - x * 20 + x);
//注意此時傳遞進去的第二個引數因為條件判斷後並沒有使用複製程式碼
上面的程式碼中,因為 a 並不等於 1 ,所以這次函式我們並沒有用到第二個引數,但是如果我們要求值就需要計算那一長串表示式,實際上是沒有必要的,所以我們更傾向於傳名呼叫這種高效率的引數求值方式。
But, what a pity! JavaScript語言使用的方式是傳值呼叫,但我們可以用一種其他方法來實現傳名呼叫——Thunk函式!
Thunk函式
編譯器進行傳名呼叫的方法就是先將引數放到一個臨時函式中,再將這個臨時函式傳入函式體。這個臨時函式就是主角 —— Thunk函式。
function double(argument){
return argument * 2;
}double(a+1);
//等同於var thunk = function(){
//thunk臨時函式 return a + 1;
}function double(thunk){
return thunk() * 2;
}複製程式碼
相當於是當我們需要用的那個引數裡表示式的值的時候就呼叫那個函式求值即可。它是實現傳名呼叫的一種策略。
在Javascript中實現Thunk函式
對於Javascript這門語言來說,它的Thunk函式更加特殊。它替換的不是表示式而是多引數函式,把它變成一個-只接受回撥函式-作為引數-的-單引數函式。
// ES6版本var Thunk = function(fun) {
//一個thunk函式轉換器 return function (...args) {
return function (callback) {
return fn.call(this, ...args, callback);
}
};
};
複製程式碼
使用轉換器我們可以把一個需要引入多個回撥函式的函式變為一個thunk函式。
var multipleParametersFunc = function(param,callback){
console.log(param);
callback();
}//使用轉換器var thunkFunc = Thunk(multipleParametersFunc);
thunkFunc(param)(callback);
複製程式碼
Thunk的強大功能——Generator函式的自動流程管理
Thunk函式在ES6的出現之後又有了一個強大的功能——對Generator函式的自動流程管理。
在不使用Thunk函式時我們可以用如下方法自動執行Generator
function* gen(){
//do something...
}var g = gen();
var result = g.next();
while(!result.done){
console.log(result.value);
result = g.next();
}複製程式碼
下面我們使用了Thunk函式自動執行
function thunkRunGen(fun){
var gen = fun();
function next(err, data){
var result = gen.next(data);
if(result.done) return;
result.value(next);
} next();
}function* g(){
//do something
}thunkRunGen(g);
複製程式碼
上面程式碼的thunkRunGen
函式,就是一個 Generator 函式的自動執行器。內部的next
函式就是 Thunk 的回撥函式。next
函式先將指標移到 Generator 函式的下一步(gen.next
方法),然後判斷 Generator 函式是否結束(result.done
屬性),如果沒結束,就將next
函式再傳入 Thunk 函式(result.value
屬性),否則就直接退出。
使用這個執行器,不管Generator函式內部有多少個非同步操作,我們只需要把Generator函式傳入thunkRunGen即可。但有個前提,每個yield 函式後面都需要是Thunk函式。
Thunk 函式它並不是 Generator 函式自動執行的唯一方案。因為自動執行的關鍵是-自動控制 Generator 函式的流程,接收和交還程式的執行權。回撥函式可以做到這一點,Promise 物件當然也可以做到這一點。