一、什么是Currying
定義:柯里化(Currying)是把接受多個(gè)參數(shù)的函數(shù)變換成接受一個(gè)單一參數(shù)(最初函數(shù)的第一個(gè)參數(shù))的函數(shù)如捅,并且返回接受余下的參數(shù)而且返回結(jié)果的新函數(shù)的技術(shù)嗓袱。
// 普通的add函數(shù)
function add(x, y) {
return x + y
}
// Currying后
function curryingAdd(x) {
return function (y) {
return x + y
}
}
add(1, 2) // 3
curryingAdd(1)(2) // 3
實(shí)際上就是把a(bǔ)dd函數(shù)的x摧莽,y兩個(gè)參數(shù)變成了先用一個(gè)函數(shù)接收x然后返回一個(gè)函數(shù)去處理y參數(shù)⊥冢現(xiàn)在思路應(yīng)該就比較清晰了,就是只傳遞給函數(shù)一部分參數(shù)來(lái)調(diào)用它栏赴,讓它返回一個(gè)函數(shù)去處理剩下的參數(shù)。
二靖秩、Currying的優(yōu)點(diǎn)
- 參數(shù)復(fù)用 – 復(fù)用最初函數(shù)的第一個(gè)參數(shù)
- 提前返回 – 返回接受余下的參數(shù)且返回結(jié)果的新函數(shù)
- 延遲執(zhí)行 – 返回新函數(shù)须眷,等待執(zhí)行
// 正常正則驗(yàn)證字符串 reg.test(txt)
// 函數(shù)封裝后
function check(reg, txt) {
return reg.test(txt)
}
//判斷字符串中是否包含數(shù)字
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
curryingCheck(/\d+/g)('test1') // true
hasNumber('testtest') // false
hasLetter('21212') // false
// 兼容IE瀏覽器事件監(jiān)聽(tīng)方法
//原生事件監(jiān)聽(tīng)的方法在現(xiàn)代瀏覽器和IE瀏覽器會(huì)有兼容問(wèn)題,解決該兼容性問(wèn)題的方法是進(jìn)行一層封裝沟突,若不考慮柯里化函數(shù)花颗,我們正常情況下會(huì)像下面這樣進(jìn)行封裝,如下:
/*
* @param el Object DOM元素對(duì)象
* @param type String 事件類(lèi)型
* @param fn Function 事件處理函數(shù)
* @param Capture Boolean 是否捕獲
*/
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)
})
}
}
//唯一的缺陷就是惠拭,每次對(duì)DOM元素進(jìn)行事件綁定時(shí)(每調(diào)用一次函數(shù))都需要重新進(jìn)行判斷扩劝,其實(shí)對(duì)于事件監(jiān)聽(tīng)網(wǎng)頁(yè)一發(fā)布瀏覽器已經(jīng)確定了,就可以知曉瀏覽器到底是需要哪一種監(jiān)聽(tīng)方式职辅。所以我們可以讓判斷只執(zhí)行一次棒呛。
var curEvent = (function() {
if(window.addEventListener) {
return function(el, sType, fn, capture) { // return funtion
el.addEventListener(sType, function() {
fn.call(el, e)
}, capture)
}
} else {
return function(el, sType, fn) {
el.attachEvent('on'+sType, function(e) {
fn.call(el, e)
})
}
}
})
var addEvent = curEvent(); // addEvent 這回得到的,就是if..else...里面的那個(gè)return 的function域携,所以只需要curEvent()執(zhí)行一遍判斷了if..else簇秒,其他時(shí)候就都不需要判斷了
addEvent(elem)
//這里面使用的其實(shí)就是柯里化的應(yīng)用,在這里面addEvent 它就是提前返回了一個(gè)新的函數(shù)秀鞭,而且這個(gè)函數(shù)是根據(jù)瀏覽器到底采用哪種監(jiān)聽(tīng)而返回的趋观。第一次調(diào)用addEvent會(huì)對(duì)瀏覽器做能力檢測(cè),然后锋边,重寫(xiě)了addEvent皱坛。下次再調(diào)用的時(shí)候,由于函數(shù)被重寫(xiě)豆巨,不會(huì)再做能力檢測(cè)剩辟。
這個(gè)例子利用了柯里化提前返回和延遲執(zhí)行的特點(diǎn):
- 提前返回 – 使用函數(shù)立即調(diào)用進(jìn)行了一次兼容判斷,返回兼容的事件綁定方法
- 延遲執(zhí)行 – 返回新函數(shù),在新函數(shù)中調(diào)用兼容的事件方法抹沪。等待addEvent新函數(shù)調(diào)用刻肄,延遲執(zhí)行
//沒(méi)有currying前
var fishWeight = 0;
var addWeight = function(weight) {
fishWeight += weight;
};
addWeight(2.3);
addWeight(6.5);
addWeight(1.2);
addWeight(2.5);
console.log(fishWeight); // 12.5
//currying后
//包裝的思想就是
// 1.sum中建立一個(gè)數(shù)組arr,多次調(diào)用后融欧,數(shù)組把所有的參數(shù)都接收到
// 2.當(dāng)沒(méi)有參數(shù)的時(shí)候敏弃,執(zhí)行add.apply(arr)
//總的來(lái)說(shuō)就是把一堆參數(shù)都先存起來(lái),然后最后再來(lái)執(zhí)行
var curryWeight = function(fn) {
var _fishWeight = [];
return function() {
if (arguments.length === 0) {
return fn.apply(null, _fishWeight);
} else {
_fishWeight = _fishWeight.concat([].slice.call(arguments));
}
}
};
var add = function() {
var fishWeight = 0;
for ( var i=0噪馏,len = arguments.length; i<len; i++) {
fishWeight += arguments[i];
}
return fishWeight
}
var sum = curryWeight(add);
sum(6.5);
sum(1.2);
sum(2.3);
sum(2.5);
sum(); // 這里才計(jì)算 結(jié)果為12.5
sum(6.5)(1.2)(2.3)(2.5)(); // 等效于這種 結(jié)果為12.5
三麦到、Currying的實(shí)現(xiàn)
function curry (fn, currArgs) {
return function() {
let args = [].slice.call(arguments);
// 首次調(diào)用時(shí),若未提供最后一個(gè)參數(shù)currArgs欠肾,則不用進(jìn)行args的拼接
if (currArgs !== undefined) {
args = args.concat(currArgs);
}
// 遞歸調(diào)用
if (args.length < fn.length) {
return curry(fn, args);
}
// 遞歸出口
return fn.apply(null, args);
}
}
首先瓶颠,它有 2 個(gè)參數(shù),fn 指的就是源處理函數(shù) 刺桃;currArgs 是調(diào)用 curry 時(shí)傳入的參數(shù)列表粹淋,比如 (1)(3) 這樣的。
再看到 curry 函數(shù)內(nèi)部瑟慈,它會(huì)整個(gè)返回一個(gè)匿名函數(shù)桃移。
再接下來(lái)的 let args = [].slice.call(arguments);,意思是將 arguments 數(shù)組化葛碧。arguments 是一個(gè)類(lèi)數(shù)組的結(jié)構(gòu)借杰,它并不是一個(gè)真的數(shù)組,所以沒(méi)法使用數(shù)組的方法进泼。我們用了 call 的方法蔗衡,就能愉快地對(duì) args 使用數(shù)組的原生方法了。
currArgs !== undefined 的判斷乳绕,是為了解決遞歸調(diào)用時(shí)的參數(shù)拼接绞惦。
最后,判斷 args 的個(gè)數(shù)刷袍,是否與 fn (也就是 sum )的參數(shù)個(gè)數(shù)相等翩隧,相等了就可以把參數(shù)都傳給 fn,進(jìn)行輸出呻纹;否則堆生,繼續(xù)遞歸調(diào)用,直到兩者相等雷酪。
function sum(a, b, c) {
console.log(a + b + c);
}
const fn = curry(sum);
fn(1, 2, 3); // 6
fn(1, 2)(3); // 6
fn(1)(2, 3); // 6
fn(1)(2)(3); // 6
四淑仆、總結(jié)
函數(shù)的柯里化,它返回的哥力,是一個(gè)函數(shù)的函數(shù)蔗怠。其實(shí)現(xiàn)方式墩弯,需要依賴(lài)參數(shù)以及遞歸,通過(guò)拆分參數(shù)的方式寞射,來(lái)調(diào)用一個(gè)多參數(shù)的函數(shù)方法渔工,以達(dá)到減少代碼冗余,增加可讀性的目的桥温。