一、简单了解apply和call
- call 和 apply 都是为了改变某个函数运行时的 context 即上下文而存在的,换句话说,就是为了改变函数体内部 this 的指向。
- call 和 apply二者的作用完全一样,只是接受参数的方式不太一样。call其实是apply的一种语法糖。
- 格式:
apply(context,[arguments]) ,call(context,param1,param2,...) 。
二、什么是函数柯里化?
柯里化(Currying)是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果的新函数的技术。
在这里举个例子,有一个add() 函数,它是用来处理我们传给它的参数(param1,params2,…)相加求和的一个函数。
function add(x , y){
return x + y;
}
add(4,6);
function add(4 , y){
return 4 + y;
}
function add(4 , 6){
return 4 + 6;
}
如果我们将add() 函数柯里化,是什么样子呢?在这里简单的实现一下:
function add(x ,y){
if (typeof y === 'undefined') {
return function (newy){
return x + newy;
}
}
return x + y;
}
console.log(typeof add(4));
console.log(add(4)(6));
let saveAdd = add(4);
console.log(saveAdd(6));
从以上简单柯里化的add() 函数可以看出,函数可以接受部分函数,然后返回一个新的函数,使其继续处理剩下的函数。
三、写一个公共的柯里化函数
在这里我们创建一个公共的柯里化函数,那样我们就不必每次写一个函数都要在其内部实现复杂的柯里化过程。
function createCurry(fn){
var slice = Array.prototype.slice,
stored_args = slice.call(arguments,1);
return function () {
let new_args = slice.call(arguments),
args = stored_args.concat(new_args);
return fn.apply(null,args);
}
}
在以上公共的柯里化函数中:
arguments ,并不是一个真的数组,只是一个具有length 属性的对象,所以我们从Array.prototype 中借用slice 方法帮我们把arguments 转为一个真正的数组,方便我们更好的操作。- 当我们第一次调用函数
createCurry 的时候,其中变量stored_args 是保持了除去第一个参数以外的参数,因为第一个参数是我们需要柯里化的函数。 - 当我们执行
createCurry 函数中返回的函数时,变量new_args 获取参数并转为数组。 - 内部返回的函数通过闭包访问变量
stored_args 中存储的值和变量new_args 的值合并为一个新的数组,并赋值给变量args 。 - 最后调用
fn.apply(null,args) 方法,执行被柯里化的函数。
现在我们来测试公共的柯里化函数
function add(x , y){
return x + y;
}
var newAdd = createCurry(add,4);
console.log(newAdd(6));
console.log(createCurry(add,4)(6));
当然这里并不局限于两个参数的柯里化,也可以多个参数:
function add(a,b,c,d){
return a + b + c + d;
}
console.log(createCurry(add,4,5)(5,6));
let add_one = createCurry(add,5);
console.log(add_one(5,5,5));
let add_two = createCurry(add_one,4,6);
console.log(add_two(6));
通过以上的例子,我们可以发现一个局限,那就是不管是两个参数还是多个参数,它只能分两步执行,如以下公式:
- fn(x,y) ==> fn(x)(y);
- fn(x,y,z,w) ==> fn(x)(y,z,w) || fn(x,y)(z,w)||…
如果我们想更灵活一点:
- fn(x,y) ==> fn(x)(y);
- fn(x,y,z) ==> fn(x,y)(z) || fn(x)(y)(z);
- fn(x,y,z,w) ==> fn(x,y)(z)(w) || fn(x)(y)(z)(w) || …;
我们该怎么实现呢?
四、创建一个灵活的柯里化函数
经过以上练习,我们发现我们创建的柯里化函数存在一定局限性,我们希望函数可以分为多步执行:
let createCurry = (fn,...params)=> {
let args = parsms || [];
let fnLen = fn.length;
return (...res)=> {
let allArgs = args.slice(0);
allArgs.push(...res);
if(allArgs.length < fnLen){
return createCurry.call(this,fn,...allArgs);
}else{
return fn.apply(this,allArgs);
}
}
}
function add(a,b,c,d){
return a + b + c + d;
}
let curryAdd = createCurry(add,1);
console.log(curryAdd(2)(3)(4));
以上我们已经实现了灵活的柯里化函数,但是这里我们又发现了一个问题:
- 如果我第一次就把参数全部传入,但是它并没有返回结果,而是一个函数(function)。
- 只有我们再次将返回的函数调用一次才能返回结果:
curryAdd(add,1,2,3,4)() ; - 可能有人说如果是全部传参,就调用原来的
add() 函数就行了,这也是一种办法;但是我们在这里既然是满足参数数量,对于这种情况我们还是处理一下。
在这里我们只需要在返回函数前做一下判断就行了:
let createCurry = (fn,...params)=> {
let args = parsms || [];
let fnLen = fn.length;
if(length === _args.length){
return fn.apply(this,args);
}
return (...res)=> {
let allArgs = args.slice(0);
allArgs.push(...res);
if(allArgs.length < fnLen){
return createCurry.call(this,fn,...allArgs);
}else{
return fn.apply(this,allArgs);
}
}
}
以上可以算是完成了一个灵活的柯里化的函数了,但是这里还不算很灵活,因为我们不能控制它什么时候执行,只要参数数量足够它就自动执行。我们希望实现一个可以控制它执行的时机该怎么办呢?
五、写一个可控制的执行时间的柯里化函数
我们这里直接说明一下函数公式:
- fn(a,b,c) ==> fn(a)(b)(c )();
- fn(a,b,c) ==> fn(a);fn(b);fn(c );fn();
- 当我们参数足够时它并不会执行,只有我们再次调用一次函数它才会执行并返回结果。在这里我们在以上例子中加一个小小的条件就可以实现。
let createCurry = (fn,...params)=> {
let args = parsms || [];
let fnLen = fn.length;
return (...res)=> {
let allArgs = args.slice(0);
allArgs.push(...res);
if(res.length > 0 || allArgs.length < fnLen){
return createCurry.call(this,fn,...allArgs);
}else{
return fn.apply(this,allArgs);
}
}
}
function add(a,b,c,d){
return a + b + c + d;
}
let curryAdd = createCurry(add,1);
console.log(curryAdd(2)(3)(4));
console.log(curryAdd(2)(3)(4)());
console.log(curryAdd(2)(3)());
|