函数柯里化(概念)
函数柯里化,又可以被称作部分求值。此类函数,在传入部分的值后,并不会立刻求值,而是返回一个函数,并且在参数未满足条件时(参数个数未达标或未传入结束标识符),会将传入的值存储在形成的闭包中,等待下一次调用。
一个函数,在经过柯里化后,可以不需要将值一次性传入。例如:
// 仅作为示例,具体使用参照下文中的代码
// 柯里化前
add(1,2,3)
// 柯里化后
add(1,2)(3)
优缺点
优点:
- 延迟计算,当参数未齐或者未传入结束标识符时,仍返回函数
- 参数复用,利用闭包的特性,将第一个传入的参数存入闭包空间,不会被销毁。
缺点:
- 容易造成内存泄漏,也是由闭包的特性造成
- 性能不高,因为使用了递归的方式
- 函数进行了嵌套,新人不易理解
经典面试题
函数柯里化,在前端面试八股文中,是比较经典的一个面试题。例如:
实现一个add函数,实现以下功能
add(1,2,3)
add(1)(2)(3)
add(1,2)(3)
add(1,2,3,4)
此类题目,部分面试官会给出限定的参数个数,但也有部分面试官不会给出指定的参数个数。
固定参数个数
const curry = function(fn){
let len = fn.length // 获取函数的参数个数
return function t(){
let innerLen = arguments.length
// 因为arguments只是类数组,需要先将其转化为数组
const args = Array.prototype.slice.call(arguments)
if(innerLen>=length){
// 参数个数达到要求,执行fn函数
return fn.apply(undefined,args)
} else{
// 参数个数未达要求,递归执行,继续返回函数
return function(){
// 获取下一次调用时传入的参数,与之前的参数进行拼接后,重新调用t函数
const innerArgs = Array.prototype.slice.call(arguments)
const allArgs = args.concat(innerArgs)
return t.apply(undefined,allArgs)
}
}
}
}
function add(num1,num2,num3){
return num1+num2+num3
}
let finalFun = curry(add)
let res1 = finalFun(1)(2)(3) // 6
let res2 = finalFun(1)(2,3) // 6
let res3 = finalFun(1,2)(3) // 6
无固定参数个数,以标识符作为结尾
const curry = function(fn){
return function t(){
// 因为arguments只是类数组,需要先将其转化为数组
const args = Array.prototype.slice.call(arguments)
if(args.includes("end")){
return fn.apply(undefined,args)
} else{
return function(){
const innerArgs = Array.prototype.slice.call(arguments)
const allArgs = args.concat(innerArgs)
return t.apply(undefined,allArgs)
}
}
}
}
function add(){
// 因为参数个数不固定,所以需要从arguments中获取
let args = Array.prototype.slice.call(arguments)
// Array.findIndex,传入一个方法进行判断,当方法返回true时,findIndex停止执行,直接返回符合条件的下标
let endIndex = args.findIndex((item) => {
return item === 'end'
})
let sum = 0
for(let index = 0;index<endIndex;index++){
sum += args[index]
}
return sum
}
let finalFun = curry(add)
let res1 = finalFun(1)(2)('end') // 3
let res2 = finalFun(1)(2,3)('end') // 6
let res3 = finalFun(1)(2,3)(4,'end') // 10