浅谈认识和了解JS函数柯里化和反柯里化

214 阅读3分钟

在计算机科学中,柯里化(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. 参数复用
  2. 提前返回
  3. 延迟执行

好处一:参数复用

  • 对于一些有很多参数相同的函数调用情况,柯里化就非常适用了。
    • 看下边例子↓↓↓
// 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}

写在最后:我发现其实还有很多原理性的东西没有讲出来,后续会慢慢更新这个柯里化的过程,忘大家多多指正共同进步,一起学习呀~如果有哪位小伙伴比较清楚欢迎一起讨论呀,尤其是在实际过程中的应用等。。。