函数柯里化(颗粒化)
普通函数写法:
function add(a, b) {
return a + b
}
柯里化之后的函数:
const curry = function (fn) {
return function cu(a) {
return function (b) {
return fn(a, b)
}
}
}
const newFn = curry(add) // 得到一个 function cu(num1){....}
console.log(newFn(1)(2)); // 3
console.log(newFn(1)); // function(b){...}
/* 特点: */
// 参数不够 返回剩余未释放的闭包
// 参数筹齐才能完全释放
柯里化特点:需要将参数全部筹齐才能够调用我们真正想要的函数,如果不够参数则返回一个保留已入参数的的函数闭包等待继续入参调用,在开发中当我们的参数需要进行一系列复杂处理(ajax请求...)才能拿到时,柯里化函数的优势就显而易见了
概念补充:
函数是有长度属性的,其长度就是该函数的参数列表长度
function n(a, b, c) { }
function d(a, b) { }
console.log(n.length); // 2
console.log(d.length); // 3
封装一个能将函数颗粒化的函数,并且不限参数:
以上例子中,我们的a,b是写死的,curry只针对俩参函数进行柯里化包装,所以我们将其进行改良,传入任意长度参数的函数,依旧能将其柯里化
function addX(a, b, c, d) {
return a + b + c + d
}
const curryAll = (fn) => {
// 新函数【首先入参】 在这一层
return function cu(...args) {
// 【入参长度正好够时】
if (args.length === fn.length) {
// 参数彻底交到fn身上 并调用,完全释放闭包
return fn(...args)
}
// 上面如果 return 不执行下面代码 故不写else
/* 思路: */
// 长度不够 我们可以返回一个释放一半的函数闭包
// 理应返回一个function (...newArgs){..} 即能保存args,又能将后面入的参拼接起来
return function (...newArgs) {
// 因为这层函数本身就是 function cu(){}中返回的一个闭包函数
// 所以本身可以保存 args参数列表
args = args.concat(newArgs)
// 参数拼接完 需要再次看一下这个参数长度是否等于fn函数长度
// 如果不够 我们还是依旧返回一个函数
// !故使用【递归】 参数变动:新参数 递归出口:直到走第一条分支 调用真正的fn()
return cu(...args)
}
}
}
函数封好后进行测试
function addX(a, b, c, d) {
return a + b + c + d
}
// add函数进去 颗粒化了的cadd函数出来
const cadd = curryAll(addX); // function cu(args){...}
// 一次性给够参数 = add(1,2,3,4)
console.log(cadd(1, 2, 3, 4)); //10 // 一次性出来 返回 addX(1,2,3,4)
// 分批次喂入数据
console.log(cadd(1)(2)(3)(4)); //10
console.log(cadd(1, 2)(3)(4, 5)); // 由于拼接后的长度大于函数长度 ,走第二条分支 依旧返回一个函数闭包
管道函数与组合函数:
这两者其本质其实类似,就是将一个让一个函数的返回值作为另一个函数的入参
// 准备3个测试用的函数
const len = (n) => {
console.log('len执行');
return n+10
}
const pow = (n) => {
console.log('pow执行');
return n * n
}; //求n的平方
const log = (n) => {
console.log('log执行,打印结果为', n)
}; //打印n
组合函数封装:
function combine(fn1, fn2) {
return function co(data) {
return fn1(fn2(data)) //f2(data)调用后的返回值再入参给fn1调用
}
}
const newfn1 = combine(log, pow) // function co(data){...}
const newfn2 = combine(log, combine(len, pow)) // 结合律
newfn1(10)
/*
pow执行
log执行,打印结果为 100
*/
newfn2(10)
/*
pow执行
len执行
log执行,打印结果为 110
*/
管道函数的封装:
// 不限函数个数
const pipe = function (...args) {
return function (data) {
return args.reduce((lv, cv) => {
return cv.apply(null, [lv])
}, data)
}
}
// 测试
const init = pipe(len, log, pow,) // 从左到右执行
init(100)
/*
len执行
log执行,打印结果为 110
pow执行
*/
可以看出管道函数和组合函数无非就是方向不同,入参的函数个数不同,开发中使用一种即可瞒足我们一些业务需求,因此我们将管道函数改良升级一下
// 封装一个能发现那个函数中有bug的函数,并返还bug函数执行前,我们传入的data现在等于什么值(状态)
const pipeX = function (...args) {
return function (data) {
let ret = args[0](data)
for (let i = 1; i < args.length; i++) {
try { // 每一次调用函数都要检查程序bug
ret = args[i](ret)
} catch (error) {
console.log(`${error}Bug来源:第${i}个函数`);
break
}
}
return ret
}
}
// 测试
const initX = pipeX(len, log, pow, a, pow, log) // a未定义
initX(100)
/*
len执行
log执行,打印结果为 110
pow执行
TypeError: args[i] is not a functionBug来源:第3个函数
*/