在知乎上看到這樣一個問題:http://www.zhihu.com/question/31805304;
簡單地說就是實現這樣一個add函式:
add(x1)(x2)(x3)...(xn) == x1 + x2 + x3 + ... + xn // true;
正好發現在codewars上也有這道題,那不妨一塊刷了吧。
Kata Description
level:5 kyu
We want to create a function that will add numbers together when called in succession.
add(1)(2); // returns 3
We also want to be able to continue to add numbers to our chain.
add(1)(2)(3); // 6 add(1)(2)(3)(4); // 10 add(1)(2)(3)(4)(5); // 15
and so on...
Test cases:
Test.expect(add(1) == 1); Test.expect(add(1)(2) == 3); Test.expect(add(1)(2)(3) == 6);
Solution
首先,第一個示例似乎表示add(1)(2)應該返回數字3,但如此一來add(1)(2)(3)豈不是將函式呼叫應用在了一個數字上面?
我們再來看這道題提供的Test Cases, 注意cases裡使用的是'=='而不是'===',也就是說返回值不需要是一個數字,只需要是一個能進行型別轉換以變成數字的‘值’就可以了。而這道題中,因為add可以連續呼叫,所以這個‘值’應該是一個函式。
那麼我們要解決的第一個問題: 怎樣能使得函式轉化為數字?
問題一 怎樣將函式與數字聯絡起來
1.valueOf() 方法。
顧名思義,這個方法返回的是呼叫這個物件的‘值’。
預設情況下一個函式呼叫valueOf()方法,返回值是函式本身
var fn = function(num){return ++num;}; fn(1); // 2 (fn.valueOf())(1); // 2
然而這並沒有什麼卵用。
在這個題目裡,我們需要重寫這個方法:
var fn = function () {}; fn.valueOf = function(){ return 233;}; console.log(fn==233); // true console.log(fn===233); //false
以上我們可以發現,當 == 對函式物件進行型別轉化的時候,會讀取函式的valueOf的返回值,這樣就達到了我們的目的。
實際上Function本身並沒有valueOf方法,該方法繼承自Object.prototype.valueOf();
也就是說,不僅僅是函式,當我們需要對任何物件與數字進行比較的時候,都能夠使用這個方法。
var o = {}; o.valueOf = function(){return 233;}; console.log(o == 233) // true console.log(o === 233) // false
另外,與Function不同,Number,String這些物件,都有著自己的valueOf方法,而非繼承於Object。
所以我們可以猜測,==符進行型別轉化的時候,可能都是先讀取物件的valueOf的返回值進行比較。
2. toString()方法。
除了用上面的valueOf方法以外,toString也可以達到同樣的目的。
var fn = new Function; fn.toString = function() { return 233;}; console.log(fn==233); // true console.log(fn===233); //false
和valueOf不同的是,Function中的toString並非繼承自Object,它使用的是自己的toString方法。
對於函式而言,toString的返回值就像是呼叫函式的‘名字’。修改了toString, 就好像給函式換了個‘稱號’:
var fn = function(){}; console.log(fn); // function() var fn2 = function(){}; fn2.toString = function(){return 233;}; console.log(fn2) // 233
預設呼叫toString返回的值是該函式的字串形式,就像對這段程式碼進行了反編譯。
var fn = function(){ //Can you see me? //Oh.. return 233; }; var fnStr = fn.toString(); console.log(fnStr);
輸出
'function (){ //Can you see me? //Oh.. return 233; }'
嗯沒錯連函式裡的註釋,換行,空格什麼的都包括進來了。
另外MDN的文件說toString()還可以接受一個縮排的引數,不過我試了試好像沒有什麼用。
3. toString() 與 valueOf()
既然上面兩種方法都可以,那麼在這個題目裡,如果同時使用了這兩種方法,那種的優先順序更高呢?
這個簡單,我們來試一試:
var fn = function(){}; fn.toString = function(){return 'toString wins';}; fn.valueOf = function() {return 'valueOf wins';} fn == 'toString wins'; // false fn == 'valueOf wins'; // true
看來是valueOf()等級更高啊。
也就是說,使用==符對物件與普通資料進行比較的時候,先讀取物件的‘value’,如果value不是普通資料型別,再讀取‘string’,如果還不是,那就輸出false。當然,在讀取過程中,如果讀取到了普通資料型別並且判斷為不等,那就直接輸出fasle,不會進行下一步的比較。
問題二 然後呢
上面的問題解決後這個題也就解決得差不多了。
首先我們的add函式應該返回一個函式,並且我們需要對這個函式的valueOf(或者toString)進行重寫:
var add=function(num){
var inner = function(){}; inner.valueOf=function(){}; return inner; };
然後用一個temp變數來儲存累加的結果吧,temp同時也應該是內部函式的valueOf的返回值:
var add=function(num){ var temp = num; var inner = function(num2){}; inner.valueOf=function(){
return temp;
}; return inner; };
最後補全內部函式的主體部分,因為這個函式要能夠不停地被呼叫,所以inner函式的返回值應該是它本身:
var add=function(num){ var temp = num; var inner = function(num2){ temp+=num2; return inner; }; inner.valueOf=function(){
return temp;
}; return inner; };
到此大功告成,submit, pass!
到這裡這道題告一段落,不過呢我發現上面的解決方案裡完美的解決方案還有一定距離,在我提交程式碼並看了排名靠前的程式碼後……
比如temp這個變數是必須的嗎?
程式碼裡出現了兩次 return inner;是不是有些冗餘的感覺呢?
……