關於JavaScript函數柯里化問題探索

函數柯里化

  關於函數柯里化的問題最初是在《JavaScript忍者祕籍》中講閉包的部分中看到的,相信不少同窗見過這樣一道和柯里化有關的面試題:
  實現一個add函數,使得以下斷言可以可以經過:javascript

add(1)(2)(3) === 6
add(1)(2,3)(4) === 10

  簡單說就是實現一個求值函數,可以將全部參數相加得出結果。
  分析一下:主要有兩個要點——1.拿add(1)(2)(3)來講,若是想要在add(1)後再執行(2),那麼須要add(1)返回一個函數才能作到,而後這個函數的入參是2,依次類推。2.這種遞歸何時是個頭呢,貌似沒看到有什麼操做能讓它停下來,這是個好問題,咱們稍後解答。
  根據分析,咱們先實現一個add函數:html

function add(){
    let arr = [];
    arr = arr.concat(Array.prototype.slice.apply(arguments))
    let fun = function(){
        arr = arr.concat(Array.prototype.slice.apply(arguments))
        return fun
    }
    return fun
}
console.log(add(1)(2,3)(4))

這段代碼在chrome中輸出:java

ƒ (){
        arr = arr.concat(Array.prototype.slice.apply(arguments))
        return fun
    }

  沒錯,和咱們預期的同樣...由於無法跳出遞歸調用,因此輸入了fun函數,並且咱們只是把參數存在了數組arr中,可是沒有作累加計算。繼續改進函數:node

function add(){
    let arr = [];
    arr = arr.concat(Array.prototype.slice.apply(arguments))
    let fun = function(){
        arr = arr.concat(Array.prototype.slice.apply(arguments))
        return fun
    }
    fun.getValue = function(){
        return arr.reduce(function(total, num){
            return total+num
        }, 0)
    }
    return fun
}
console.log(add(1,2)(2,3)(4).getValue())  //12

  如今能夠輸出正確的值了,咱們給fun函數添加了一個函數getValue,用於將記錄參數的數組中的元素求和。可是這樣須要在add函數後調用一下getValue,這好像與需求有點差別...面試

valueOf()和toString()

  這時候,咱們想到兩個方法valueOf()和toString(),這兩個都是定義在Object原型上的方法,他們有什麼特別嗎?chrome

Object.prototype.valueOf()
用 MDN 的話來講,valueOf() 方法返回指定對象的原始值。
  JavaScript 調用 valueOf() 方法用來把對象轉換成原始類型的值(數值、字符串和布爾值)。可是咱們不多須要本身調用此函數,valueOf 方法通常都會被 JavaScript 自動調用。
  記住上面這句話,下面咱們會細說所謂的自動調用是什麼意思。
Object.prototype.toString()
toString() 方法返回一個表示該對象的字符串。
  每一個對象都有一個 toString() 方法,當對象被表示爲文本值時或者當以指望字符串的方式引用對象時,該方法被自動調用。
  這裏先記住,valueOf() 和 toString() 在特定的場合下會自行調用。
                  ——摘自ChokCoco《一道面試題引起的對javascript類型轉換的思考數組

  簡單來講就是,這兩個函數會在特定的場合下被js引擎隱式調用,固然兩個函數被調用的條件是不一樣的,這裏不展開分析,可參考《一道面試題引起的對javascript類型轉換的思考》。
  咱們繼續改造add函數:瀏覽器

function add(){
    let arr = [];
    arr = arr.concat(Array.prototype.slice.apply(arguments))
    let fun = function(){
        arr = arr.concat(Array.prototype.slice.apply(arguments))
        return fun
    }
    fun.toString = function(){
        console.log(222)
        return arr.reduce(function(total, num){
            return total+num
        }, 0)
    }
    return fun
}
console.log(add(1,2)(2,3)(4))

  先不執行它,咱們來分析一下。咱們重寫了fun函數的toString方法,假設它會被js引擎調用,咱們調用了reduce方法來爲數組中的元素求和而後return出來,看起來沒什麼毛病對吧?
  首先在chrome(62.0.3202.94)上執行一下這段代碼,看到了什麼?閉包

ƒ 12
222
222

  是否是很詭異?我想要的是12,這f 12是什麼鬼...222輸出了兩遍又是什麼東東,更詭異的是,先輸出了f 12後輸出了222...
  chrome上輸出f 12,咱們能夠寫一個更簡潔的函數來模擬:app

var app = function(){}; 
app.toString = function(){ 
    console.log('toString')
    return 12
}; 
function app1(){ 
    return app 
};
console.log(app1())

  輸出結果和上面徹底同樣,但至於爲何先輸出f 12以及爲何輸出兩遍222,這個須要剖析chrome底層機制了,此處不作討論。(暫時還沒搞明白,後續搞清楚了會更新上來,若是有大神清楚能夠在下面評論。)
  再來看FireFox(57.0)中的表現:
圖片描述
  無語了吧,直接無視咱寫的toString方法,該啥樣還啥樣...
  再來看看IE(11):
圖片描述
  啥也不說了,再瞅瞅node:
圖片描述
  一臉懵逼啊...雖然不知道爲何不一樣環境會輸出這些,但能夠確定的一點是——toString方法都沒有被正常執行。因此,爲了規避這個問題,咱們須要讓js引擎更明確地知道咱們想調用toString,因此,修改一下打印語句:

console.log(''+add(1,2)(2,3)(4))

如今再看看,是否是全部環境輸出都正常了:

222
12

  若是你是用valueOf,也會有相似的問題,只需將打印語句改成:

console.log(+add(1,2)(2,3)(4))

console.log()和alert()

  除了上述的解決方法以外,還可使用alert函數來輸出結果,即:

alert(add(1,2)(2,3)(4))

  你們可自行測試,除node外,瀏覽器中均可以彈出「12」。這是爲啥呢?alert和console.log不同嗎?  還真不同,console.log可輸入任何類型的數據,然而alert只能輸出String類型的數據,因此...懂了吧?  最後建議你們平時本身寫代碼不要像本例這樣,在toString/valueOf函數中作數值運算,並且慎用類型轉換。本文做爲填坑記錄,有不對的地方歡迎指正,文中的問題有大神有看法能夠在評論區討論。

相關文章
相關標籤/搜索