柯里化与反柯里化

媒介

柯里化,能够明白为
提早吸收部份参数,耽误实行,不马上输出结果,而是返回一个接收盈余参数的函数。因为如许的特征,也被称为部份盘算函数。柯里化,是一个逐渐吸收参数的历程。在接下来的理会中,你会深入体会到这一点。

反柯里化,是一个泛型化的历程。它使得被反柯里化的函数,能够吸收更多参数。目标是建立一个更普适性的函数,能够被差别的对象运用。有鸠占鹊巢的结果。

一、柯里化

1.1 例子

完成 add(1)(2, 3)(4)() = 10 的结果

依题意,有两个症结点要注意:

  • 传入参数时,代码不实行输出结果,而是先影象起来
  • 当传入空的参数时,代表能够举行真正的运算

完全代码以下:

function currying(fn){
    var allArgs = [];

    return function next(){
        var args = [].slice.call(arguments);

        if(args.length > 0){
            allArgs = allArgs.concat(args);
            return next;
        }else{
            return fn.apply(null, allArgs);
        }
    } 
}
var add = currying(function(){
    var sum = 0;
    for(var i = 0; i < arguments.length; i++){
        sum += arguments[i];
    }
    return sum;
});

1.2 影象传入参数

由因而耽误盘算结果,所以要对参数举行影象。
这里的完成体式格局是采纳闭包。

function currying(fn){
    var allArgs = [];

    return function next(){
        var args = [].slice.call(arguments);

        if(args.length > 0){
            allArgs = allArgs.concat(args);
            return next;
        }
    } 
}

当实行var add = currying(...)时,add变量已指向了next要领。此时,allArgsnext要领内部有引用到,所以不能被GC接纳。也就是说,allArgs在该赋值语句实行后,一向存在,构成了闭包。
依托这个特征,只需把吸收的参数,不停放入allArgs变量举行存储即可。
所以,当arguments.length > 0时,就能够将吸收的新参数,放到allArgs中。
末了返回next函数指针,构成链式挪用。

1.3 推断触发函数实行前提

题意是,空参数时,输出结果。所以,只需推断arguments.length == 0即可实行。
别的,因为盘算结果的要领,是作为参数传入currying函数,所以要应用apply举行实行。
综合上述思索,就能够获得以下完全的柯里化函数。

function currying(fn){
    var allArgs = []; // 用来吸收参数

    return function next(){
        var args = [].slice.call(arguments);

        // 推断是不是实行盘算
        if(args.length > 0){
            allArgs = allArgs.concat(args); // 网络传入的参数,举行缓存
            return next;
        }else{
            return fn.apply(null, allArgs); // 相符实行前提,实行盘算
        }
    } 
}

1.4 总结

柯里化,在这个例子中能够看出很明显的行动规范:

  • 逐渐吸收参数,并缓存供后期盘算运用
  • 不马上盘算,延后实行
  • 相符盘算的前提,将缓存的参数,一致传递给实行要领

1.5 扩大

完成 add(1)(2, 3)(4)(5) = 15 的结果。
许多人这里就犯嘀咕了:我怎样晓得实行的机遇?
实在,这里有个忍者武艺:valueOftoString
js在猎取当前变量值的时刻,会依据语境,隐式挪用valueOftoString要领举行猎取须要的值。
那末,完成起来就很简朴了。

function currying(fn){
    var allArgs = [];

    function next(){
        var args = [].slice.call(arguments);
        allArgs = allArgs.concat(args);
        return next;
    }
    // 字符范例
    next.toString = function(){
        return fn.apply(null, allArgs);
    };
    // 数值范例
    next.valueOf = function(){
        return fn.apply(null, allArgs);
    }

    return next;
}
var add = currying(function(){
    var sum = 0;
    for(var i = 0; i < arguments.length; i++){
        sum += arguments[i];
    }
    return sum;
});

二、反柯里化

2.1 例子

有以下轻提醒类。如今想要零丁运用其show要领,输出新对象obj中的内容。

// 轻提醒
function Toast(option){
  this.prompt = '';
}
Toast.prototype = {
  constructor: Toast,
  // 输出提醒
  show: function(){
    console.log(this.prompt);
  }
};

// 新对象
var obj = {
    prompt: '新对象'
};

用反柯里化的体式格局,能够这么做

function unCurrying(fn){
    return function(){
        var args = [].slice.call(arguments);
        var that = args.shift();
        return fn.apply(that, args);
    }
}

var objShow = unCurrying(Toast.prototype.show);

objShow(obj); // 输出"新对象"

2.2 反柯里化的行动

  • 非我之物,为我所用
  • 增添被反柯里化要领吸收的参数

在上面的例子中,Toast.prototype.show要领,本来是Toast类的私有要领。跟新对象obj没有半毛钱关联。
经由反柯里化后,却能够为obj对象所用。
为何能被obj所用,是因为内部将Toast.prototype.show的上下文从新定义为obj。也就是用apply改变了this指向。
而完成这一步骤的历程,就须要增添反柯里化后的objShow要领参数。

2.3 另一种反柯里化的完成

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

// 运用
var objShow = Toast.prototype.show.unCurrying();
objShow(obj);

这里的难点,在于明白Function.prototype.call.apply(self, arguments);
能够分拆为两步:

1) Function.prototype.call.apply(...)的剖析

能够看成是callFunction.apply(...)。如许,就清楚许多。
callFunctionthis指针,被apply修改成self
然后实行callFunction -> callFunction(arguments)

2) callFunction(arguments)的剖析

call要领,第一个参数,是用来指定this的。所以callFunction(arguments) -> callFunction(arguments[0], arguments[1-n])
由此能够得出,反柯里化后,第一个参数,是用来指定this指向的。

3)为何要用apply(self, arguments)
假如运用apply(null, arguments),因为null对象没有call要领,会报错。

三、实战

3.1 推断变量范例(反柯里化)

var fn = function(){};
var val = 1;

if(Object.prototype.toString.call(fn) == '[object Function]'){
    console.log(`${fn} is function.`);
}

if(Object.prototype.toString.call(val) == '[object Number]'){
    console.log(`${val} is number.`);
}

上述代码,用反柯里化,能够这么写:

var fn = function(){};
var val = 1;
var toString = Object.prototype.toString.unCurrying();

if(toString(fn) == '[object Function]'){
    console.log(`${fn} is function.`);
}

if(toString(val) == '[object Number]'){
    console.log(`${val} is number.`);
}

3.2 监听事宜(柯里化)


function nodeListen(node, eventName){
    return function(fn){
        node.addEventListener(eventName, function(){
            fn.apply(this, Array.prototype.slice.call(arguments));
        }, false);
    }
}

var bodyClickListen = nodeListen(document.body, 'click');
bodyClickListen(function(){
    console.log('first listen');
});

bodyClickListen(function(){
    console.log('second listen');
});

运用柯里化,优化监听DOM节点事宜。addEventListener三个参数不必每次都写。

跋文

实在,反柯里化和泛型要领一样,只是理念上有一些差别罢了。明白这类头脑即可。

    原文作者:leon
    原文地址: https://segmentfault.com/a/1190000015743329
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞