前言
首先函数柯里化作为闭包的一种使用场景,指的是一种将使用多个参数的一个函数转换成一系列使用一个参数的函数的技术,其主要的目的在于避免频繁调用具有相同参数的函数,实现参数的复用。
例如将fn(1,2,3)的形式变换成fn(1)(2)(3)。
这里就不再赘述它的作用,下文将直接给出定参与不定参的两种实现方式,以及阐述它的实现过程。
参数个数固定
function curry(fn,...args){
// 返回改造后的函数
return function(){
//拼接参数 这里就用到了闭包的特性,访问外层函数的变量
let subArgs = [...args,...arguments]
// 判断参数长度是否已经满足函数所需参数的长度 递归出口
if(subArgs.length>= fn.length){
// 满足就直接执行函数
return fn.apply(this,subArgs)
}else{
// 若不满足,则递归返回柯里化的函数,等待后续参数的传入
return curry.call(this,fn,...subArgs)
}
}
}
定参的函数柯里化只要理解了柯里化的意义则基本可以独自实现。
要注意的点就三个:
- 函数所需参数固定
- 每次返回的都是一个函数
如函数柯里化是能够把fn(1,2,3)的形式变换成fn(1)(2)(3)。
在这个示例中,fn(1)是一个函数,fn(1)(2)也是一个函数,fn(1)(2)(3)仍然是一个函数。因此当然也可以直接以fn(1)()的形式来调用函数,这里后文在阐述不定参的柯里化函数时会再细讲。
- 传入的参数需要不断向下传递
对于第一点,所以需要获取fn.length用以判断是否满足递归条件,此后再根据是否满足递归条件来判断要不要执行函数本身。对于第二点,因此需要具有return function xx(){}的结构。对于第三点,就需要用到闭包的特性,把外层函数传入的参数保存,并和此次调用函数传入的参数做拼接并向下传递。
来验证一下结果:
var fn0 = function(a, b, c, d) {
return [a, b, c, d];
}
var fn1 = curry(fn0);
console.log(fn1("a")("b","c")("d"));//["a", "b", "c", "d"]
console.log(fn1("a")("b")("c")("d"));//["a", "b", "c", "d"]
function add(a, b) {
return a + b;
}
var add1 = curry(add, 1, 2);
console.log(add1()) // 3
var add2 = curry(add, 1);
console.log(add2(2)) // 3
var add3 = curry(add);
console.log(add3(1, 2)) // 3
tips:注意扩展运算符和rest参数的使用。
rest剩余参数 eg.function a(…args) 语法允许我们将一个不定数量的参数表示为一个数组;扩展运算符可以将数组或者对象转为用逗号分隔的参数序列。
function a(...args){
console.log(...args)//1 2 3
console.log(args)// [1, 2, 3]
}
a(1,2,3)
不定参数
这里先设定一个经典场景:实现add(1)(2)(3)(4)=10; add(1)(1,2,3)(2)=9;
我们先给出最终实现代码。
function add(){
let args = [...arguments]
function curry(){
args.push(...arguments)
return curry
}
curry.toString = function(){
return args.reduce((pre,cur)=>pre+cur)
}
return curry
}
同样的,我们来验证一下结果:
let a = add(1)(2)
console.log(a);//3
let b = add(1)(1,2,3)(2)
console.log(b);//9
理解以上代码,需要以下前置知识——为什么要重写curry函数的toString方法?
我们先把重写toString的部分注释掉,那么会得到这样的结果:
function add(){
let args = [...arguments]
function curry(){
args.push(...arguments)
return curry
}
return curry
}
console.log(add());
/*
function curry(){
args.push(...arguments)
return curry
}
*/
也就是说当我们return curry时,其实就是得到curry函数的源代码,而获取curry函数源码会自动调用函数的toString方法,这也是上文中为什么我们要重写该方法的理由——因为我们需要在打印的时候可以直接得到求和的结果。
ps.在这篇博客中对不同类型的数据调用toString()方法有较为详细的描述,感兴趣的可以看看。
以下是一些逐步的实现过程,假如我们先实现add(1)(2)的求和,则有:
function add1(...args){
return function(){
let temp = [...args,...arguments]
return temp.reduce((pre,cur)=>pre+cur)
}
}
console.log(add1(1)(2));//3
console.log(add1(1));
/*
function(){
let temp = [...args,...arguments]
return temp.reduce((pre,cur)=>pre+cur)
}
*/
console.log(add1(1)(2)(4));//add1(...)(...) is not a function
console.log(add1(1)());//1
add1(…)(…) is not a function,是因为很明显在我们调用add(1)时会返回一个函数,但是再对这个函数传入参数2的时候,代码就会执行到return temp.reduce((pre,cur)=>pre+cur)这一行,因此返回的直接是一个数,而非函数。
但我们的需求是:希望add1(…)(…)…(…)既是一个函数,同时又希望它可以打印时直接输出求和结果。因此需要改写toString()。
最后再看回最终代码
function add(...args){
function curry(){
//同样是拼接参数 参数传递 利用闭包特性保存args
args.push(...arguments)
//为了能让add(...)(...)...(...)一直是一个函数的形式
return curry
}
curry.toString = function(){
//这部分写希望函数实际的处理,让控制台可以打印
}
//一样是最终返回一个函数
return curry
}