阅读 1400

柯里化、偏函数、反柯里化

柯里化

  • 描述

柯里化算是特殊的偏函数,把一个多参数函数转换成多个单参数函数,也就是说把一个具有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
复制代码

反柯里化

  • 说明

柯里化其实就是偏函数的特殊情况,所以在反柯里化这里就之说偏函数,我觉得这样更合适

  • 对比 —— 偏函数、反柯里化

  • 偏函数:偏函数是对高阶函数的降阶处理,再朴素点的描述就是,降低函数的通用性,创建一个针对性更强的函数,比如上面讲的偏函数部分的ajaxpartialAjax
  • 反柯里化:和偏函数刚好相反,增加方法的适用范围(即通用性)
  • 通用代码

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其实就是偏函数的应用
  • 反柯里化都用在哪些地方

    • 一个对象需要借用其它对象的方法时用反柯里化
文章分类
前端
文章标签