函数柯理化:面试官的 “心头好”,今天咱给它扒得明明白白 😎
柯理化?听着就像 “玄学”!
你听说过柯理化吗?🧐
有一说一,柯理化在现实项目里确实不算 “常客”。毕竟正常开发时,咱调用函数都是 “一口气” 把参数传完,比如add(1,2,3)多痛快,谁会闲得慌拆成add(1)(2)(3)?但架不住面试官他老人家爱考啊!简历上写 “熟悉 JavaScript”?行,先手写个柯理化函数看看。所以今天,咱就撸起袖子,把这 “面试高频考点” 撕得明明白白!
一、什么是柯理化?看完你就懂了 🤔
柯理化(Currying)听着洋气,其实本质特简单。咱先看官方点的解释:它是函数式编程的重要思想,核心是把接收多个参数的函数,拆成一系列只接收部分参数的函数。这些函数依次 “接力” 接收参数,直到参数凑够了,再执行原函数。
举个生活化的例子:就像点外卖,你不用一次性告诉商家 “要汉堡 + 可乐 + 薯条”,可以先告诉它 “要汉堡”,过会儿再补 “加可乐”,最后说 “再来份薯条”,凑齐了商家就给你做 —— 这就是柯理化的思路!
它还有几个关键特点,记好了,面试官可能直接问:
- 本质是闭包:每一层函数都像个 “小仓库”,用闭包把收到的参数存起来(这些参数叫 “自由变量”),不会被销毁,等着后续函数来用。
- 参数分步传:可以一次传 1 个,也能一次传 3 个,分几次传完都行,灵活得很。
- 执行看参数够不够:原函数需要多少参数(用
fn.length能拿到),柯理化函数就一直收参数,收够了就 “触发” 执行。 - 实现靠俩兄弟:闭包(负责存参数)+ 递归(负责重复收参数的过程)。
二、将函数柯里化:从简单加法开始动手 ✍️
光说不练假把式,咱从最基础的 “两数相加” 开始,一步步把普通函数改成柯理化函数。
1. 先看个 “正常” 的加法函数
咱先写个最普通的两数相加函数,长这样:
javascript
function add(a, b) {
return a + b;
}
console.log(add(2, 3)); // 输出:5
这代码简单到不用解释 —— 调用add时,一次性传入2和3,直接返回结果5。但这不符合柯理化 “分步传参” 的气质,得改!
2. 柯理化改造第一版:保留外部add函数
咱先给add做个 “柯理化包装”,让它能分步收参数。代码长这样:
javascript
// 原加法函数不变
function add(a, b) {
return a + b;
}
// 柯理化处理:把多参数函数拆成单参数函数
function curryAdd(a) {
// 内层函数通过闭包“记住”a的值
return function(b) {
// 当b传入后,参数够了,调用原函数计算
return add(a, b);
}
}
代码详解:闭包是 “记忆大师” 🧠
curryAdd其实就是对add的 “柯理化改造器”。它做了两件关键事:
- 第一步:
curryAdd(a)先接收第一个参数a,然后返回一个内层函数。这时候a并没有消失,而是被内层函数通过 “闭包” 牢牢记住了(就像给a拍了张照片存在内存里)。 - 第二步:当调用返回的内层函数时,传入第二个参数
b。此时参数数量(2 个)和原函数add的参数数量(add.length是 2)对上了,于是调用add(a, b)算出结果。
所以调用方式就变成了:curryAdd(1)(2)。先传1得到 “记住 1 的函数”,再传2,最后算出3—— 是不是有点柯理化那味儿了?
3. 柯理化改造第二版:直接在柯理化函数里实现逻辑
刚才的版本还依赖外部的add,咱再简化一下,把计算逻辑直接放进柯理化函数里:
javascript
function add(a) {
// 外层函数接收a,返回内层函数
return function(b) {
// 内层函数用闭包拿到a,和b相加
return a + b;
}
}
// 调用方式:分步传参
console.log(add(1)(2)); // 输出:3
代码详解:两次调用的 “接力赛” 🏃
这个add函数已经不是原来的 “急性子” 了,变成了 “慢性子”:
- 外层函数
add(a):只做一件事 —— 接收第一个数a(比如1),然后把a“记在心里”(闭包的功劳),再返回一个 “等待第二个数” 的内层函数。 - 内层匿名函数
function(b):收到第二个数b(比如2)后,掏出之前 “记着” 的a,和b相加,返回结果3。
咱把add(1)(2)拆成两步看更清楚:第一步:add(1) → 调用外层函数,a=1,返回内层函数(此时内层函数已经 “知道”a=1了)。第二步:(2) → 调用第一步返回的内层函数,b=2,计算1+2=3并返回。
是不是很像接力赛?第一棒传1,第二棒传2,两棒跑完才出结果~
三、手写通用柯理化函数:一招鲜吃遍天 ✨
刚才的例子只能处理 2 个参数,但实际开发中函数参数可能是 3 个、4 个,总不能每个函数都手动拆吧?咱得搞个 “通用柯理化工具”,不管原函数有多少参数,都能自动柯理化(参考 3.js)。
1. 通用柯理化函数代码
废话不多说,直接上代码:
javascript
// 通用柯理化函数:接收原函数,返回柯理化后的函数
function curry(fn) {
// 定义一个闭包函数curried,用于收集参数
return function curried(...args) {
// 情况1:收集的参数数量 >= 原函数需要的参数数量 → 执行原函数
if (args.length >= fn.length) {
return fn(...args); // 用扩展运算符把参数传给原函数
} else {
// 情况2:参数不够 → 返回新函数,继续收集参数
return (...rest) => curried(...args, ...rest); // 合并已有参数和新参数,递归调用
}
}
}
看一下通用柯理化函数的效果:
2. 看懂代码:参数够了就 “发车” 🚗
这个curry函数是核心,咱一步步拆解它的执行逻辑:
核心逻辑:
- 闭包存参数:
curried函数通过闭包保存每次收集到的args(已有参数),不会被销毁。 - 递归收参数:只要参数不够,就返回新函数继续收,直到参数够了为止。
- 终止条件:
args.length >= fn.length(fn.length是原函数的参数数量),此时执行原函数。
思考curriedAdd(1)(2)(3)(4) 是怎么跑的?
- 第一次调用
curriedAdd(1):args = [1],fn.length是 4(add需要 4 个参数),1 < 4→ 返回新函数,等待更多参数。 - 第二次调用
(2):新函数接收[2],合并已有参数变成[1,2],递归调用curried([1,2])→ 还是不够,继续返回新函数。 - 第三次调用
(3):合并参数变成[1,2,3]→ 还是不够,返回新函数。 - 第四次调用
(4):合并参数变成[1,2,3,4]→4 >= 4,满足条件!调用add(1,2,3,4),返回结果10。
灵活传参:一次传多个也能行!
柯理化不是非得 “一次传一个”,一次传多个也支持。比如:
因为代码里用了...args和...rest收集参数,不管一次传几个,都会合并到args里,直到凑够数~
四、柯理化实际运用:日志系统秒变 “灵活工” 📝
光说理论太空泛,咱看个实际场景:写日志系统。日志分不同类型(ERROR、INFO、WARN),每次打印都要带类型,用柯理化能省不少事。
1. 不使用柯理化:重复劳动太麻烦 😫
先看不用柯理化的写法:
javascript
// 普通日志函数:每次都要传类型
function log(type, message) {
console.log(`[${type}] ${message}`);
}
// 每次调用都得写类型,重复!
log('ERROR', '数据库连接失败');
log('ERROR', '用户名不能为空');
log('INFO', '用户登录成功');
log('INFO', '数据同步完成');
缺陷:每次打印日志都要重复传'ERROR'或'INFO',手敲多了容易错,还显得冗余。
2. 使用柯理化:一次定义,多次复用 🚀
用柯理化改造后:
javascript
// 柯理化日志函数:先固定类型,再传消息
const log = type => message => {
console.log(`[${type}] ${message}`);
};
// 提前固定日志类型,生成专用日志函数
const errorLog = log('ERROR'); // 专门打错误日志
const infoLog = log('INFO'); // 专门打信息日志
// 调用时不用再传类型,清爽!
errorLog('数据库连接失败');
errorLog('用户名不能为空');
infoLog('用户登录成功');
infoLog('数据同步完成');
代码详解:“预制模板” 思维 📌
log是个柯理化函数:
- 第一步:
log('ERROR')先固定type为'ERROR',返回一个只需要message的函数(errorLog)。 - 第二步:调用
errorLog('数据库连接失败')时,直接用之前固定的type,拼接消息打印。
优点:通过柯理化 “预制” 了不同类型的日志函数,减少重复参数,调用更简洁,还能避免传错类型(比如把ERROR写成ERORR)。
五、面试官会问:这些坑你得填上 🚨
柯理化是面试高频考点,这些问题提前备好答案:
- 柯理化的本质是什么? 答:本质是利用闭包保存参数,通过递归分步收集参数,直到参数数量满足原函数需求后执行。
fn.length的作用是什么? 答:fn.length能获取原函数的参数数量(形参个数),是柯理化判断 “参数是否够了” 的依据。- 柯理化和普通函数相比,优势在哪? 答:参数分步传递更灵活(比如固定部分参数复用)、代码更简洁(减少重复传参)、符合函数式编程思想。
六、结语:柯理化,学的是思想不是炫技 🎯
看到这,你可能会说:“柯理化看着挺厉害,但平时开发真用得上吗?” 确实,日常业务中直接写柯理化的场景不多,但它背后的思想 ——闭包保存状态、递归处理重复逻辑—— 是 JavaScript 的核心考点。
掌握柯理化,不仅能应付面试官的 “灵魂拷问”,更能帮你理解函数式编程的精髓。就像练武功,柯理化可能不是最常用的招式,但它能帮你打通 “闭包” 和 “递归” 的任督二脉~ 😉