持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第3天,点击查看活动详情
背景解读
实现函数调用 add(1)(2)(3) 后,输出值为 1+2+3=6 的结果。
核心描述
- 柯里化定义:将一个使用多个参数的函数,转换成一系列使用一个函数的技术。
- 初级实现版本
/**
* 仅支持 add(1)(2)(3) 的调用方式
*/
function add(x){
return function(y) {
return function(z) {
return x+y+z
}
}
}
console.log(add(1)(2)(3)) // 输出 6
- 通用实现版本
/**
* 支持调用方式包括:
* add(1)(2)(3)
* add(1,2)(3)
* add(1,2,3)
*/
function addNumber(x,y,z){
return x+y+z
}
// 柯里化函数
function curry(fn,...args){
// 如果参数的长度大于等于被包装的函数的入参数
// 则直接执行被包装的函数,并将入参数依次传入
// 此处为递归的出口,如果不满足此条件,将递归执行 curry 方法
if(args.length >= fn.length) {
return fn(...args)
} else {
// 否则返回一个函数,将已传入的参数 args 提前传入 curry 中
// 将新传入的参数 _args 也传入到 curry 中
// 对 curry 柯里化函数递归调用
return function(..._args) {
return curry(fn,...args,..._args)
}
}
}
// 获取经过 curry 柯里化包装后的 add 方法
const add = curry(addNumber)
console.log(add(1)(2)(3)) // 输出 6
console.log(add(1,2,3)) // 输出 6
console.log(add(1,2)(3)) // 输出 6
- 高级实现版本
/**
* 支持以下输出示例:
* add(1)(2).valueOf() // 输出 3
* add(1,2).valueOf() // 输出 3
* add(1)(2)(3).valueOf() // 输出 6
* add(1,2)(3).valueOf() // 输出 6
* add(1)(2)(3)(4).valueOf() // 输出 10
*/
function add(...args){
// 定义一个 func 函数对象
// 实质上是用来不断的保存参数,直到方法统一调用时,即 valueOf 方法被调用时
let func = function(..._args){
return add(...args,..._args)
}
// 重写函数对象的 valueOf 方法
// 此方法为递归调用的结束
// 如果不调用此方法,则表示还有其他参数,则会继续返回 func
// 如果想用别的方式停止,编写对应逻辑即可
// 如:当执行方法不传入参数时,则判断一下入参长度 _args.length === 0,为 true 则执行
// valueOf 中现有的逻辑即可
func.valueOf = function(){
console.log(args)
return args.reduce((a,b)=>a+b)
}
return func
}
console.log(add(1)(2).valueOf()) // 输出 3
console.log(add(1,2).valueOf()) // 输出 3
console.log(add(1)(2)(3).valueOf()) // 输出 6
console.log(add(1,2)(3).valueOf()) // 输出 6
console.log(add(1)(2)(3)(4).valueOf()) // 输出 10
- 实现核心:
- 判断传入的参数 args 与原始函数定义的长度比较,如果 args 更长或者相同,则可以直接执行原始函数,否则递归柯里化函数
- 利用闭包将函数的参数储存起来,等参数达到一定数量时执行函数。
- 柯里化的好处:
- 参数复用
- 延迟执行:如节流、防抖、bind 函数的 polyfill 实现
- 提前返回:比如封装一个 addEvent,提前返回之后,下一次就不会再进入对应的浏览器环境判断了,减少了执行时间
- 函数中有 if...else... 或者其他分支判断
- 函数返回的是一个函数,而不是一个值
知识拓展
- 纯函数,满足下面2个条件的函数,就可以称为纯函数:
- 如果函数的调用参数相同,则永远返回相同的结果。它不依赖于程序执行期间函数外部任何状态或数据的变化,必须只依赖于其输入参数。
- 该函数不会产生任何可观察的副作用,例如网络请求,输入和输出设备或数据突变(mutation)。
- 高阶函数,就是一个将函数作为参数或者返回值的函数。
- 偏函数,是 JS 函数柯里化运算的一种特定应用场景。简单描述,就是把一个函数的某些参数先固化,也就是设置默认值,返回一个新的函数,在新函数中继续接收剩余参数,这样调用这个新函数会更简单。
- 函数柯里化,将一个使用多个参数的函数,转换成一系列使用一个函数的技术。
- 函数反柯里化,是一个泛型化的过程。它使得被反柯里化的函数,可以接收更多参数。目的是创建一个更普适性的函数,可以被不同的对象使用。有鸠占鹊巢的效果。
- 举例:Array.prototype.push 的方法一般只用于数组,但是通过 Array.prototype.push.call(null,{}) 的调用,可以用于类似 function 的 arguments 参数等等。
- bind 实现,其实也是一种柯里化的应用。
参考资料
- 函数柯里化:三行代码实现 add(1)(2)(3):juejin.cn/post/684490…
- 柯里化(Currying):zh.javascript.info/currying-pa…
- Currying in JavaScript(柯里化):cythilya.github.io/2017/02/27/…
- 理解Javascript的柯里化:segmentfault.com/a/119000002…
- 柯里化与反柯里化:juejin.cn/post/684490…
浏览知识共享许可协议
本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。