javascript中函式的迴圈呼叫
這裡說的迴圈呼叫不是指函式的遞迴,而是指函式的返回值仍然是函式,可以繼續傳參呼叫,如下面的程式碼:
function add(n){
...
}
add(1);
add(1)(2)(3);
這個循壞呼叫問題的來源是筆者在codewars裡遇到的一道題,題目資訊如下圖:
翻譯一下題目大意:實現add函式,函式支援累加功能,並在累加完成後返回累加結果。
筆者開始考慮的是用callee來實現,程式碼如下:
var result = 0;
function add(n){
result += n;
return arguments.callee;
}
對這個實現簡單說明一下:
arguments是進行函式呼叫時,除了指定的引數外,還另外建立的一個隱藏物件(例如上面呼叫函式add時,除了顯式的引數n外還會額外建立一個隱藏引數物件arguments,且這個隱藏引數物件arguments只能在呼叫的函式add內部使用),這個隱藏物件代表正在執行的函式和呼叫它的函式的引數。arguments.callee可以返回正在執行的函式本身,而arguments[i]則可以返回引數列表中第i個引數。
當add函式迴圈呼叫時,前一次呼叫完成後返回的是add函式本身,然後把後一次的引數傳入,開始後一次呼叫。下面用幾個測試用例驗證一下結果是否正確:
var result = 0;
function add(n){
result += n;
return arguments.callee;
}
add(1)(2);
console.log(result);
result = 0;
add(3)(4);
console.log(result);
驗證結果如下:
累加功能驗證沒有問題,如果我們是實現一個業務功能,那麼這麼寫也沒毛病,只是每次迴圈呼叫完成後都要去result裡取值。不過codewars的用例是肯定跑不過的,因為每次函式呼叫返回的都是一個函式,而不是一個值(如add(1)(2)(3)的返回結果期望是6,但上面的實現返回的則是一個add函式物件)。
再考慮一下,估計很多同學都會有下面這個哥們的問題:
翻譯一下這位暴躁老哥的回覆重點:他無法理解同一個函式的返回結果既是一個值又是一個物件——函式add的返回怎麼可以既是一個值,又可以作為一個函式傳入引數執行呢?換句話說,就是最後一次迴圈呼叫需要返回一個值,除最後一次迴圈呼叫外其他呼叫需要返回一個可以傳入引數的函式。
一個函式的返回結果當然不可能既是一個值又是一個物件,所以我們需要從另一個角度來考慮這個問題:當函式本身需要作為一個值加入運算時(在題目中就是作為一個數值),能否自動把函式轉換為期望的資料型別呢?
當然是可以的,就是重寫add函式返回物件的valueof方法。
下面先對valueof方法作一個簡單說明:
無論是一元加號(用於數值運算)還是二元加號(用於字串拼接),都會嘗試將物件轉為期望的資料型別,無論是一元加號還是二元加號,首先會呼叫物件的valueof方法(valueof是從object繼承過來,預設返回物件本身),如果valueof方法的返回結果是基本資料型別,則會用這個值,如果是物件則會繼續呼叫物件的toString方法,如果toString方法的返回結果是基本資料型別,則會用這個值,否則報錯。
重寫valueof方法的實現程式碼如下:
function add(n){
var fn = function(x) {
return add(n + x);
};
fn.valueOf = function() {
return n;
};
return fn;
}
繼續用幾個測試用例驗證一下結果是否正確:
function add(n){
var fn = function(x) {
return add(n + x);
};
fn.valueOf = function() {
return n;
};
return fn;
}
console.log(add(1)(3));
console.log(add(4)(5));
驗證結果如下:
累加功能驗證通過。簡單地說,add(1)(3)的返回結果實際上是在函式add內定義的fn物件,當我們用console.log列印返回結果或者把返回結果用於一元運算時,會隱式呼叫fn物件的valueof方法進行轉換。