本文详细解析了JavaScript中函数柯里化的概念,并通过从基础实现到ES6语法的逐步优化,最终展示如何用一行代码实现柯里化函数。内容涵盖参数累积、递归调用及箭头函数的精简技巧,帮助开发者深入理解柯里化的核心机制。
函数柯里化的概念与类比
首先,我们需要明确柯里化的作用。通过观察以下示例函数,可以初步理解其基本形态。
let store = (a,b,c) => "这是你的七仔面"
//函数就好像一个小卖部,一碗七仔面要三张软妹币:a,b,c (五个参数)
那么,什么是函数柯里化?我们可以将其类比为购买面条的过程。
let curryStore = curry(store) //刚刚的小卖部被柯里化了
//最佳状况
curryStore(1,5,1)// 老板,刚好!不用找了,刚好七块钱。老板: "这是你的七仔面"
//偶尔出现的情况
let boss = curryStore(5) //老板,这五块钱你先拿着,我找找有没有一块钱。老板:……
boss = boss(1) //有了老板,这一块钱你先拿着,我找找还有没有一块钱。。老板:……
boss(1) //哈哈,终于找到了,给!老板:"这是你的七仔面"
curryStore(5)(1)(1) //等价于上诉情况
由此可见,函数柯里化的核心在于逐步收集参数,并延迟函数的实际执行。这类似于先分期付款,后交付商品的模式。
柯里化函数的基础实现
接下来,我们通过一个具体案例来练习。
当前的目标是实现一个名为 curry 的函数,使其具备以下行为:当提供的参数数量足够时,执行原函数;若参数不足,则返回一个新的柯里化函数。
let curryPlus = curry((a,b,c) => a+b+c) //这里给到一个有三个参数的函数
curryPlus(1)(2)(3) //返回 6
curryPlus(1)(2,3) //返回 6
curryPlus(1,2)(3) //返回 6
let x = curryPlus(1)(2) //喂,怎么才两个参数?返回一个curry函数(已经带了两个参数)
x(1) //返回 4
x(2) //返回 5
对于熟练掌握 JavaScript 的开发者而言,一行代码解决此问题并非难事,但为了清晰展示逻辑,我们暂不直接给出最终答案。
因此,我们首先从传统的实现方法开始讲解。
根据柯里化的使用场景,其原理是返回一个接收不确定数量参数的函数(例如,用户可能一次性提供多个参数)。因此,我们可以利用不定参数语法来实现:
const curry = (fn) => {
return (...args) =>{ //不定参数,想给多少给多少
//给钱交货环节
}
}
柯里化函数需要记录已传入的参数。如果尚未传入参数,则默认使用一个空数组:
const curry = (fn,arr=[]) => { //arr数组用于记录已有参数
return (...args) =>{
//给钱交货环节
}
}
在每次调用时,需检查参数总数是否已满足原函数的要求。若满足,则执行原函数 fn;若不满足,则返回一个新的柯里化函数,并将当前已收集的参数传入其中:
const curry = ( fn, arr = []) => {
return (...args) => {
//判断参数总数是否和fn参数个数相等
if([...arr, ...args].length === fn.length){
return fn(...arr, ...args) //拓展参数,调用fn
}else{
return curry(fn,[...arr, ...args]) //迭代,传入现有的所有参数
}
}
}
至此,我们已经成功实现了基础的 curry 函数。
ES6一行代码的推导与优化
接下来,我们探讨如何使代码更加简洁。首先,可以将中间逻辑封装为立即执行函数,从而省略部分变量声明(如 ...arr 和 ...args):
const curry = ( fn, arr = []) => {
return (...args) => {
return ( a => { //a是一个数组
if(a.length === fn.length) {
return fn(...a)
}else{
return curry(fn, a)
}
})([...arr, ...args]) //这里把arr和args摊开成一个数组赋值给a
}
}
此外,if 语句可以简化为三元表达式,进一步减少代码量:
const curry = ( fn, arr = []) => {
return (...args) => {
return ( a => {
return a.length === fn.length ? fn(...a) : curry(fn, a)
})([...arr, ...args])
}
}
最后,既然函数体仅包含 return 语句,我们可以利用箭头函数的简洁语法(input => output),省略 return 关键字和大括号:
const curry = ( fn, arr = []) => {
return (...args) => {
return ( a => a.length === fn.length? fn(...a) : curry(fn, a))([...arr, ...args]) //先折叠一层
}
}
继续对代码进行折叠处理:
const curry = ( fn, arr = []) => {
return (...args) => ( a => a.length === fn.length? fn(...a) : curry(fn, a))([...arr, ...args]) //再折叠
}
最终版本如下:
const curry = ( fn, arr = []) => (...args) => ( a => a.length === fn.length? fn(...a) : curry(fn, a))([...arr, ...args])
//衣服给您叠好了
你可以尝试运行以下代码进行验证:
const curry = ( fn, arr = []) => (...args) => ( a => a.length === fn.length? fn(...a) : curry(fn, a))([...arr, ...args])
let curryPlus = curry((a,b,c,d)=>a+b+c+d)
curryPlus(1,2,3)(4) //返回10
curryPlus(1,2)(4)(3) //返回10
curryPlus(1,2)(3,4) //返回10
特殊场景:参数个数为0时的处理
当然,柯里化函数的主要用途在于延迟执行。触发执行的條件不一定是参数数量相等,也可以是其他条件。例如,当参数个数为 0 时执行,这时我们需要对上述 curry 函数稍作修改:
const curry = ( fn, arr = []) => (...args) => ( (a,b) => b.length === 0? fn(...a) : curry(fn, a))([...arr, ...args],[...args])
let curryPlus = curry((...x)=>x.reduce((a,b)=>a+b))
curryPlus(1) //返回一个函数
curryPlus(1)(2) //返回一个函数
//遇到参数个数为0的情况才执行
curryPlus(1)(2)(4)() //返回7
curryPlus(1,2)(4)() //返回7
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持本站。