写多参数函数总重复传值?用柯里化3步搞定参数复用与延迟执行

在计算机科学中柯里化(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);//不够长则继续柯里化
    }
  };
}

三、总结

柯里化 提高了代码的复用性 (参数复用),增强了灵活性 (延迟执行),适合处理多场景下的使用(接口请求),具有以下特点:

  1. 分阶段传参:允许将参数拆分到多次调用中传递,而非一次性传完
  2. 延迟执行:只有当收集到足够参数时,才执行原函数
  3. 参数复用:固定部分通用参数,生成可复用的专用函数
  4. 闭包保存(代码实现中的特点)

前辈关于柯里化的文章:

函数式编程--柯理化(Currying) - 知乎

柯里化函数(Currying),什么是柯里化,为什么要进行柯里化,高级柯里化函数的实现-CSDN博客

相关推荐
我叫黑大帅3 小时前
面对组件的不听话,我还是用了它…………
前端·javascript·vue.js
尔嵘3 小时前
vue2+elementUi实现自定义表格框选复制粘贴
前端·javascript·elementui
chéng ௹4 小时前
Vue3+Ts+Element Plus 权限菜单控制节点
前端·javascript·vue.js·typescript
携欢4 小时前
PortSwigger靶场之Exploiting server-side parameter pollution in a REST URL通关秘籍
前端·javascript·安全
软件技术NINI5 小时前
html css js网页制作成品——HTML+CSS+js早餐铺网页设计(4页)附源码
javascript·css·html
艾小码5 小时前
前端必备:JS数组与对象完全指南,看完秒变数据处理高手!
前端·javascript
weixin-a153003083166 小时前
vue疑难解答
前端·javascript·vue.js
大怪v13 小时前
【搞发🌸活】不信书上那套理论!亲测Javascript能卡浏览器Reader一辈子~
javascript·html·浏览器
西陵13 小时前
Nx带来极致的前端开发体验——任务缓存
前端·javascript·架构