阅读 1391

看完这个,你还不懂函数柯里化?

函数柯里化(Currying), 是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。

简单来说,就是固定一些参数,返回一个接受剩余参数的函数。实质就是使用闭包返回一个延迟执行函数

其好处主要在于:

  1. 参数复用,或者说是固定参数,避免重复传参;
  2. 提前返回,或者说是提前确认,避免重复判断;
  3. 延迟执行。

函数柯里化的使用原理及方式

从简单例子出发:

实现一个函数能够使 add(1, 2) => 3 转变为 add(1)(2) => 3

function add(num1, num2) {
    return num1 + num2;
}
复制代码

分析: 如果要实现add(num1, num2)到add(num1)(num2)的转变,我们首先要做的就是将add的return改造成一个闭包函数,这样才能实现连续执行且在执行第二个函数的时候,访问到num1参数。

function curriedAdd(num1) {
    return function(num2) {
        return num1 + num2;  // 实际上这就是add(num1, num2);
    }
}
复制代码

也就是说add的curry函数就是

function curry(fn, num1) {
    return function(num2) {
        return fn(num1, num2)
    }
}

const curriedAdd = curry(add, 1); 
curriedAdd(2);  
复制代码

将参数抽象一下

function curry(fn) {
    const curryArgs = Array.from(arguments).slice(1);  // 获取curry函数的参数,也就是上例中的num1,slice(1)是排除掉fn
    return function() {
        const callbackArgs = Array.from(arguments);    // 获取回调函数的参数,也就是上例中的num2
        return fn(...callbackArgs, ...curryArgs);
    }
}  
复制代码

了解原理之后,我们简化一版

function curry(fn, ...curryArgs) {
    return (...callbackArgs) => {
        return fn(...callbackArgs, ...curryArgs);
    }
}
复制代码

拓展一下,怎样实现add(num1)(num2)(num3)(num4)...、add(num1, num2)(num3)(num4)、add(num1)(num2)(num3, num4)()呢?

function add(num1, num2, num3, num4) {
    return num1 + num2 + num3 + num4;
}
复制代码

分析:不断递归获取传入参数,直到取到的参数个数等于fn的参数个数为止,最终将获取到的所有参数传给fn并返回执行结果。动手吧!

function curry(fn) {
    // 注意一下,如果设置add函数num1、num2、num3、num4 等的初始值,则将会对通过function.length获取function的形参数造成影响
    const curryLength = fn.length;  // fn.length是为了获取函数的形参个数,用以判断递归是否结束
    const curryArgs = Array.from(arguments).slice(1);
    
    return function() {
        const callbackArgs = Array.from(arguments);
        const currentArgs = [...curryArgs, ...callbackArgs];  // 注意,这里代表了callback函数执行的所有参数
        if (callbackArgs.length === 0 || currentArgs.length === curryLength) { // 执行到最后一步,或者参数为空时
            return fn(...currentArgs);
        } else {
           return curry(fn, ...currentArgs);
        }
    }
}
复制代码

简化一下

function curry(fn, ...curryArgs) {
    return (...callbackArgs) => {
        const currentArgs = [...curryArgs, ...callbackArgs];
        return fn.length === currentArgs.length || callbackArgs.length === 0 ? fn(...currentArgs) : curry(fn, ...currentArgs);
    }
}
复制代码

这样我们就可以使用

curry(add)(1)(2)(3)(4);  =>  10
复制代码

函数柯里化的好处

【复用参数】

比如说我们用正则验证一个手机号。

function curry(fn, ...args) {
    return (...callbackArgs) => {
        const currentArgs = [...args, ...callbackArgs];
        return callbackArgs.length === 0 || currentArgs.length === fn.length ? fn(...currentArgs) : curry(fn, ...currentArgs);
    }
}

const phoneReg = /^1[3-9]\d{9}$/;

function _checkPhone(reg, phone) {
    return reg.test(phone);
}

console.log(_checkPhone(phoneReg, 19956526362));

// 柯里化
const checkPhone = curry(_checkPhone)(phoneReg);  // 这样我们就复用了验证手机的正则,这就是复用参数,或者说是固定参数
checkPhone(19956526362);
checkPhone(16956526362);
复制代码

【提前返回、延迟执行】

再做一个拓展,我们需要对一个正确的手机号做一系列不同步的操作(同步的话就没有意义了)

function doSomething1(reg, phone, callback) {
    reg.test(phone) && callback();
}

function doSomething2(reg, phone, callback) {
    reg.test(phone) && callback();
}

doSomething1(phoneReg, 19956526362,callback1);
doSomething2(phoneReg, 19956526362,callback2);
// 既然是对同一个号码做判断,我们当然可以先将判断结果保存下来,这样就不用每次都做判断了
function _doSomething(reg, phone, callback) {
    reg.test(phone) && callback();
}

const doSomething = curry(_doSomething)(19956526362);  // 这里就是提前返回电话号码是否正确了
doSomething(callback1);                                // 这里就是延迟执行
doSomething(callback2);
复制代码
文章分类
前端
文章标签