在计算机科学中,柯里化(Currying)是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果的新函数的技术。
主要作用是提高函数的专用性。
下边我们通过用例来理解下柯里化的思想吧!
/**
* @author: hyx
* @description: 返回两个参数的合
* @param {*} a
* @param {*} b
* @return {*} a + b
* @Date: 2022-05-12 00:40:20
*/
function add(a,b) {
return a + b;
}
/**
* @author: hyx
* @description: 柯里化如下
* @param {*} a
* @return {*} a + b
* @Date: 2022-05-12 00:41:42
*/
function curryAdd(a) {
return function(b) {
return a + b;
}
}
console.log(curryAdd(1)(2)); // 输出 3
如果我们的原函数add有3个参数或者更多参数,我们是不是要一层一层的嵌套函数啊,这样太麻烦了。所以下边我们借用纯函数的思想来抽象化这个过程。上代码如下:
// 三个参数的情况
// 原函数
function add(a,b,c) {
return a + b + c;
}
/**
* @author: hyx
* @description: 函数柯里化过程
* @param {*} fn
* @param {*} passedParams
* @return {*}
* @Date: 2022-05-12 00:45:35
*/
function curry(fn,passedParams = []){
let needParamsNum = fn.length;
if(!Array.isArray(passedParams)) {
passedParams = [passedParams];
}
return function() {
// 实例化 passedParams
let _passedParams = passedParams.concat(Array.prototype.slice.call(arguments)); // 切莫在此处直接修改passedParams,否则函数颗粒化后会失去复用参数等特性。
// let _passedParams = passedParams.concat(Array.from(arguments))
if(_passedParams.length < needParamsNum) {
return curry(fn,_passedParams);
}else{
return fn(..._passedParams)
}
}
}
// 柯里化函数后调用
console.log("========>>>", curry(add, 1)(2, 3)); // => 6
console.log("========>>>", curry(add, 1)(2)(3)); // => 6
console.log("========>>>", curry(add)(1, 2, 3)); // => 6
console.log("========>>>", curry(add)(1, 2)(3)); // => 6
console.log("========>>>", curry(add)(1)(2, 3)); // => 6
console.log("========>>>", curry(add)(1)(2)(3)); // => 6
函数柯里化的应用(好处)
- 参数复用
- 提前返回
- 延迟执行
好处一:参数复用
- 对于一些有很多参数相同的函数调用情况,柯里化就非常适用了。
- 看下边例子↓↓↓
// 1. 参数复用
// 原函数
function add(a,b,c) {
return a + b + c;
}
/**
* @author: hyx
* @description: 函数柯里化过程
* @param {*} fn
* @param {*} passedParams
* @return {*}
* @Date: 2022-05-12 00:57:23
*/
function curry(fn,passedParams = []){
let needParamsNum = fn.length;
if(!Array.isArray(passedParams)) {
passedParams = [passedParams];
}
return function() {
// 实例化 passedParams
let _passedParams = passedParams.concat(Array.prototype.slice.call(arguments)); // 切莫在此处直接修改passedParams,否则函数颗粒化后会失去复用参数等特性。
// let _passedParams = passedParams.concat(Array.from(arguments))
if(_passedParams.length < needParamsNum) {
return curry(fn,_passedParams);
}else{
return fn(..._passedParams)
}
}
}
// 假设我们有三组数据需要做累加,其中三组数据中有 2 个数据是相同的,那么柯里化函数的复用参数的特性就发挥用处了。
// 数据组一: [2, 7, 18]
// 数据组一: [2, 7, 13]
// 数据组一: [2, 7, 56]
// 如下就复用了2 , 7两个参数
let addGoOn = curry(add)(2)(7);
res_1 = addGoOn(18);
res_2 = addGoOn(13);
res_3 = addGoOn(56);
console.log("======>>>", res_1); // => 27
console.log("======>>>", res_2); // => 22
console.log("======>>>", res_3); // => 65
好处二:提前返回
- 提前返回函数避免重复操作,提高效率。
- 看下边的经典案例↓↓↓
// 2. 提前返回
// 原函数
function addEvent(el,eventName,callBack,capture){
if(window.addEventListener){
el.addEventListener(eventName,callBack,capture);
}else if(window.attatchEvent) {
el.attatchEvent("on" + eventName, callBack)
}
}
// 函数柯里化
function curry() {
if(window.addEventListener){
return function() {
let [el, ...otherParams] = Array.from(arguments);
if(otherParams.length < 2) return console.error("缺少参数");
el.addEventListener(...otherParams);
}
}else if(window.attatchEvent){
return function () {
let [el, ...otherParams] = Array.from(arguments);
if(otherParams.length < 2) return console.error("缺少参数");
el.attatchEvent(...otherParams);
}
}
}
let commonAddEvent = curry();
commonAddEvent(document.getElementsByTagName("div")[0],"click",() => console.log('hello'));
好处三:延迟执行
- 在所有参数接收完毕后延迟统一执行
- 看下边的经典案例↓↓↓
// 3. 延迟执行
// 原函数
function add(a,b,c) {
return a + b + c;
}
/**
* @author: hyx
* @description: 函数柯里化过程
* @param {*} fn
* @param {*} passedParams
* @return {*}
* @Date: 2022-05-12 00:57:23
*/
function curry(fn,passedParams = []){
let needParamsNum = fn.length;
if(!Array.isArray(passedParams)) passedParams = [passedParams];
return function() {
// 实例化 passedParams
let _passedParams = passedParams.concat(Array.prototype.slice.call(arguments)); // 切莫在此处直接修改passedParams,否则函数颗粒化后会失去复用参数等特性。
if(_passedParams.length < needParamsNum) {
return curry(fn,_passedParams);
}else{
return function() {
return fn.apply(this, _passedParams);
}
}
}
}
let curryAdd = curry(add);
console.log("======>>>", curryAdd(1, 2,3)());
反柯里化
概述:
反柯里化其实就是柯里化的逆过程,目的就是扩大函数的适用范围。
经典用例:
- 数组的push方法只能用于数组,我们现在通过反柯里化提取push函数,让它也适用于Object对象。
// 反柯里化
function.prototype.uncurrying = function() {
let prototype = this;
return function() {
let obj = Array.prototype.shift.call(arguments);
prototype.apply(obj, arguments);
}
};
let myObj = {
name:'hyx',
age: 18
};
push = Array.prototype.push.uncurrying();
push(myObj, 'hello world');
console.log("======>>>", myObj); // {0:"hello world", name:"hyx",age: 18, length: 1}
写在最后:我发现其实还有很多原理性的东西没有讲出来,后续会慢慢更新这个柯里化的过程,忘大家多多指正共同进步,一起学习呀~如果有哪位小伙伴比较清楚欢迎一起讨论呀,尤其是在实际过程中的应用等。。。