在计算机科学中 ,柯里化(Currying)是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果的新函数的技术。这个技术由 Christopher Strachey 以逻辑学家 Haskell Curry 命名的,尽管它是 Moses Schnfinkel 和 Gottlob Frege 发明的。
引用于------柯里化_百度百科
柯里化函数着重于理解,笔者将带读者简单实现一份柯里化函数,一起学习^_^
你有没有过这样的开发经历?写费用计算函数时,每次调用都要重复传运费、打包费这些固定参数;写接口请求时,基础URL和token明明通用,却还要在每个请求里再写一遍------其实这些「重复传参」的麻烦,用柯里化就能轻松解决。
一、理解柯里化
1.1 什么是柯里化
柯里化是将一个接受多个参数 的函数,变换成一个可以接受单一参数的函数。
例如:fn(a,b,c) => fn(a)(b)(c) || fn(a,b,c) => fn(a,b)(c)
如果例子里看懂了,说明你的理解能力不错,可以略过下方的一个样例。
笔者将会写一份简单的原函数(add )和一份柯里化后的函数(curriedAdd),帮助读者理解柯里化的思维。
- 样例原函数:
js
function add(a, b, c) {
//可以看到这里是接受三个参数的
return a + b + c;//结果为(a+b+c)
}
console.log(add(1, 2, 3)); // 输出6
- 柯里化原函数 fn(a,b,c) => fn(a)(b)(c):
js
function curriedAdd(a) {
return function (b) {
return function (c) {
return a + b + c;
//注意这里的结果依旧不变为(a+b+c),但嵌套成三个函数
};
};
}
const add1 = curriedAdd(1);//第二层函数
const add1And2 = add1(2);//第三层函数
const result = add1And2(3);//调用第三层函数
console.log(result); // 输出6
相信屏幕前聪明的读者,已经在大脑里构建出了一个柯里化的雏形,笔者就不再在这赘述fn(a,b,c) => fn(a,b)(c)
1.2 柯里化的运用场景
笔者将会描述多个场景帮助读者理解柯里化的作用和意义
参数复用
场景一:在点单界面,不同的商品根据不同的类型会有不同的附加费用。
两个参数 :经常点外卖的读者知道,外卖中盒装餐品需要 1 元的打包费,而一些袋装的商品不需要打包费,也就是打包费 0 元,费用的计算统一使用一个函数,函数为 getCost({商品费},{打包费}),现在多份商品的打包费相同 ,每次计算费用调用该函数时仍然要传入两个参数,这会带来开发者的心智负担,现在两个参数这个效果不明显。
多个参数 :现在费用增加到:运费、纳税、关税、红包、代金券、打包费、商品费 7 种费用,实际购物的时候只使用到了{打包费}和{商品费},但是仍然要传入7个参数来计算我们的费用,这将十分的繁琐,带来大量的心智负担
JS代码构建出上述场景
js
// 定义一个特殊的占位符符号
const _ = Symbol('placeholder');
// 支持占位符的柯里化函数
function curry(fn, ...args)
/
// 应用场景:多参数费用计算
function calculateTotal(
productCost, // 商品费
packagingFee, // 打包费
shippingFee, // 运费
tax, // 纳税
tariff, // 关税
redEnvelope, // 红包
coupon // 代金券
) {
return productCost + packagingFee + shippingFee + tax + tariff - redEnvelope - coupon;
}
// 1. 柯里化费用计算函数
const curriedCalculate = curry(calculateTotal);
// 2. 第一步:用占位符固定后5个参数(前2个参数留空)
const fixedCommonCosts = curriedCalculate(_, _, 5, 0, 0, 0, 0);
// 此时参数为:[_, _, 5, 0, 0, 0, 0](含占位符,不执行原函数)
// 3. 第二步:传入前2个参数(替换占位符)
const boxedMeal = fixedCommonCosts(35, 1);
console.log(`盒装餐品总价:${boxedMeal}元`); // 35+1+5=41元
const baggedGoods = fixedCommonCosts(20, 0);
console.log(`袋装商品总价:${baggedGoods}元`); // 20+0+5=25元
// 4. 支持部分替换和多轮替换
const partialReplace = curriedCalculate(_, 1, 5, _, 0, 0, 0); // 部分占位
const withTax = partialReplace(35, 2); // 替换剩余占位符
console.log(`含税费总价:${withTax}元`); // 35+1+5+2=43元
// 5. 支持超额参数(自动忽略多余部分)
const extraArgs = fixedCommonCosts(100, 1, '这是多余的参数');
console.log(`超额参数处理:${extraArgs}元`); // 100+1+5=106元
延迟执行
柯里化的延迟执行特性非常适合处理 "需要分阶段收集参数" 的场景。
接口请求 : 我们可以先固定请求的通用参数(如基础 URL、token),后续再传入动态参数(如用户 ID、订单编号 NO),既保证了代码复用,又能灵活应对不同请求场景。
js
function request(baseUrl, headers, endpoint, data) {
console.log(`请求: ${baseUrl}${endpoint}`, { headers, data });
// 模拟接口调用
return Promise.resolve({ code: 200, data });
}
const curry = (fn, ...args)//柯里化函数
const withBase = curry(request)('https://api.example.com', { token: 'xxx' });
const getUser = withBase('/user'); // 用户接口
const getOrder = withBase('/order'); // 订单接口
getUser({ id: 123 }).then(res => console.log('用户结果:', res));
getOrder({ no: 'OD123' }).then(res => console.log('订单结果:', res));
// 请求: https://api.example.com/user { headers: { token: 'xxx' }, data: { id: 123 } }
// 请求: https://api.example.com/order { headers: { token: 'xxx' }, data: { no: 'OD123' } }
// 用户结果: { code: 200, data: { id: 123 } }
// 订单结果: { code: 200, data: { no: 'OD123' } }
二、实现柯里化函数
相信读者已经理解了柯里化,笔者将会简单实现一份柯里化函数
Function:length - JavaScript | MDN
笔者实现了一份简易的柯里化函数,实现带占位符的柯里化函数篇幅较长,本文主要着重于理解柯里化函数。
js
// 通用柯里化函数实现
function curry(fn, ...args) {
const requiredArgs = fn.length;//获取函数fn需要的参数长度 忘记了上方给出了MDN链接
//提示:这里闭包保存了函数 fn
return function(...newArgs) {
//返回一个柯里化后的函数
const allArgs = [...args, ...newArgs];//将新获取的参数添加到 旧参数数组中
if (allArgs.length >= requiredArgs) {
//所有的参数都收集完后直接调用原函数
return fn.apply(this, allArgs);
} else {
return curry.call(this, fn, ...allArgs);//不够长则继续柯里化
}
};
}
三、总结
柯里化 提高了代码的复用性 (参数复用),增强了灵活性 (延迟执行),适合处理多场景下的使用(接口请求),具有以下特点:
- 分阶段传参:允许将参数拆分到多次调用中传递,而非一次性传完
- 延迟执行:只有当收集到足够参数时,才执行原函数
- 参数复用:固定部分通用参数,生成可复用的专用函数
- 闭包保存(代码实现中的特点)
前辈关于柯里化的文章: