柯里化
描述
柯里化算是特殊的偏函数,把一个多参数函数转换成多个单参数函数,也就是说把一个具有n个参数的函数转换成n个一元函数
示例
// 正常写法
function add (a, b) {
return a + b
}
const resAdd = add(2, 3)
console.log(resAdd) // 5
// 柯里化
function currieAdd (a) {
return function (b) {
return a + b
}
}
const resCurrie = currieAdd(2)(3)
console.log(resCurrie) // 5
通用写法
上面的示例代码比较简单,如果有十几个参数呢?所以需要一个通用柯里化的写发
-
代码的关键点在于
- 闭包,调用柯里化函数(currie)返回另外一个函数(_myFn),通过闭包缓存真正执行运算的函数(fn)和参数(args)
- 通过返回的函数传递参数,并进行判断,如果参数已经传递够了,就执行函数(fn)并返回结果,如果参数还没传递完,则继续返回函数(_myFn)接收参数
// 柯里化一个函数
function currie (fn) {
// 利用闭包缓存传递进来的参数
const args = []
return function _myFn (arg) {
args.push(arg)
if (args.length === fn.length) {
// 说明参数已经传递够了,执行fn函数并返回结果
return fn.apply(null, args)
} else {
// 发现参数没有传递完,则返回_myfn函数,继续调用
return _myFn
}
}
}
// 示例 1
function add_1 (a, b) {
return a + b
}
const currieAdd_1 = currie(add_1)
const res1 = currieAdd_1(2)(3)
console.log(res1) // 5
// 示例 2
function add_2 (a, b, c, d, e) {
return a + b + c + d + e
}
const currieAdd_2 = currie(add_2)
const res2 = currieAdd_2(1)(2)(3)(4)(5)
console.log(res2) // 15
偏函数
描述
偏函数又叫局部应用,固定函数的一个或多个参数,也就是说把一个n
元函数转换成一个n - x
元函数
示例
// 封装一个ajax方法
function ajax (url, data, callback) {
...
}
// 调用ajax方法,
ajax('http://lyn.com', { a: 'aa' }, function () { // 回调 A })
ajax('http://lyn.com', { b: 'bb' }, function () { // 回调 B })
...
ajax('http://lyn.com', { y: 'yy' }, function () { // 回调 Y })
发现以上所有的调用,第一个参数都一样,这时候就需要想有没有什么方法可以简化这种重复参数的填写,
偏函数
出马
// 偏函数
function partial (url) {
return function (data, cb) {
ajax(url, data, cb)
}
}
// 调用偏函数
const partialAjax = partial('http://lyn.com')
// 发送ajax请求
partialAjax({ a: 'aa' }, function () { // 回调 A })
partialAjax({ b: 'bb' }, function () { // 回调 B })
...
partialAjax({ y: 'yy' }, function () { // 回调 Y })
通用写法
代码的关键点
偏函数的代码比较简单,就是利用闭包缓存实际的执行方法(fn)和与之的参数(preset),然后返回一个接收剩余参数的方法,方法的实现就是执行fn并返回结果
function partial (fn, ...preset) {
return function (...args) {
return fn.apply(null, preset.concat(args))
}
}
// 示例, 通过一个简单的add方法来模拟
function add (a, b, c, d) {
return a + b + c + d
}
// 多次调用传递的前两个参数是一样的
// add(1, 2, 3, 4)
// add(1, 2, 5, 6)
const partialAdd = partial(add, 1, 2)
const res1 = partialAdd(3, 4)
console.log(res1) // 10
const res2 = partialAdd(5, 6)
console.log(res2) // 14
反柯里化
说明
柯里化其实就是偏函数的特殊情况,所以在反柯里化这里就之说偏函数,我觉得这样更合适
对比 —— 偏函数、反柯里化
- 偏函数:偏函数是对高阶函数的降阶处理,再朴素点的描述就是,降低函数的通用性,创建一个针对性更强的函数,比如上面讲的
偏函数
部分的ajax
和partialAjax
- 反柯里化:和偏函数刚好相反,增加方法的适用范围(即通用性)
通用代码
Function.prototype.uncurrie = function (obj) {
// 参数obj是需要操作的对象
// 这里的this是指obj对象需要借用的方法,比如示例中的Array.prototype.push
const fnObj = this
return function (...args) {
// 难点,以下代码相当于:fnObj.call(obj, ...args), 没理解请看下面的 “代码解析” 部分
return Function.prototype.call.apply(fnObj, [obj, ...args])
}
}
// 示例,导出Array.prototype.push方法给对象使用
const obj = { a: 'aa' }
const push = Array.prototype.push.uncurrie(obj)
push('b')
push('c')
console.log(obj) // {0: "b", 1: "c", a: "aa", length: 2}
代码解析
这部分内容负责解析上面的
通用代码
- 首先声明,个人觉得这个通用代码是没必要的,因为这段通用代码的本质就是
call、apply
,通过call、apply改变方法的this上下文,使得对象可以使用不属于它的方法,这也是反柯里化的本质,增强方法的使用范围- 这段通用代码的难点在于
Function.prototype.call.apply(fnObj, [obj, ...args])
这句,以下解析采用通用代码
中的示例代码
- 以下解释需要你熟悉
apply、call
方法的源码实现,如果不熟悉请参考 javascript源码解析,里面的call、apply两部分的源码解析会回答你的疑问- 正式开始解析
通用代码
,通过通用代码
中的示例
代码进行讲解通用代码
其实就是个闭包,执行Array.prototype.push.uncurrie(obj)
,传递一个需要操作的对象(const obj = {a: 'aa'}),其中fnObj = Array.prototype.push
,这时向外面return一个接收参数的函数- 返回的函数中就一句代码:
return Function.prototype.call.apply(fnObj, [obj, ...args])
,
- 上面的代码可以翻译为:
return Function.prototype.call.apply(Array.prototype.push, [{a: 'aa'}, ...args])
- 再进一步翻译(需要了解call、apply的原理,不明白请参考javascript源码解析):
return Array.prototype.push.call({a: 'aa'}, ...args)
,这句就等同于:Arrray.prototype.push.call(obj, 'b')
,看到这里就会明白我开始说的 “声明” 部分的意思了
总结
柯里化其实就是特殊的偏函数,偏函数的本质就是通过调用函数,预置一部分参数,然后返回一个参数更少但针对性更强的函数;而反柯里化,不知道为啥叫反柯里化,感觉应该叫反偏函数更好一点,反柯里化作用和偏函数相反,它的本质是增强一个函数的使用范围,让一个对象可以使用不属于对象自己的方法,就像apply、call、bind(也有偏函数的作用)的作用,而事实上反柯里化就是通过apply、call方法实现的
-
偏函数都用在哪些地方
- 需要减少参数的地方
- 需要延迟计算的地方
- Function.prototype.bind其实就是偏函数的应用
-
反柯里化都用在哪些地方
- 一个对象需要借用其它对象的方法时用反柯里化