一道著名的坑人題,可能一些同學見過:
1 |
['2', '3', '4'].map(parseInt); |
說出上面程式碼的執行結果。
如果不過腦子的說是 [2, 3, 4]
,那麼肯定是錯了。實際上,真正的執行結果是 [2, NaN, NaN]
。為什麼會這樣呢?是因為 map 的運算元是有兩個引數的,第一個引數是被迭代陣列的元素,第二個引數是該元素的下標。所以 ['2', '3', '4'].map(parseInt)
實際上相當於執行了 [parseInt('2', 0), parseInt('3', 1), parseInt('4', 2)]
,結果就變成了 [2, NaN, NaN]
了。
今天我們在這裡主要不是討論為什麼 parseInt('2', 0)
是 2,而 parseInt('3', 1)
是 NaN,實際上我們期望的結果應該是 parseInt('2', 10)
和 parseInt('3', 10)
,把字串 ‘2’、’3′ 轉成十進位制 number。
所以,正確的寫法應該是:
1 |
['2', '3', '4'].map(num => parseInt(num, 10)); |
上面這個寫法對於這個問題來說是簡單的,和函數語言程式設計關係不大。但是,這個問題如果用過程抽象的思路通用化思考,應該是這樣的:
1 |
['2', '3', '4'].map(parseInt.bindRight(null, 10)); |
其中 bindRight 和 bind 方法是相反的,bindRight 相當於從右往左 bind:
1 2 3 4 5 6 7 8 9 |
function add(x, y, z){ return 100*x + 10 * y + z; } let add1 = add.bind(null, 1, 2); let add2 = add.bindRight(null, 1, 2); add1(3); //123 add2(3); //321 |
要實現 bindRight,考慮各種情況,稍稍有些複雜,但也並不太麻煩:
1 2 3 4 5 6 7 8 9 10 11 12 |
Function.prototype.bindRight = function(thisObj, ...values){ let fn = this, len = fn.length - values.length; return function(...args){ let rest = [], rargs = values.reverse(); if(len > 0){ rest = args.slice(0, len); } return fn.apply(thisObj, rest.concat(rargs)); } } |
這樣就實現了我們需要的 bindRight,完整的結果如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
Function.prototype.bindRight = function(thisObj, ...values){ let fn = this, len = fn.length - values.length; return function(...args){ let rest = [], rargs = values.reverse(); if(len > 0){ rest = args.slice(0, len); } return fn.apply(thisObj, rest.concat(rargs)); } } console.log(["2","3","4"].map(parseInt)); console.log(["2","3","4"].map(parseInt.bindRight(null, 10))); function add(x, y, z){ return 100*x + 10 * y + z; } let add1 = add.bind(null, 1, 2); let add2 = add.bindRight(null, 1, 2); console.log(add1(3)); //123 console.log(add2(3)); //321 |
bindRight 程式碼並不複雜,如果 bindRight 的引數個數比函式形參多,那麼簡單將引數次序 reverse 之後傳給原來的函式,否則將函式前面的引數補齊。
除了這樣實現 bindRight 之外,還可以有利用更基礎的高階函式操作組合的方式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
Function.prototype.reverseArgs = function(){ let fn = this; return function(...args){ return fn.apply(this, args.reverse()); } } Function.prototype.fixArgsLength = function(len){ let fn = this; return function(...args){ args.length = len || fn.length; return fn.apply(this, args); } } Function.prototype.bindRight = function(thisObj, ...values){ return this.reverseArgs().bind(thisObj, ...values).reverseArgs().fixArgsLength(1); } |
上面的這段程式碼裡我們通過 reverseArgs 和 fixArgsLength 以及 bind 來組合出 bindRight。reverseArgs 是將函式引數順序倒序的高階函式,fixArgsLength 是將函式引數個數固定的高階函式,我們可以看到 bindRight 就是先 reverseArgs 再 bind,最後 fixArgsLength(大家可以思考下為什麼要 fixArgsLength)。這樣就可以了。
不過話說,就上面這個問題直接用 fixArgsLength 也可以——
1 |
["2","3","4"].map(parseInt.fixArgsLength(1)); // [2, 3, 4] |
最後是完整程式碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
Function.prototype.reverseArgs = function(){ let fn = this; return function(...args){ return fn.apply(this, args.reverse()); } } Function.prototype.fixArgsLength = function(len){ let fn = this; return function(...args){ args.length = len || fn.length; return fn.apply(this, args); } } Function.prototype.bindRight = function(thisObj, ...values){ return this.reverseArgs().bind(thisObj, ...values).reverseArgs().fixArgsLength(1); } var a = parseInt.bind(null, 10); console.log(["2","3","4"].map(parseInt)); console.log(["2","3","4"].map(parseInt.bindRight(null, 10))); console.log(["2","3","4"].map(parseInt.fixArgsLength(1))); function add(x, y, z){ return 100*x + 10 * y + z; } let add1 = add.bind(null, 1, 2); let add2 = add.bindRight(null, 1, 2); console.log(add1(3)); //123 console.log(add2(3)); //321 |
如有任何問題,歡迎討論。