[TOC]

函数柯里化

在计算机科学中,柯里化(英语:Currying),又译为卡瑞化或加里化,是把接受多个参数的函数转换成一系列使用一个参数的函数的技术。

柯里化是一种函数的转换,它是指将一个函数从可调用的 f(a, b, c) 转换为可调用的 f(a)(b)(c)

柯里化不会调用函数。它只是对函数进行转换。

  1. 柯里化突出一种重要思想:降低适用范围,提高适用性
  2. 柯里化的三个作用和特点:参数复用、提前返回、延迟执行
  3. 柯里化是闭包的一个典型应用,利用闭包形成了一个保存在内存中的作用域,把接收到的部分参数保存在这个作用域中,等待后续使用。并且返回一个新函数接收剩余参数

1. 代码实现

正常的的函数:

function sum(a, b, c) {
  return a + b + c;
}
console.log(sum(1,2,3)); // 6

柯里化实现:

function curry(f) { // 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; }
var curriedSum = curry(sum);
console.log(curriedSum(4)(5)(6)); // 15

高级柯里化实现:

function curry(func) {
  return function curried(...args) {
    if (args.length >= func.length) {
      return func.apply(this, args);
    } else {
      return function(...args2) {
        return curried.apply(this, args.concat(args2));
      }
    }
  };
}

// 使用
function sum(a, b, c) {
  return a + b + c;
}
let curriedSum = curry(sum);
console.log( curriedSum(1, 2, 3) ); // 6,仍然可以被正常调用
console.log( curriedSum(1)(2,3) ); // 6,对第一个参数的柯里化
console.log( curriedSum(1)(2)(3) ); // 6,全柯里化

2. 柯里化的作用

2.1 参数复用

有的时候同一个参数可能会反复使用(比如数组类型数据),这就造成了代码的重复,利用柯里化就能够消除重复,达到复用参数的目的。

柯里化的一种重要思想:降低适用范围,提高适用性

示例一:查询数组中是否存在某值

一个简单的函数用于查询数组中是否某个值,每次使用都需要这样调用:

const find = function (arr, value) {
  return arr.indexOf(value) !== -1;
};
find(arr, 1);
find(arr, 2);

既然 arr 是个固定参数,那么我们可以先保存一个接收过 arr 的函数,再用这个函数去处理变化的参数。

const collection = [5, 4, 3, 2, 1];
const findInCollection = currying(find)(collection);
findInCollection(1);
findInCollection(2);

示例一:通过正则校验电话号、邮箱、身份证是否合法等等

/**
 * @description 通过正则校验字符串
 * @param {RegExp} regExp 正则对象
 * @param {String} str 待校验字符串
 * @return {Boolean} 是否通过校验
 */
function checkByRegExp(regExp, str) {
    return regExp.test(str)
}
// 校验手机号
checkByRegExp(/^1\d{10}$/, '15152525634'); 
checkByRegExp(/^1\d{10}$/, '13456574566'); 
// 校验邮箱
checkByRegExp(/^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/, 'fsds@163.com'); 
checkByRegExp(/^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/, 'fdsf@qq.com'); 

貌似没什么问题,事实上还有改进的空间

  1. 校验同一类型的数据时,相同的正则我们写了很多次。
  2. 代码可读性较差,如果没有注释,我们并不能一下就看出来正则的作用

我们试着使用函数柯里化来改进:

// 将函数柯里化
function checkByRegExp(regExp) {
    return function(str) {
        return regExp.test(str)
    }
}
// 传入不同的正则对象,就可以得到功能不同的函数:
// 校验手机
const checkPhone = curryingCheckByRegExp(/^1\d{10}$/)
// 校验邮箱
const checkEmail = curryingCheckByRegExp(/^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/)

// 校验手机号
checkPhone('15152525634'); 
checkPhone('13456574566'); 
// 校验邮箱
checkEmail('fsds@163.com'); 
checkEmail('fdsf@qq.com'); 

通用函数(如checkByRegExp)解决了兼容性问题,但也会带来使用的不便,比如不同的应用场景需要传递多个不同的参数来解决问题

有的时候同一种规则可能会反复使用(比如校验手机的参数),这就造成了代码的重复,利用柯里化就能够消除重复,达到复用参数的目的。

2.2 提前返回(先执行部分功能)

在JS DOM事件监听程序中,我们用addEventListener方法为元素添加事件处理程序,但是部分浏览器版本不支持此方法,我们会使用attachEvent方法来替代。

attachEvent和addEventListener异同点

1.attachEvent是IE有的方法,它不遵循W3C标准,而其他的主流浏览器如FF等遵循W3C标准的浏览器都使用addEventListener,所以实际开发中需分开处理。

2.多次绑定后执行的顺序是不一样的,attachEvent是后绑定先执行,addEventListener是先绑定先执行。

这时我们会写一个兼容各浏览器版本的代码:

/**
 * @description: 
 * @param {object} element DOM元素对象
 * @param {string} type 事件类型
 * @param {Function} fn 事件处理函数
 * @param {boolean} isCapture 是否捕获
 * @return {void}
 */
function addEvent(element, type, fn, isCapture) {
    if (window.addEventListener) {
        element.addEventListener(type, fn, isCapture)
    } else if (window.attachEvent) {
        element.attachEvent("on" + type, fn)
    }
}

每次调用此方法时,都会进行一次判断,事实上浏览器版本确定下来后,没有必要进行重复判断。

柯里化处理:

function curryingAddEvent() {
    if (window.addEventListener) {
        return function(element, type, fn, isCapture) {
            element.addEventListener(type, fn, isCapture)
        }
    } else if (window.attachEvent) {
        return function(element, type, fn) {
            element.attachEvent("on" + type, fn)
        }
    }
}
const addEvent = curryingAddEvent()

现在我们得到的addEvent是经过判断后得到的函数,以后调用就不用重复判断了。

这就是提前返回或者说提前确认,函数柯里化后可以提前处理部分任务,返回一个函数处理其他任务

2.3 延迟执行

事实上,上述正则校验和事件监听的例子中已经体现了延迟执行

curryingCheckByRegExp函数调用后返回了checkPhonecheckEmail函数

curringAddEvent函数调用后返回了addEvent函数

返回的函数都不会立即执行,而是等待调用。

参考资料

什么是函数柯里化?能手写实现吗?

Last Updated: 5/15/2024, 8:31:26 PM