1. 程式人生 > >javascript中函式的迴圈呼叫

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方法進行轉換。