關於函數柯里化的問題最初是在《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(),這兩個都是定義在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))
除了上述的解決方法以外,還可使用alert函數來輸出結果,即:
alert(add(1,2)(2,3)(4))
你們可自行測試,除node外,瀏覽器中均可以彈出「12」。這是爲啥呢?alert和console.log不同嗎? 還真不同,console.log可輸入任何類型的數據,然而alert只能輸出String類型的數據,因此...懂了吧? 最後建議你們平時本身寫代碼不要像本例這樣,在toString/valueOf函數中作數值運算,並且慎用類型轉換。本文做爲填坑記錄,有不對的地方歡迎指正,文中的問題有大神有看法能夠在評論區討論。