函数柯里化

字数 699阅读 202

思考:执行add(1,2,3)(2)()就能输出1+2+3+2=8

const adder = function () {
    let _args = [];
    return function () {
      if (arguments.length === 0) {
        return _args.reduce(function (a, b) {
          return a + b;
        });
      }
      [].push.apply(_args, [].slice.call(arguments));
      return arguments.callee;
    }
  };
  const sum = adder();

  console.log(sum);     // Function

  sum(100, 200)(300);    // 调用形式灵活,一次调用可输入一个或者多个参数,并且支持链式调用
  sum(400);
  console.log(sum());   // 1000 (加总计算)

柯里化(curring)

柯里化又称部分求值,字面意思就是不会立刻求值,而是到了需要的时候再去求值。

栗子1

我们有这样一个场景,记录程序员一个月的加班总时间,那么好,我们首先要做的是记录程序员每天加班的时间,然后把一个月中每天的加班的时间相加,就得到了一个月的加班总时间。原文

let monthTime = 0;

function overtime(time) {
 return monthTime += time;
}

overtime(3.5);    // 第一天
overtime(4.5);    // 第二天
overtime(2.1);    // 第三天
//...

console.log(monthTime);    // 10.1

每次传入加班时间都进行累加,这样当然没问题,但你知道,如果数据量很大的情况下,这样会大大牺牲性能。

那怎么办?这就是柯里化要解决的问题。

其实我们不必每天都计算加班时间,只需要保存好每天的加班时间,在月底时计算这个月总共的加班时间,所以,其实只需要在月底计算一次就行。

下面的overtime函数还不是一个柯里化函数的完整实现,但可以帮助我们了解其核心思想:

const overtime = (function () {
    let args = []
    return function () {
      if (arguments.length === 0) {
        let time = 0
        for (let i = 0; i < args.length; i++) {
          time += args[i]
        }
        return time
      } else {
        [].push.apply(args, arguments) // apply 将数组转化为一个个参数传入 push 中
      }
    }
  })()
  overtime(3.5);    // 第一天
  overtime(4.5);    // 第二天
  overtime(2.1);    // 第三天
  //...

console.log( overtime() );    // 10.1

柯里化的作用

  • 延迟计算。上面的例子已经比较好低说明了。
  • 参数复用。当在多次调用同一个函数,并且传递的-参数绝大多数是相同的,那么该函数可能是一个很好的柯里化候选。
  • 动态创建函数。这可以是在部分计算出结果后,在此基础上动态生成新的函数处理后面的业务,这样省略了重复计算。或者可以通过将要传入调用函数的参数子集,部分应用到函数中,从而动态创造出一个新函数,这个新函数保存了重复传入的参数(以后不必每次都传)。

栗子2

 const addEvent = function(el, type, fn, capture) {
     if (window.addEventListener) {
         el.addEventListener(type, function(e) {
             fn.call(el, e)
         }, capture)
     } else if (window.attachEvent) {
         el.attachEvent("on" + type, function(e) {
             fn.call(el, e)
         })
     } 
 }

每次添加事件处理都要执行一遍if...else...,其实在一个浏览器中只要一次判定就可以了,把根据一次判定之后的结果动态生成新的函数,以后就不必重新计算。

const addEvent = (function () {
    if (window.addEventListener) {
      return function (el, sType, fn, capture) {
        el.addEventListener(sType, function (e) {
          fn.call(el, e)
        }, (capture))
      }
    } else if (window.attachEvent) {
      return function (el, sType, fn, capture) {
        el.attachEvent("on" + sType, function (e) {
          fn.call(el, e)
        })
      }
    }
  })()

反柯里化(uncurring)

反柯里化的作用是,当我们调用某个方法,不用考虑这个对象在被设计时,是否拥有这个方法,只要这个方法适用于它,我们就可以对这个对象使用它。

Function.prototype.uncurring = function () {
    const self = this
    return function () {
      const obj = Array.prototype.shift.call(arguments)
      return self.apply(obj, arguments)
    }
  }
  const push = Array.prototype.push.uncurring();

  //测试一下
  (function () {
    push(arguments, 4);
    console.log(arguments); //[1, 2, 3, 4]

参考文章

浅析 JavaScript 中的 函数 uncurrying 反柯里化
浅析 JavaScript 中的 函数 uncurrying 柯里化
简单理解JavaScript中的柯里化和反柯里化

推荐阅读更多精彩内容