摘要:函數柯里化在函數式編程中,函數是一等公民。函數柯里化的主要作用和特點就是參數復用提前返回和延遲執行。可能在實際應用場景中,很少使用函數柯里化的解決方案,但是了解認識函數柯里化對自身的提升還是有幫助的。
最近在整理面試資源的時候,發現一道有意思的題目,所以就記錄下來。
題目如何實現 multi(2)(3)(4)=24?
首先來分析下這道題,實現一個 multi 函數并依次傳入參數執行,得到最終的結果。通過題目很容易得到的結論是,把傳入的參數相乘就能夠得到需要的結果,也就是 2X3X4 = 24。
簡單的實現那么如何實現 multi 函數去計算出結果值呢?腦海中首先浮現的解決方案是,閉包。
function multi(a) { return function(b) { return function(c) { return a * b * c; } } }
利用閉包的原則,multi 函數執行的時候,返回 multi 函數中的內部函數,再次執行的時候其實執行的是這個內部函數,這個內部函數中接著又嵌套了一個內部函數,用于計算最終結果并返回。
單純從題面來說,似乎是已經實現了想要的結果,但仔細一想就會發現存在問題。
上面的實現方案存在的缺陷:
代碼不夠優雅,實現步驟需要一層一層的嵌套函數。
可擴展性差,假如是要實現 multi(2)(3)(4)...(n) 這樣的功能,那就得嵌套 n 層函數。
那么有沒有更好的解決方案,答案是,使用函數式編程中的函數柯里化實現。
函數柯里化在函數式編程中,函數是一等公民。那么函數柯里化是怎樣的呢?
函數柯里化指的是將能夠接收多個參數的函數轉化為接收單一參數的函數,并且返回接收余下參數且返回結果的新函數的技術。
函數柯里化的主要作用和特點就是參數復用、提前返回和延遲執行。
例如:封裝兼容現代瀏覽器和 IE 瀏覽器的事件監聽的方法,正常情況下封裝是這樣的。
var addEvent = function(el, type, fn, capture) { if(window.addEventListener) { el.addEventListener(type, function(e) { fn.call(el, e); }, capture); }else { el.attachEvent("on" + type, function(e) { fn.call(el, e); }) } }
該封裝的方法存在的不足是,每次寫監聽事件的時候調用 addEvent 函數,都會進行 if else 的兼容性判斷。事實上在代碼中只需要執行一次兼容性判斷就可以了,后續的事件監聽就不需要再去判斷兼容性了。那么怎么用函數柯里化優化這個封裝函數。
var addEvent = (function() { if(window.addEventListener) { return function(el, type, fn, capture) { el.addEventListener(type, function(e) { fn.call(el, e); }, capture); } }else { return function(ele, type, fn) { el.attachEvent("on" + type, function(e) { fn.call(el, e); }) } } })()
js 引擎在執行該段代碼的時候就會進行兼容性判斷,并且返回需要使用的事件監聽封裝函數。這里使用了函數柯里化的兩個特點:提前返回和延遲執行。
柯里化另一個典型的應用場景就是 bind 函數的實現。使用了函數柯里化的兩個特點:參數復用和提前返回。
Function.prototype.bind = function(){ var fn = this; var args = Array.prototye.slice.call(arguments); var context = args.shift(); return function(){ return fn.apply(context, args.concat(Array.prototype.slice.call(arguments))); }; };柯里化的實現
那么如何通過函數柯里化實現面試題的功能呢?
通用版function curry(fn) { var args = Array.prototype.slice.call(arguments, 1); return function() { var newArgs = args.concat(Array.prototype.slice.call(arguments)); return fn.apply(this, newArgs); } }
curry 函數的第一個參數是要動態創建柯里化的函數,余下的參數存儲在 args 變量中。
執行 curry 函數返回的函數接收新的參數與 args 變量存儲的參數合并,并把合并的參數傳入給柯里化了的函數。
function multiFn(a, b, c) { return a * b * c; } var multi = curry(multiFn); multi(2,3,4);
結果:
雖然得到的結果是一樣的,但是很容易發現存在問題,就是代碼相對于之前的閉包實現方式較復雜,而且執行方式也不是題目要求的那樣 multi(2)(3)(4)。那么下面就來改進這版代碼。
改進版就題目而言,是需要執行三次函數調用,那么針對柯里化后的函數,如果傳入的參數沒有 3 個的話,就繼續執行 curry 函數接收參數,如果參數達到 3 個,就執行柯里化了的函數。
function curry(fn, args) { var length = fn.length; var args = args || []; return function(){ newArgs = args.concat(Array.prototype.slice.call(arguments)); if(newArgs.length < length){ return curry.call(this,fn,newArgs); }else{ return fn.apply(this,newArgs); } } } function multiFn(a, b, c) { return a * b * c; } var multi = curry(multiFn); multi(2)(3)(4); multi(2,3,4); multi(2)(3,4); multi(2,3)(4);
可以看到,通過改進版的柯里化函數,已經將題目定的實現方式擴展到好幾種了。這種實現方案的代碼擴展性就比較強了,但是還是有點不足,就是必須事先知道求值的參數個數,那能不能讓代碼更靈活點,達到隨意傳參的效果,例如: multi(2)(3)(4),multi(5)(6)(7)(8)(9) 這樣的。
優化版function multi() { var args = Array.prototype.slice.call(arguments); var fn = function() { var newArgs = args.concat(Array.prototype.slice.call(arguments)); return multi.apply(this, newArgs); } fn.toString = function() { return args.reduce(function(a, b) { return a * b; }) } return fn; }
這樣的解決方案就可以靈活的使用了。不足的是返回值是 Function 類型。
總結就題目本身而言,是存在多種實現方式的,只要理解并充分利用閉包的強大。
可能在實際應用場景中,很少使用函數柯里化的解決方案,但是了解認識函數柯里化對自身的提升還是有幫助的。
理解閉包和函數柯里化之后,如果在面試中遇到類似的題型,應該就可以迎刃而解了。
后記本著學習和總結的態度寫的技術輸出,文中有任何錯誤和問題,請大家指出。更多的技術輸出可以查看我的 github博客。
整理了一些前端的學習資源,希望能夠幫助到有需要的人,地址: 學習資源匯總。
參考https://segmentfault.com/q/10...
https://blog.csdn.net/crystal...
https://mp.weixin.qq.com/s?__biz=MjM5MTA1MjAxMQ==&mid=2651228431&idx=1&sn=c9d62a30a52f4572cc0cb4aaf2a82ef3
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/97071.html
摘要:忍者秘籍一書中,對于柯里化的定義如下在一個函數中首先填充幾個參數然后再返回一個新函數的技術稱為柯里化。回到我們的題目本身,其實根據測試用例我們可以發現,函數的要求就是接受單一函數,例如但是與柯里化不同之處在于,柯里化返回的一個新函數。 歡迎大家再一次來到我的文章專欄:從面試題中我們能學到什么,各位同行小伙伴是否已經開始了悠閑的春節假期呢?在這里提前祝大家雞年大吉吧~哈哈,之前有人說...
摘要:原題如下寫一個方法,當使用下面的語法調用時,能正常工作這道題要考察的,就是對函數柯里化的理解。當參數只有一個的時候,進行柯里化的處理。這其實就是函數柯里化的簡單應用。 showImg(https://segmentfault.com/img/bVbopGm?w=620&h=350); 前言 這是前端面試題系列的第 6 篇,你可能錯過了前面的篇章,可以在這里找到: ES6 中箭頭函數的...
摘要:函數柯里化關于函數柯里化的問題最初是在忍者秘籍中講閉包的部分中看到的,相信很多同學見過這樣一道和柯里化有關的面試題實現一個函數,使得如下斷言能夠能夠通過簡單說就是實現一個求值函數,能夠將所有參數相加得出結果。方法返回一個表示該對象的字符串。 函數柯里化 ??關于函數柯里化的問題最初是在《JavaScript忍者秘籍》中講閉包的部分中看到的,相信很多同學見過這樣一道和柯里化有關的面試題:...
閱讀 3338·2022-01-04 14:20
閱讀 3107·2021-09-22 15:08
閱讀 2188·2021-09-03 10:44
閱讀 2314·2019-08-30 15:44
閱讀 1490·2019-08-29 18:40
閱讀 2654·2019-08-29 17:09
閱讀 2988·2019-08-26 13:53
閱讀 3220·2019-08-26 13:37