javascript函数柯里化

240 阅读2分钟

函数柯里化

在计算机科学中,柯里化(Currying)是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果的新函数的技术。这个技术由 Christopher Strachey 以逻辑学家 Haskell Curry 命名的,尽管它是 Moses Schnfinkel 和 Gottlob Frege 发明的。

方便的理解

我们将一个接受多个参数的函数fn(a, b, c)转换成可以一个一个接受参数的函数fn(a)(b)(c),如果要执行该函数,所需参数传满即可fn(a)(b)(c)

举个例子:

let fn1 = function (a, b, c) {
  return a + b + c
}

console.log(fn(1, 2, 3)) // 6

let fn2 = currying(fn1);

console.log(fn2(1)) // function(){...}
console.log(fn2(2)) // function(){...}
console.log(fn2(3)) // 6
console.log(fn2(3)(4)(5)) // 12

这里我们将函数fn1带入函数currying中,返回出来的函数fn2就是被柯里化后的函数。

当我们传入前两个参数的时候,函数fn2并不会立即计算,而是返回一个函数。当我们继续向函数内传入参数到达三个时,函数才会返回值。

currying化的好处

1. 参数复用

这个很好理解,在上面的例子中,如果我们将fn2(1)返回的函数复制给一个变量,我们就复用了这里的参数1

let fn1 = function (a, b, c) {
  return a + b + c
}

let fn2 = currying(fn1);
let fn3 = fn2(1)(2);

console.log(fn3(3)) // 6
console.log(fn3(4)) // 7
console.log(fn3(10)) // 13
console.log(fn3(30)) // 33

2. 延迟计算

我们经常使用的Function.prototype.bind的实现机制就是柯里化。

Function.prototype.bind = function (context) {
    var _this = this
    var args = Array.prototype.slice.call(arguments, 1)
 
    return function() {
        return _this.apply(context, args)
    }
}

3. 动态生成函数

假设我们有一个函数,需要通过判断来执行不同的代码块。

let on = function(element, event, handler) {
  if (document.addEventListener) {
    if (element && event && handler) {
      element.addEventListener(event, handler, false);
    }
  } else {
    if (element && event && handler) {
      element.attachEvent('on' + event, handler);
    }
  }
}

这样写显然不太优雅,我们换一种写法。

let on = function(isSupport = document.addEventListener, element, event, handler) {
  if (isSupport) {
    return element.addEventListener(event, handler, false);
  } else {
    return element.attachEvent('on' + event, handler);
  }
}

发现什么了吗?

如果此时我们将on函数柯里化,就可以动态生成针对不同环境的事件监听函数了。

柯里化实现

那么,我们如何来实现一个柯里化的函数呢? 直接上代码吧

function curryIt(fn) {
  // 参数fn函数的参数个数
  var n = fn.length;
  var args = [];
  return function(arg) {
    args.push(arg);
    if (args.length < n) {
      return arguments.callee; // 返回这个函数的引用
    } else {
      return fn.apply(this, args); // 调用函数
    }
  };
}