这是我参与2022首次更文挑战的第4天,活动详情查看:2022首次更文挑战」
1.术语:
下面来介绍一些后续会用到的术语。
-
一元函数:只接收一个参数的函数。
//一元函数 const func = (x) => x; -
二元函数:接收两个参数的函数。
//二元函数 const add = (a,b)=> a + b; -
变参函数:接收
可变数量参数的函数function variableFunc (a){ console.log(a); console.log(arguments) } variableFunc(214,520,920); //打印结果 //214 // [214,520,920]argumnets可以捕获调用函数的参数
//es6中的拓展运算符 function variableFunc (a,...variables){ console.log(a) console.log(variables) } variableFunc(214,520,920); //打印结果 //214 // [214,520,920]es6的拓展运算符
2.柯里化:
🚀柯里化:把一个多参数函数转换为一个嵌套的一元函数的过程称之为柯里化
例如:const add = (x,y) => x +y柯里化后const addCurry = x => y => x+y
🚀 多参数函数柯里化实现:
const curriedN = function(func){
if(typeof func !== "function"){
throw new Error('func');
}
return function curried(...args){
if(args.length < func.length){
return function(){
return curried.apply(null,args.concat([].slice.call(arguments)));
}
}
return func.apply(null,args);
}
}
🚀 分析:
-
args.length < func.length是如果传入的参数args小于func函数所需要的参数个数就进入判断;否则就直接返回func.apply(null,args)的调用结果。 -
下面重点说说图片中的代码:
-
图片中的
return funciton(){...略}为了获取到后面返回函数传入的参数,也就是获取到的arguments参数 -
注意闭包的使用-
args,通过返回函数后,curried函数的args并没有释放,因为在返回的内层的匿名函数中有使用,这样在下次递归调用curried函数的时候就能完成拼接操作--args.concat([].slice.call(arguments));🚀 柯里化函数实战使用测试:
例子1:求三个数的乘积:
const multiply = (x,y,z)=> x*y*z; let curried = curriedN(multiply); let sixty = curried(3)(4)(5); console.log(sixty); //60 let res1 = curried(3); let res2 = res1(4); let res3 = res2(10); console.log(res3); //120可以看到,无论直接传入还是分步骤传入,结果没有问题。
例子2:求数组的平方:
let map1 = curriedN(function (func, arr) {
return arr.map(func);
});
let squareAll = map1((x) => x * x);
let res4 = squareAll([1,2,3,4]);
console.log(res4);
//[1,4,9,16]
- 通过
curriedN对函数进行柯里化,--map1 - 给
map1传入执行函数,也就是x=>x*x,--squareAll - 给
squareAll传入真实的参数,也就是数组--[1,2,3,4]
3.偏应用:
什么是偏应用呢?先来看下面的一个场景:
假设我们要 10ms 后做一组操作,可以通过 setTimeout 来实现
setTimeout(()=>console.log('吃饭饭🍚'),10)
setTimeout(()=>console.log('睡觉觉😴'),10)
这个函数可以柯里化吗?答案是否定的。curry 的参数列表是从左往右的。不过可以通过
const setTimeoutWrapper = (time,func)=>{
setTimeout(func,time);
};
const delayTenMs = curriedN(setTimeoutWrapper)(10);
dealyTenMs(()=>console.log('摸🐟🐟'));
delayTenMs(()=>console.log('扣🦵🏻🦵🏻'));
- 为了能是函数正常的进行柯里化,需要创建类似上面的一个
包裹函数,这也是一种开销,由此可以使用偏应用技术。
🚀 下面来看下偏应用的实现:
//偏应用函数实现
function partial(fn){
var args = Array.prototype.slice.call(arguments); //将类数组对象arguments转为数组
args.shift(); //除去fn参数
for (var i = args.length;i<fn.length;i++) //补齐,跟fn的参数列表对应上
args.push(undefined)
return function() {
var remainArgs = Array.prototype.slice.call(arguments), // 剩余参数
index = 0;
for (i = 0; i < args.length; i++) {
args[i] === undefined && (args[i] = remainArgs[index++])
}
return fn.apply(this,args);
}
}
//偏应用函数使用
function computeFourNums(a,b,c,d){
return a + b + c + d;
}
var partialComputeFourNums = partial(computeFourNums,1,undefined,3,undefined);
console.log(partialComputeFourNums(2,4));
//10
- 用
undefined给下次要传入的参数做占位 partial函数体开始将参数fn从arguments剔除- 通过
for循环将参数补齐(因为传参数不一定都按照fn的所需的参数个数去传--partial(computeFourNums,1,undefined));为了避免这种情况的发生就需要对参数进行一个补齐。