A-A+

简单理解JavaScript中的柯里化和反柯里化

2018年10月31日 javascript, 前端技术 暂无评论 阅读 19 views 次

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

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

柯里化(curring)

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

但问题来了,我们有很多种方法可以实现它,比如最简单的:

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

overtime(3.5); // 第一天 overtime(4.5); // 第二天 overtime(2.1); // 第三天 //... console.log(monthTime); // 10.1 

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

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

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

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

var overtime = (function() {
  var args = [];

  return function() {
    if(arguments.length === 0) {
      var time = 0;
      for (var i = 0, l = args.length; i < l; i++) {
        time += args[i];
      }
      return time;
    }else {
      [].push.apply(args, arguments);
    }
  }
})();

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

console.log( overtime() );    //

反柯里化

作用

扩展函数适用范围的方法
把函数也当作普通数据来使用, 当函数名本身是个变量的时候, 这种调用方法特别方便.
扩大函数的适用性,使本来作为特定对象所拥有功能的函数可以对全体对象使用

函数柯里化,是固定部分参数,返回一个接受剩余参数的函数,也称为部分计算函数,目的是为了缩小适用范围,创建一个针对性更强的函数。

那么反柯里化函数,从字面讲,意义和用法跟函数柯里化相比正好相反,扩大适用范围,创建一个应用范围更广的函数。使本来只有特定对象才适用的方法,扩展到更多的对象。

Function.prototype.currying = function() {
    var that = this;
    return function() {
        return Function.prototype.call.apply(that, arguments);
    }
}

1 为Function原型添加unCurrying方法,这样所有的function都可以被借用;

2 返回一个借用其它方法的函数,这是目的;

3 借用call方法实现,但call方法参数传入呢?借用apply,至此完毕。

Function.prototype.unCurrying = function () {
    var f = this;
    return function () {
        var a = arguments;
        return f.apply(a[0], [].slice.call(a, 1));
    };
};
Function.prototype.unCurrying = function () {
    return this.call.bind(this);
};

原理都相同,最终是把this.method转化成 method(this,arg1,arg2....)以实现方法借用和this的泛化。

鸭式辩型:如果一个对象可以像鸭子一样走路,游泳,并且嘎嘎叫,就认为这个对象是鸭子,哪怕它并不是从鸭子对象继承过来的。

故事:
很久以前有个皇帝喜欢听鸭子呱呱叫,于是他召集大臣组建一个一千只鸭子的合唱团。大臣把全国的鸭子都抓来了,最后始终还差一只。有天终于来了一只自告奋勇的鸡,这只鸡说它也会呱呱叫,好吧在这个故事的设定里,它确实会呱呱叫。 后来故事的发展很明显,这只鸡混到了鸭子的合唱团中。— 皇帝只是想听呱呱叫,他才不在乎你是鸭子还是鸡呢。
这个就是鸭子类型的概念

在javascript里面,很多函数都不做对象的类型检测,而是只关心这些对象能做什么。

Array构造器和String构造器的prototype上的方法就被特意设计成了鸭子类型。这些方法不对this的数据类型做任何校验。这也就是为什么arguments能冒充array调用push方法.
function ArrayPush() {
    var n = TO_UINT32(this.length);
    var m = %_ArgumentsLength();
    for (var i = 0; i < m; i++) {
        this[i + n] = %_Arguments(i); //属性拷贝
        this.length = n + m; //修正length
        return this.length;
    }
}

这就给对象冒充创造了条件,也就我们讨论的函数柯反里化unCurrying。反柯里化其实反映的是一种思想,扩大方法的适用范围!

看下面例了,让一个普通对象具备push方法:

var push = Array.prototype.push.unCurrying(),
obj = {};
push(obj, 'first', 'two');
console.log(obj);
/*obj {
: "first",
: "two"
}*/

obj被push了两个元素:first 和two,同时还具备了一个length属性,其实我们创建了一类数组对象。

var toUpperCase = String.prototype.toUpperCase.unCurrying();
console.log(toUpperCase('avd')); // AVD

function AryUpper(ary) {
    return ary.map(toUpperCase);
}

console.log(AryUpper(['a', 'b', 'c'])); // ["A", "B", "C"]

只是方法都可以借用。包括call方法。

var call = Function.prototype.call.unCurrying();
function $(id) {
    return this.getElementById(id);
}
var demo = call($, document, 'demo');
console.log(demo);

这似乎看起来相对于前两个例子,比较难理解,其实一句话就可以解释:document借用了$方法,并替换了其中的this。

在函数柯里化例子中bind的例子中,柯里化的目的是为了固定可变参数this。而反柯里化,把原来拥有方法的this泛化了,泛化到所有对象都可以借用,也就是替代当前拥有方法的this。

这里,希望不要混淆,这里的this并不是指上例中的this,上例中的this只是因为借用的方法是call。

更有趣的是,unCurrying本身也是方法,它是否可以被借用呢?答案是肯定的。这就是js的奇妙之处,反柯里化的奇妙之处。

var unCurrying = Function.prototype.unCurrying.unCurrying();
var map = unCurrying(Array.prototype.map);
var sq = map([1, 2, 3],
function(n) {
    return n * n;
});
console.log(sq); // [1,4,9]

无论是柯里化还是反柯里化,其实反应的都是一种设计思想

打赏作者
如果文章对您有所帮助请打赏支持本站发展。

您的支持将鼓励我们继续创作!

[微信] 扫描二维码打赏

[支付宝] 扫描二维码打赏

给我留言

您必须 登录 才能发表留言!

Copyright © 前端技术分享休闲玩耍去处分享-大一网 保留所有权利.   Theme  Ality

用户登录 ⁄ 注册

分享到: