您好,登錄后才能下訂單哦!
這篇文章將為大家詳細(xì)講解有關(guān)JavaScript中的函數(shù)柯理化是什么意思,小編覺得挺實(shí)用的,因此分享給大家做個(gè)參考,希望大家閱讀完這篇文章后可以有所收獲。
什么是函數(shù)柯里化?先看看維基百科如何解釋:
在計(jì)算機(jī)科學(xué)中,柯里化(英語:Currying),又譯為卡瑞化或加里化,是把接受多個(gè)參數(shù)的函數(shù)變換成接受一個(gè)單一參數(shù)(最初函數(shù)的第一個(gè)參數(shù))的函數(shù),并且返回接受余下的參數(shù)而且返回結(jié)果的新函數(shù)的技術(shù)。這個(gè)技術(shù)由克里斯托弗·斯特雷奇以邏輯學(xué)家哈斯凱爾·加里命名的,盡管它是Moses Sch?nfinkel和戈特洛布·弗雷格發(fā)明的。
在直覺上,柯里化聲稱“如果你固定某些參數(shù),你將得到接受余下參數(shù)的一個(gè)函數(shù)”。所以對(duì)于有兩個(gè)變量的函y^x,如果固定了y=2,則得到有一個(gè)變量的函數(shù)2^x。
Currying的概念其實(shí)并不復(fù)雜,用通俗易懂的話說:只傳遞給函數(shù)一部分參數(shù)來調(diào)用它,讓它返回一個(gè)函數(shù)去處理剩下的參數(shù)。
如果文字解釋還是有一點(diǎn)抽象,我們就拿add函數(shù),來做一個(gè)簡(jiǎn)單的函數(shù)柯里化的實(shí)現(xiàn)。
// 普通的add函數(shù) function add(x, y) { return x + y } // add函數(shù)柯里化后 var curryingAdd = function(x) { return function(y) { return x + y; }; }; // 函數(shù)復(fù)用 var increment = curryingAdd(1); var addTen = curryingAdd(10); increment(2); // 3 addTen(2); // 12
實(shí)際上就是把a(bǔ)dd函數(shù)的x,y兩個(gè)參數(shù)變成了先用一個(gè)函數(shù)接收x然后返回一個(gè)函數(shù)去處理y參數(shù)?,F(xiàn)在思路應(yīng)該就比較清晰了,就是只傳遞給函數(shù)一部分參數(shù)來調(diào)用它,讓它返回一個(gè)函數(shù)去處理剩下的參數(shù)。
看完上面的關(guān)于add函數(shù)的柯里化,問題來了,費(fèi)這么大勁封裝一層,到底有什么用處呢?
其實(shí)剛剛第一個(gè)add函數(shù)的柯里化例子中已經(jīng)涉及到了函數(shù)柯里化所帶來的函數(shù)復(fù)用的便捷,我們通過add函數(shù)柯里化,很快捷地實(shí)現(xiàn)了increment函數(shù)和addTen函數(shù),再來看個(gè)例子:
// 正常正則驗(yàn)證字符串 reg.test(txt) // 函數(shù)封裝后 function check(reg, txt) { return reg.test(txt) } check(/\d+/g, 'test') //false check(/[a-z]+/g, 'test') //true // Currying后 function curryingCheck(reg) { return function(txt) { return reg.test(txt) } } var hasNumber = curryingCheck(/\d+/g) var hasLetter = curryingCheck(/[a-z]+/g) hasNumber('test1') // true hasNumber('testtest') // false hasLetter('21212') // false
上面的示例是一個(gè)正則的校驗(yàn),正常來說直接調(diào)用check函數(shù)就可以了,但是如果我有很多地方都要校驗(yàn)是否有數(shù)字,其實(shí)就是需要將第一個(gè)參數(shù)reg進(jìn)行復(fù)用,這樣別的地方就能夠直接調(diào)用hasNumber,hasLetter等函數(shù),讓參數(shù)能夠復(fù)用,調(diào)用起來也更方便。
var on = function(element, event, handler) { if (document.addEventListener) { if (element && event && handler) { element.addEventListener(event, handler, false); } } else { if (element && event && handler) { element.attachEvent('on' + event, handler); } } } var on = (function() { if (document.addEventListener) { return function(element, event, handler) { if (element && event && handler) { element.addEventListener(event, handler, false); } }; } else { return function(element, event, handler) { if (element && event && handler) { element.attachEvent('on' + event, handler); } }; } })();
換一種寫法可能比較好理解一點(diǎn),上面就是把isSupport這個(gè)參數(shù)給先確定下來了
var on = function(isSupport, element, event, handler) { isSupport = isSupport || document.addEventListener; if (isSupport) { return element.addEventListener(event, handler, false); } else { return element.attachEvent('on' + event, handler); } }
我們?cè)谧鲰?xiàng)目的過程中,封裝一些dom操作可以說再常見不過,上面第一種寫法也是比較常見,但是我們看看第二種寫法,它相對(duì)一第一種寫法就是自執(zhí)行然后返回一個(gè)新的函數(shù),這樣其實(shí)就是提前確定了會(huì)走哪一個(gè)方法,避免每次都進(jìn)行判斷。
Function.prototype.bind = function (context) { var _this = this var args = Array.prototype.slice.call(arguments, 1) return function() { return _this.apply(context, args) } }
像我們js中經(jīng)常使用的bind,實(shí)現(xiàn)的機(jī)制就是Currying.
通用的封裝方法:
// 初步封裝 var currying = function(fn) { // args 獲取第一個(gè)方法內(nèi)的全部參數(shù) var args = Array.prototype.slice.call(arguments, 1) return function() { // 將后面方法里的全部參數(shù)和args進(jìn)行合并 var newArgs = args.concat(Array.prototype.slice.call(arguments)) // 把合并后的參數(shù)通過apply作為fn的參數(shù)并執(zhí)行 return fn.apply(this, newArgs) } }
這邊首先是初步封裝,通過閉包把初步參數(shù)給保存下來,然后通過獲取剩下的arguments進(jìn)行拼接,最后執(zhí)行需要currying的函數(shù)。
但是上面的函數(shù)還是有些缺陷,這樣返回的話其實(shí)只能多擴(kuò)展一個(gè)參數(shù),currying(a)(b)(c)這樣的話,貌似就不支持了(不支持多參數(shù)調(diào)用),一般這種情況都會(huì)想到使用遞歸再進(jìn)行封裝一層。
// 支持多參數(shù)傳遞 function progressCurrying(fn, args) { var _this = this var len = fn.length; var args = args || []; return function() { var _args = Array.prototype.slice.call(arguments); Array.prototype.push.apply(args, _args); // 如果參數(shù)個(gè)數(shù)小于最初的fn.length,則遞歸調(diào)用,繼續(xù)收集參數(shù) if (_args.length < len) { return progressCurrying.call(_this, fn, _args); } // 參數(shù)收集完畢,則執(zhí)行fn return fn.apply(this, _args); } }
這邊其實(shí)是在初步的基礎(chǔ)上,加上了遞歸的調(diào)用,只要參數(shù)個(gè)數(shù)小于最初的fn.length,就會(huì)繼續(xù)執(zhí)行遞歸。
關(guān)于Currying的性能,我們應(yīng)該知道下面幾點(diǎn):
存取arguments對(duì)象通常要比存取命名參數(shù)要慢一點(diǎn)
一些老版本的瀏覽器在arguments.length的實(shí)現(xiàn)上是相當(dāng)慢的
使用fn.apply( … ) 和 fn.call( … )通常比直接調(diào)用fn( … ) 稍微慢點(diǎn)
創(chuàng)建大量嵌套作用域和閉包函數(shù)會(huì)帶來花銷,無論是在內(nèi)存還是速度上
其實(shí)在大部分應(yīng)用中,主要的性能瓶頸是在操作DOM節(jié)點(diǎn)上,這js的性能損耗基本是可以忽略不計(jì)的,所以curry是可以直接放心的使用。
// 實(shí)現(xiàn)一個(gè)add方法,使計(jì)算結(jié)果能夠滿足如下預(yù)期: add(1)(2)(3) = 6; add(1, 2, 3)(4) = 10; add(1)(2)(3)(4)(5) = 15; function add() { // 第一次執(zhí)行時(shí),定義一個(gè)數(shù)組專門用來存儲(chǔ)所有的參數(shù) var _args = Array.prototype.slice.call(arguments); // 在內(nèi)部聲明一個(gè)函數(shù),利用閉包的特性保存_args并收集所有的參數(shù)值 var _adder = function() { _args.push(…arguments); return _adder; }; // 利用toString隱式轉(zhuǎn)換的特性,當(dāng)最后執(zhí)行時(shí)隱式轉(zhuǎn)換,并計(jì)算最終的值返回 _adder.toString = function () { return _args.reduce(function (a, b) { return a + b; }); } return _adder; } add(1)(2)(3) // 6 add(1, 2, 3)(4) // 10 add(1)(2)(3)(4)(5) // 15 add(2, 6)(1) // 9
通過簡(jiǎn)單地傳遞幾個(gè)參數(shù),就能動(dòng)態(tài)創(chuàng)建實(shí)用的新函數(shù);而且還能帶來一個(gè)額外好處,那就是保留了數(shù)學(xué)的函數(shù)定義,盡管參數(shù)不止一個(gè)。
Currying函數(shù)用起來非常得心應(yīng)手,每天使用它對(duì)我來說簡(jiǎn)直就是一種享受。它堪稱手頭必備工具,能夠讓函數(shù)式編程不那么繁瑣和沉悶。
關(guān)于JavaScript中的函數(shù)柯理化是什么意思就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,可以學(xué)到更多知識(shí)。如果覺得文章不錯(cuò),可以把它分享出去讓更多的人看到。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。