函数柯里化是什么?
首先来看下维基百科上对柯里化的定义:
在计算机科学中,柯里化(英语:Currying),又译为卡瑞化或加里化,是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。——维基百科_柯里化
听上去比较抽象,下面来举一个例子,方便大家理解:
- 首先定义一个函数,作用是返回传入参数的和。
// 首先定义一个函数
function sum(a, b, c) {
return a + b + c;
}
let result = sum(1, 2, 3)
console.log(result) // 6
- 下面我们来看该函数柯里化后的代码。
function sum(a) {
return function(b) {
return function (c) {
return a + b +c
}
}
}
let result2 = sum(1)(2)(3)
console.log('sum-result::',result) // sum-result::6
来解析下上面的代码,在柯里化后,sum函数出现了下面的几个特征:
- 函数作为返回值,内部的a、b、c变量因为访问了外部作用域的变量,形成了闭包。
- 在调用的时,如果该函数的参数的数量不满足原函数的参数数量,则内部会向外返回一个函数,该函数可以接受并处理剩余的参数。
- 当参数数量和原函数一致的时候,触发后的结果和原有函数一致。
所以可以根据维基百科给出的定义,以及上文中的代码,我们做一个小结:
- 柯里化是将一种函数转换为另一种函数的技术。
- 原有函数为多个参数的函数,在柯里化后,该函数会转换为能够接收并处理剩余参数的新函数。
函数柯里化的特性
当使用函数柯里化时,参数的复用是一个非常有用的特性。 通过函数柯里化,可将一个函数拆分成多个部分,每个部分接受一个参数,并返回一个新的函数,这样就可以在不同的上下文中重复使用这些参数。
函数柯里化的应用
上文可知函数柯里化的显著特性就是可以实现参数的复用,那么这种特性如何运用于日常开发中呢? 可以这样整理思路:
- 比如为在求值的过程中,如果需要多次调用相同的函数,但是每次调用都需要传入部分相同的参数,这样写,代码看上去是否冗余会有些冗余呢?
- 那么为什么不能直接固定部分参数,减少后续求值的中所需要写的重复代码。
带着这个思路,下面举个例子,方便大家理解:
例:日志记录
比如在开发过程中,我们难免会遇到需要记录日志的例子:
function log(date, importance, message) {
console.log(`[${date.getHours()}:${date.getMinutes()}] [${importance}] ${message}`);
}
log(new Date('2024/01/01 15:30:00'), "INFO", "test")
log(new Date('2024/01/01 15:30:00'), "DANGER", "danger test")
log(new Date('2024/01/01 15:30:00'), "WARNING", "warning test")
可以看到如果我们需要记录日志,假设每次都需要传入同样的日期,而后面参数的importance和message需要经常变动,那么为何不可以固定这里的第一个参数呢?
这里我们将该函数柯里化(使用lodash内置的柯里化函数API),看看具体的效果吧!
function log(date, importance, message) {
console.log(
`[${date.getHours()}:${date.getMinutes()}] [${importance}] ${message}`
)
}
let curryLog = _.curry(log)
// log(new Date('2024/01/01 15:30:00'), 'INFO', 'test')
// log(new Date('2024/01/01 15:30:00'), 'DANGER', 'danger test')
// log(new Date('2024/01/01 15:30:00'), 'WARNING', 'warning test')
// 固定时间
curryLog(new Date('2024/01/01 15:30:00'))('INFO')('test')
// 同样也可以把importance(重要等级)固定下来封装为一个新的函数
const info = curryLog(new Date('2024/01/01 15:30:00'))('INFO')
info('test2')
运行结果:
柯里化的实现
在了解完成柯里化的应用后,现在来看看柯里化是怎么实现的吧! 下面是柯里化函数的简易实现代码:
function curry(func) {
return function curried(...args) {
if (args.length >= func.length) {
return func.apply(this, args)
} else {
return function (...args2) {
return curried.apply(this, [...args, ...args2])
}
}
}
}
实现思路:
- 首先定义一个函数,参数为需要柯里化的函数。
- 返回
curried函数,参数为...args。 - 判断该函数传入的参数的长度是否大于该函数的期望的参数数量。
- 当该函数传入的参数的长度大于该函数的期望的参数数量的时候,返回该函数自身的拷贝。
- 如果不满足函数传入的参数的长度大于该函数的期望的参数数量,则返回一个新的匿名函数,此时创建
args2用于保存后续传入的参数,然后将之前传入的参数args和新传入的参数args2做一个合并,变成一个新的数组,后返回curried的函数拷贝。 - 回到第3步。
测试:
function log(date, importance, message) {
console.log(
`[${date.getHours()}:${date.getMinutes()}] [${importance}] ${message}`
)
}
function curry(func) {
return function curried(...args) {
if (args.length >= func.length) {
return func.apply(this, args)
} else {
return function (...args2) {
return curried.apply(this, [...args, ...args2])
}
}
}
}
let curryLog = curry(log)
// 固定时间
curryLog(new Date('2024/01/01 15:30:00'))('INFO')('test')
// 同样也可以把importance固定下来封装为一个新的函数
const info = curryLog(new Date('2024/01/01 15:30:00'))('INFO')
info('test2')
测试结果:
柯里化的缺点
- 因为依赖闭包以及查找作用域链的特性,所以会造成额外的开销,进而影响代码性能。
总结
函数柯里化的作用:
- 参数的复用:将一个函数拆分成多个部分,每个部分接受一个参数,并返回一个新的函数,这样就可以在不同的上下文中重复使用这些参数,实现参数的复用和灵活的代码组合。
- 简化函数调用:帮助我们简化函数的调用方式,提高代码的可读性和可维护性。
函数柯里化的缺点:
- 性能损耗:因为依赖闭包以及查找作用域链的特性,所以会造成额外的开销,进而影响代码性能。