Currying
柯里化:把多参函数转换为单参函数(是函数式编程的重要概念)。
思路:获取函数形参长度,每一次单参函数的执行目的是获取并保存参数,待最后一次单参函数执行把之前收集的参数传递给原函数执行。
const curry = fn => {
let length = fn.length
const args = []
return function genFn(arg){
args.push(arg)
if (--length === 0) return fn(...args)
return genFn
}
}
// test case
function sum(a, b, c){
return a + b + c
}
curry(sum)(1)(2)(3)
// curry(sum)(1,2)(3) 报错
但是碰到非全柯里化调用则会报错,稍作修改:非单参调用则根据调用实参个数对应修改,另外使用 apply。
const curry = fn => {
let length = fn.length
const args = []
return function genFn(..._args){
args.push(..._args)
if ((length -= _args.length) === 0) return fn.apply(this, args)
return genFn
}
}
// test case
function sum(a, b, c){
return a + b + c
}
console.log(curry(sum)(1)(2)(3))
console.log(curry(sum)(1,2)(3))
console.log(curry(sum)(1)(2,3))
console.log(curry(sum)(1,2,3))
Uncurrying
反柯里化:可以理解为方法的泛化,使特定对象的方法被指定对象借用,有点 call/apply 的感觉是吧,没错就是利用 call/apply 实现。
Function.prototype.uncurry = function(){
const fn = this
return (...args) => {
Function.prototype.call.apply(fn, args)
// 可理解成 fn.call(...args)
}
}
结合用法解释下:
Function.prototype.uncurry = function(){
const fn = this
return (...args) => {
Function.prototype.call.apply(fn, args)
// 执行下面的 push(obj, 'Sean', 'Messi')
// -> Function.prototype.call.apply(Array.prototype.push, [obj, 'Sean', 'Messi'])
// -> Array.prototype.push.call(obj, 'Sean', 'Messi')
}
}
const push = Array.prototype.push.uncurry()
const obj = {}
push(obj, 'Sean', 'Messi') // 对象借用数组的 push 方法
console.log(obj)
// {0: "Sean", 1: "Messi", length: 2}
参考
Javascript 中有趣的反柯里化技术
Uncurrying “this” in JavaScript
JS进阶篇—JS中的反柯里化( uncurrying)