前言
本文将详细介绍JS中的一个关于函数的重难点--柯里化。
什么是柯里化
柯里化(Currying)是一种将多参数函数转换为一系列单参数函数的技术,名称来源于数学家 Haskell Curry。在数学领域,柯里化用于将多元函数转换为一元函数的链式调用,这种转换保持了函数的可组合性和数学性质。
在编程中,柯里化允许我们分步传递参数,当所有参数都传递完毕后,函数才会真正执行。这种技术使得函数更加灵活,便于复用和组合。
柯里化的实现过程
在该部分,我们先引入rest语法,为后面的讲解进行铺垫,然后介绍柯里化的具体实现过程,以及详细分析该过程中的递归调用。
rest 语法... 展开数组
在 ES6 中,rest 参数语法(...
)允许我们将不定数量的参数表示为一个数组。这对于柯里化实现非常重要:
展开参数:
js
function sum(...numbers) {
return numbers.reduce((acc, curr) => acc + curr, 0);
}
console.log(sum(1, 2, 3, 4)); // 10
展开数组:
js
const numbers = [1, 2, 3];
console.log(sum(...numbers)); // 6
如何柯里化--递归
下面是一个基本的柯里化函数实现:
让我们从一个简单的加法函数开始:
js
function add(a, b, c) {
return a + b + c;
}
console.log(add(1, 2, 3)); // 6
柯里化:
js
function curry(fn) {
// fn 是被柯里化的原始函数 例如我们待会要传add
// 通过闭包保持对fn的引用
let judge = (...args) => {
// 使用ES6 rest参数收集所有传入参数
// 这里由于闭包可以访问到外层函数的fn变量
if (args.length === fn.length) {
// 终止条件:当收集的参数数量等于原函数参数数量时
return fn(...args); // 展开参数执行原函数
}
// 递归逻辑
return (...newArgs) => judge(...args, ...newArgs);
}
return judge; // 返回柯里化后的函数 例如我们待会要将judge给addCurry
}
let addCurry = curry(add); //进行柯里化 addCurry就是函数返回的judge
addCurry(1)(2)(3); //柯里化调用 递归
柯里化执行过程分析
1. 初始化阶段
js
let addCurry = curry(add);
curry
函数接收add
作为参数- 创建并返回
judge
函数,该函数通过闭包记住了add
函数
2. 第一次调用:addCurry(1)
js
const step1 = addCurry(1);
- 传入第一个参数1,
args = [1]
args.length(1) < add.length(3)
,不满足执行条件- 返回一个新函数:
(...newArgs) => judge(1, ...newArgs)
3. 第二次调用:step1(2)
js
const step2 = step1(2);
- 传入第二个参数2,
args = [1, 2]
args.length(2) < add.length(3)
,仍不满足- 返回新函数:
(...newArgs) => judge(1, 2, ...newArgs)
4. 第三次调用:step2(3)
js
const result = step2(3);
- 传入第三个参数3,
args = [1, 2, 3]
args.length(3) === add.length(3)
,满足执行条件- 执行
add(1, 2, 3)
并返回结果6
介绍该实现过程中的闭包应用
闭包机制:
讲讲这里闭包的形成
js
function curry(fn) { // 外部函数
let judge = (...args) => { // 内部函数
if (args.length === fn.length) {// 由于闭包,可以访问fn
return fn(...args); //由于闭包可以访问args
}
return (...newArgs) => judge(...args, ...newArgs); //由于闭包可以访问args
}
return judge; // 返回内部函数
}
curry的fn闭包: 当执行const addCurry = curry(add)
时:
-
curry
函数执行,创建了一个作用域,其中包含:- 参数
fn
(指向add
函数) - 局部函数
judge
- 参数
-
curry
返回judge
函数,但judge
函数仍然保持着对fn
的引用 -
即使
curry
函数执行完毕,其作用域也不会被销毁,因为judge
函数还在引用其中的fn
变量
递归时的闭包: 当柯里化函数被多次调用时,会形成闭包链:
js
addCurry(1)(2)(3);
-
addCurry(1)
:- 创建新作用域,
args = [1]
- 返回新函数,该函数闭包包含了
args
和原始fn
- 创建新作用域,
-
(2)
:- 创建新作用域,
newArgs = [2]
- 合并参数
[1, 2]
- 返回新函数,闭包包含了合并后的参数和原始
fn
- 创建新作用域,
-
(3)
:- 参数足够,调用原始
fn(1, 2, 3)
- 参数足够,调用原始
柯里化在 JS 中的应用
最后讲讲柯里化在JS中的应用!柯里化在 JavaScript 中有多种实用场景:
- 参数复用:
js
const add5 = curriedAdd(5);
console.log(add5(10)(15)); // 30
- 延迟执行:
js
// 先收集参数,需要时再执行
const fetchData = curry((url, params) => {
// 实际请求逻辑
});
const getUser = fetchData('/api/user');
// 需要时再传入剩余参数
getUser({ id: 123 });
- 函数组合:
js
const compose = (...fns) => x => fns.reduceRight((v, f) => f(v), x);
const toUpperCase = str => str.toUpperCase();
const exclaim = str => str + '!';
const shout = compose(exclaim, toUpperCase);
console.log(shout('hello')); // "HELLO!"
- React 高阶组件:
js
const withProps = curry((props, Component) => {
return (ownProps) => <Component {...ownProps} {...props} />;
});
const withUser = withProps({ user: currentUser });
const EnhancedComponent = withUser(MyComponent);
总结
柯里化是一种强大的函数式编程技术,它通过将多参数函数转换为一系列单参数函数,提供了更大的灵活性和可组合性。在 JavaScript 中,我们可以利用闭包和递归优雅地实现柯里化,并在各种场景中应用它:
- 参数复用和部分应用
- 延迟函数执行
- 函数组合和管道
- 创建更专门化的函数版本
虽然柯里化可能带来一些性能上的考虑(每次调用都会创建新的函数),但在大多数应用中,它带来的代码清晰度和可维护性的提升远远超过了这点微小的性能开销。掌握柯里化可以让你写出更加声明式、模块化的 JavaScript 代码。
🌇结尾
感谢你看到最后,最后再说两点~
①如果你持有不同的看法,欢迎你在文章下方进行留言、评论。
②如果对你有帮助,或者你认可的话,欢迎给个小点赞,支持一下~
我是3Katrina,一个热爱编程的大三学生
(文章内容仅供学习参考,如有侵权,非常抱歉,请立即联系作者删除。)
作者:3Katrina
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。