基于本文回答

播面 播面

文图音视,全方位拆解八股文
0
评论

什么是函数柯里化(Currying)?它有什么实际作用?

知识点图片

函数柯里化(Currying) 是函数式编程中的一个重要概念。它的名字来源于逻辑学家 Haskell Curry。

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

公式化表达就是:将 fn(a, b, c) 转换为 fn(a)(b)(c) 的过程。


一、直观的代码示例

普通的函数(接受多个参数):

javascript
function add(a, b, c) {
    return a + b + c;
}
console.log(add(1, 2, 3)); // 输出 6

柯里化后的函数:

javascript
function curriedAdd(a) {
    return function(b) {
        return function(c) {
            return a + b + c;
        };
    };
}
console.log(curriedAdd(1)(2)(3)); // 输出 6

注:ES6 箭头函数写法可以极其精简:const curriedAdd = a => b => c => a + b + c;


二、柯里化有什么实际作用?

柯里化并不是为了炫技,它在实际开发(尤其是复杂项目和函数式编程中)有三大核心作用:

1. 参数复用(固定易变因素)

当你发现调用某个函数时,经常传入相同的参数,柯里化就可以帮你“记住”这些参数,生成一个更具体的函数。

场景:表单正则校验

javascript
// 【普通写法】每次都要传正则和字符串
function check(reg, txt) {
    return reg.test(txt);
}
check(/\d+/g, 'test');       // 校验是否含数字
check(/[a-z]+/g, 'test');    // 校验是否含字母

// 【柯里化写法】
function curriedCheck(reg) {
    return function(txt) {
        return reg.test(txt);
    }
}

// 提前绑定正则表达式,生成具有特定功能的函数
const hasNumber = curriedCheck(/\d+/g);
const hasLetter = curriedCheck(/[a-z]+/g);

// 复用时极其清爽
hasNumber('test1'); // true
hasNumber('test');  // false
hasLetter('213');   // false

2. 延迟执行

柯里化的特性是:只要参数没传够,它就不会真正执行计算,而是继续返回一个接收剩余参数的函数。这非常适合需要分步收集参数的场景。

场景:React/Vue 中的事件绑定
在前端框架中,我们经常需要在点击时传递一个特定的 ID,但 onClick 期望接收的是一个函数引用,而不是执行结果。

jsx
// 假设有一个删除函数
const handleDelete = id => event => {
    console.log("正在删除ID:", id);
    console.log("触发的事件对象:", event);
    // 实际删除逻辑...
};

// 在组件中使用:先把 id 传进去(第一层函数执行,此时没有真正删除)
// 返回一个接收 event 的函数给 onClick 绑定
<button onClick={handleDelete(1001)}>删除文章</button>

3. 配合函数组合(Compose)实现流水线处理

在纯函数式编程(如 Redux、Ramda、Lodash/fp)中,我们希望把大任务拆分成很多单参数的小函数,然后把它们“拼”起来。因为组合函数(Compose)通常要求链上的每个函数只接受一个参数,柯里化就成了连接这些函数的桥梁。

javascript
// 假设有三个处理字符串的单参数函数
const trim = str => str.trim();
const toLowerCase = str => str.toLowerCase();
// split 原本是多个参数的,通过柯里化变成单参数
const split = separator => str => str.split(separator); 

// 把它们组合起来组成一个数据处理流水线
const processText = compose(
    split(' '),
    toLowerCase,
    trim
);

// processText('  HELLO WORLD  ') 会依次执行 trim -> toLowerCase -> split(' ')
// 最终输出: ['hello', 'world']

三、底层原理

柯里化的底层基石是 闭包(Closure)
当第一层函数执行并返回第二层函数时,虽然第一层函数的执行上下文结束了,但第二层函数依然保留着对第一层函数参数(如前面例子中的 areg)的引用,这就使得参数被“记住”了。


四、高级扩展:实现一个通用的 curry 转换函数

在实际开发中,我们不会手动写一层层的 return function,而是写一个工具函数,把任何普通函数变成支持柯里化的函数。这也是前端面试常考题:

javascript
function curry(fn) {
    // 获取原函数需要的参数总个数
    const len = fn.length; 
    
    return function curried(...args) {
        // 如果当前传入的参数个数 大于等于 原函数需要的个数,直接执行原函数
        if (args.length >= len) {
            return fn.apply(this, args);
        } else {
            // 否则,返回一个新的函数,继续收集参数
            return function(...args2) {
                // 将之前收集的参数和这次新传的参数拼接起来,递归调用
                return curried.apply(this, args.concat(args2));
            }
        }
    };
}

// 测试:
function sum(a, b, c) {
    return a + b + c;
}

const curriedSum = curry(sum);

console.log(curriedSum(1, 2, 3)); // 6 (普通调用也可以)
console.log(curriedSum(1)(2)(3)); // 6 (完全柯里化)
console.log(curriedSum(1, 2)(3)); // 6 (部分柯里化)

总结

函数柯里化是一种 “预加载”函数能力 的技术。它通过降低函数的通用性,提高了函数的适用性。它的核心作用是:降低代码重复、提高代码复用率、便于函数组合以及实现延迟计算。

00:00
00:00