柯里化入门:拆拆拆,拆出函数式编程的优雅
💡什么是柯里化?
想象你在做饭,需要盐、酱油、葱花这些配料。普通函数就像一次性把所有材料都丢进锅里;而柯里化更像“先放盐,尝一口;再放酱油,尝一口;最后放葱花”。它把原本需要多个参数的函数,拆解成一个接一个的单参数函数,每次只处理一个参数,直到所有材料(参数)都齐了,才开始正式“开煮”。
👨🔬柯里化的起源
柯里化这个名字,来自逻辑学家 Haskell Curry,他的研究让函数式编程有了坚实的理论地基。现在,在像 Haskell、Scala 这样的函数式编程语言里,柯里化几乎是日常操作;在 JavaScript 中,它同样被用来写出更优雅、可复用、可组合的代码。
🚀为什么要学柯里化?
参数复用:先给函数“喂”一部分参数,返回的新函数可以在需要时继续使用。
延迟执行:函数可以只配置参数,不立即执行,直到真正需要的时候才调用。
函数组合:把多个小函数像乐高一样组合起来,形成强大的功能模块。
💻实际应用场景
无论是优化 Redux 的 Selector,处理复杂数据流,还是对深度嵌套函数进行优雅封装,柯里化都能让代码变得更灵活、更清晰、更优雅。
🍔 例子:用柯里化制作汉堡
假设你在做汉堡,需要选择面包、肉饼、酱料三个部分:
function makeBurger(bread, patty, sauce) {return `你的汉堡:${bread} + ${patty} + ${sauce}`;
}
如果使用柯里化:
function curry(fn) {return function curried(...args) {if (args.length >= fn.length) {return fn.apply(this, args);} else {return function(...nextArgs) {return curried.apply(this, args.concat(nextArgs));};}};
}const curriedMakeBurger = curry(makeBurger);console.log(curriedMakeBurger('芝麻面包')('牛肉饼')('番茄酱'));
// 输出:你的汉堡:芝麻面包 + 牛肉饼 + 番茄酱
🍟 更有趣的调用方式
你甚至可以先选好面包,后面再慢慢决定其他配料:
const withBread = curriedMakeBurger('全麦面包');
const withPatty = withBread('鸡肉饼');
console.log(withPatty('黑椒酱'));
// 输出:你的汉堡:全麦面包 + 鸡肉饼 + 黑椒酱
🔧 为什么这很酷?
参数固定:你可以先固定常用参数(例如固定成“全麦面包+牛肉饼”的健康汉堡)。
分步配置:根据用户选择一步步生成,常用于表单、多步骤配置页面。
可组合:结合其他函数式工具,如 compose、pipe,构建强大的数据处理流。
👨🔧 1. 从零实现柯里化
让我们写一个万能 curry 函数,把任何多参数函数,变成可以逐步调用的“厨师函数”。
function curry(fn) {return function curried(...args) {if (args.length >= fn.length) {// 参数够了,就开饭return fn.apply(this, args);} else {// 参数不够,再返回一个函数接着收return function(...args2) {return curried.apply(this, args.concat(args2));};}};
}
🔬 2. 它是怎么工作的?
curry 接收目标函数(比如做饭函数)
curried 函数检测参数数量:够就执行,不够就继续收集
递归调用:不停返回新函数,直到收齐全部参数
执行目标函数,返回结果
💻 3. 测试:乘法示例
function multiply(a, b, c) {return a * b * c;
}const curriedMultiply = curry(multiply);console.log(curriedMultiply(2)(3)(4)); // 24
console.log(curriedMultiply(2, 3)(4)); // 24
console.log(curriedMultiply(2)(3, 4)); // 24
🎉 多灵活! 你可以一步步传,也可以分批传,最后总会算出结果。
🌈 4. 柯里化的优点
✅ (1) 参数复用
先固定一部分参数,像提前腌好的鸡肉,随时可用。
function greet(greeting, name) {return `${greeting}, ${name}!`;
}const sayHello = curry(greet)("Hello");
console.log(sayHello("Alice")); // Hello, Alice!
console.log(sayHello("Bob")); // Hello, Bob!
✅ (2) 延迟执行
柯里化可以“先接收参数,后执行”,就像提前预约:
const add = curry((a, b) => a + b);
const addFive = add(5);
console.log(addFive(10)); // 15
✅ (3) 函数组合
把多个函数组合成数据处理管道,逻辑清晰,读起来就像在看流水线。
const compose = (f, g) => (x) => f(g(x));
const addOne = (x) => x + 1;
const double = (x) => x * 2;const addOneThenDouble = compose(double, addOne);
console.log(addOneThenDouble(3)); // 8
💡 5. 为什么柯里化这么强大?
它的核心理念就是:
一步做一件事,函数变得简单、纯粹、好测试。
没有副作用,只依赖参数,不影响全局,代码更安全可靠。
🚀 6. 实际应用场景
🛒 (1) Redux Selector 优化
在 Redux 的 reselect
中,柯里化用于高性能 Selector。
const selectCart = (state) => state.cart;
const selectCartItems = createSelector(selectCart,(cart) => cart.items
);const selectCartTotal = createSelector(selectCartItems,(items) => items.reduce((total, item) => total + item.price * item.quantity, 0)
);console.log(`Cart Total: $${selectCartTotal(state)}`);
🌟 好处:避免重复计算,让状态派生更快。
💬 (2) 数据管道:敏感词过滤 + 格式化 + 统计字数
const filterSensitiveWords = curry((words, text) => words.reduce((acc, word) => acc.replace(new RegExp(word, 'gi'), '***'), text)
);
const formatText = (text) => text.trim().toLowerCase();
const countWords = (text) => text.split(/\s+/).length;const compose = (...fns) => (x) => fns.reduceRight((v, f) => f(v), x);const processTextPipeline = compose(countWords,formatText,filterSensitiveWords(['badword', 'offensive'])
);const result = processTextPipeline(" This is a BadWord example! Badword is offensive. ");
console.log(result); // 输出处理后的字数
🔗 (3) 深度嵌套函数优化:带 Token 的 fetch
const fetchWithAuth = curry((authToken, endpoint) => fetch(endpoint, { headers: { Authorization: `Bearer ${authToken}` } })
);const fetchUserData = fetchWithAuth('my-secure-token');Promise.all([fetchUserData('/api/user/1'),fetchUserData('/api/user/2')
]).then(([user1, user2]) => Promise.all([user1.json(), user2.json()])).then(console.log).catch(console.error);