柯里化

Heptagon

柯里化

高阶函数

在说明柯里化之前,首先需要理解高阶函数的定义

高阶函数是指以函数作为参数的函数,伪代码可以理解为

function higherOrderFunction(fn) {
    console.log(typeof fn) // "function"
    
}

定义

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

从定义中我们可以对柯里化的步骤做一个简要的概括:

  1. 存在一个函数currying,接受一个函数source作为参数,并返回一个函数tmpCurrying。
  2. tmpCurrying接收单一参数,并再次返回一个tmpCurrying,直到所有tmpCurrying接收的参数和等于source函数所需的形参数量。
  3. 将tmpCurrying收到的所有单一参数按顺序放入source函数,并执行,以获得结果。

实际应用

使用形式

根据如上定义,可以用如下伪码表示柯里化的使用

  1. 参数分步输入
// 实现参数分步输入
function sum(a,b,c) {
    return [...args].reduce((pre,next) => (pre + next));
}

// 存在一个函数currying

const curriedSum = currying(sum);

curriedSum(1)(2)(3); // 6;
curriedSum(1, 2)(3); // 6;
curriedSum(1, 2, 3); // 6;
  1. 函数抽象,高阶函数封装
// 用于函数抽象,高阶函数封装等
// 存在如下功能函数

function isPhone(number) {
    return /^1[34578]\d{9}$/.test(number);
}

function isMail(mail) {
    return /^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/.test(mail);
}

/*
    可以讲上面两个函数抽象为
    regString.test(targetString);
*/
function check(reg, target) {
    return reg.test(target);
}


/*
    但是每次使用时仍然需要输入正则作为参数,于是考虑利用柯里化的功能,将函数参数拆为两部分,正则 + 校验对象
    假设存在一个柯里化函数currying(fn, reg)
*/



export const checkPhone = currying(check, /^1[34578]\d{9}$/);

export const checkMail = currying(check, /^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/);


/* 
    checkPhone和checkMail此时皆是只需要一个参数targetString的函数
    使用时只需直接使用即可
*/

checkPhone(13111111111); // true;

柯里化实现

想要实现柯里化函数,需要掌握以下知识点

  1. 闭包:内层函数可以访问外层函数的变量
  2. Function.length: 函数的length属性表示其声明时的形参数量
  3. arguments: 类数组arguments表示函数调用时的实参列表(或直接使用参数解构,获取实参数组,推荐此种。原因:arguments只是类数组,没有数组方法,不方便使用,需要用结构或apply等方式将其转化为数组)

实现解析

  1. 利用闭包将每次单独输入的参数存入外层函数currying的数组变量args中。
  2. 校验当前args的长度与被封装函数的形参数量是否相等,不相等则继续返回接受参数的中间函数。
  3. 若相等,则将参数放入源函数并返回执行结果。

实现1---每次只接受一个参数

function currying(src) {
    // 记录源函数的形参长度
    const length = src.length;
    // 参数列表
    const argsPool = [];
    
    return function tmpFn (arg) {
        // 将参数推入参数池
        argsPool.push(arg);
        
        // 长度判断
        if (length > argsPool.length) {
            return tmpFn;
        } else {
            const res = src(...argsPool);
            argsPool = [];
            return res;
        }
    }
}


function sum(a, b, c, d, e, f) {
    return [...arguments].reduce((pre, next) => (pre + next));
}


const _sum = currying(sum);
_sum(1)(2)(3)(4)(5)(6); // 21

实现2:每次接受若干个参数

function currying(src, ...args) {
    // 记录源函数的形参长度
    const length = src.length;
    // 参数列表
    const argsPool = [...args];
    
    return function tmpFn (...args) {
        // 将参数推入参数池
        argsPool.push(...args);
        
        // 长度判断
        if (length > argsPool.length) {
            return tmpFn;
        } else {
            const res = src(...argsPool);
            argsPool = [];
            return res;
        }
    }
}


function sum(a, b, c, d, e, f) {
    return [...arguments].reduce((pre, next) => (pre + next));
}


const _sum = currying(sum);
_sum(1)(2)(3)(4)(5)(6); // 21
_sum(1, 2, 3, 4)(5, 6); // 21

实现3:不规定参数个数,以无参数传入为循环终止标识

function currying(src, ...args) {
    // 参数列表
    let argsPool = [...args];
    
    
    return function tmpFn (...args) {
        if (args.length > 0) {
            argsPool.push(...args);
            return tmpFn;
        } else {
            const res = src(...argsPool);
            argsPool = [];
            return res;
        }
    }
}


function sum(...args) {
    return args.reduce((pre, next) => (pre + next));
}


const _sum = currying(sum);

_sum(1,2,3)(4,5)(); // 15
_sum(1,2)();        // 3
阅读 1.3k
221 声望
5 粉丝
0 条评论
221 声望
5 粉丝
文章目录
宣传栏