这是我参与8月更文挑战的第31天,活动详情查看:8月更文挑战
前言
柯里化原本是数学中函数的一种形式,后面扩展到计算机科学中,是把接受多个参数的函数转变为接受单一参数的函数,并且会返回一个函数,这些函数的参数接受剩余的参数,其实就是利用闭包来保存之前传入的参数,使得后续函数可以直接使用。在js中,柯里化是函数式编程的基础。
一个简单的栗子
为了更好地了解柯里化,这里通过一个求和函数sum来演示柯里化的过程:
function sum(a, b, c) {
return a + b + c;
}
根据柯里化的性质,可以知道:
sum(1,2,3) = sum(1)(2)(3) = sum(1,2)(3) = sum(1)(2,3)
很明显,柯里化后sum返回的是一个函数,并且还得保存已传入参数的状态来供返回的函数调用,这里就使用了闭包。例如,当调用sum(1)时返回的是一个已经保存了参数1的函数,后续调用这个函数会继续使用参数1来计算结果。
柯里化的最简实现代码如下:
function sum(a) {
return function(b) {
return function(c) {
return a+b+c
};
};
}
上面代码是sum柯里化的一个最简单实现,只实现了sum(1,2,3) = sum(1)(2)(3)功能,但往往,柯里化会被封装成一个包装函数,通过传入一个函数,然后返回一个柯里化后的函数,例如的lodash中的_.curry。
我们再来使用柯里化包装函数对sum进行改造:
function curry(f) {
return function(a) {
return function(b) {
return function(c) {
return f(a, b, c);
};
};
};
}
function sum(a, b, c) {
return a + b + c;
}
let curriedSum = curry(sum);
console.log(curriedSum(1)(2)(3))//6
虽然实现了柯里化包装器的封装,但这还不够,它每次只能传入1个函数,无法实现参数的自由组合,不能算是完整的柯里化。
再来完善一下,来实现一个多个参数的柯里化函数包装器。
// 柯里化包装器
function curry(func) {
return function curried(...args) { // 1
if (args.length >= func.length) { //2
return func.apply(this, args);
} else { //3
return function(...args2) { //4c
return curried.apply(this, args.concat(args2));
}
}
};
}
- 柯里化包装器的任务就是将一个函数柯里化后返回
- 如果新函数的参数个数大于等于原函数的形参个数(
Function,length),那么直接执行,对应于sum(1,2,3) = curriedSum(1,2,3) - 否则递归调用柯里化函数,返回一个保存了上一个传入参数的函数。
function sum(a, b, c) {
return a + b + c;
}
let curriedSum = curry(sum);
alert( curriedSum(1, 2, 3) ); // 6,3个参数,相当于直接调用sum
alert( curriedSum(1)(2,3) ); // 6,对第一个参数的柯里化
alert( curriedSum(1)(2)(3) ); // 6,全柯里化
应用场景
上面的sum函数柯里化只是演示了柯里化的过程,其实它是柯里化的一种用途:延迟执行。实际开发中,柯里化主要用于参数复用、兼容性处理、延迟执行等等。虽然柯里化后函数明显复杂了,但是却提高了函数的适用性。
参数复用
例如我们要封装一个发起ajax请求的函数,请求有多种方法类型,我们可以分别为这些方法进行封装,但这其实里面很多逻辑都是可以复用的,这里就可以使用函数的柯里化。
function request(method,url,payload){
const xhr = new new XMLHttpRequest(...)
xhr.open(method, url, true);
xhr.send(payload);
}
function curriedRequest(method){
return function(url,payload){
const xhr = new new XMLHttpRequest(...)
xhr.open(method, url, true);
xhr.send(payload);
}
}
这里实现了对method参数的复用,它能返回适用于不同请求方式的函数。
const getAjax = curriedRequest('GET')
const postAjax = curriedRequest('POST')
postAjax('www.example.com',JSON.stringify({...}))
/*
...
*/
特性检测/兼容性处理
因为浏览器的发展和其他各种原因,有些函数和方法没有被部分浏览器支持,而我们写的代码需要覆盖大部分的群体,因此我们需要进行提前判断,从而确定用户的浏览器支不支持相关的方法,然后对不支持的方法进行polyfill。例如下面的事件监听,addEventListener和attchEvent分别是2种注册监听的方式,其中attchEvent只在低版本IE中实现,因此我们需要创建一个函数来进行对两者的判断。
const whichEvent = ( function () {
// 绝大部分支持,优先判断addEventListener
if(window.addEventListener){
return function(element,type,listener,useCapture){
element.addEventListener(type,function(e){
listener.call(element,e);
},useCapture);
}
// 判断IE中的attchEvent
}else if(window.attchEvent){
return function(element,type,handler){
element.attchEvent('on'+element,function(e){
handler.call(element,e);
});
}
}
} )();