函数的 柯里化和反柯里化

函数1

函数的柯里化

柯里化(currying)是把接收多个参数的函数变换成为接收一个部分参数的函数,并返回接收余下参数的新函数的技术。一般这个参数是一个。
可能咱们对这个解释不太明白。 如今咱们来思考一个简单的问题。 怎么定义一个只有一个参数的函数,实现加法运算。html

function curry(a){ // 建立不销毁的做用域,保存参数。
    return function(b){ // 返回函数求和
        return a + b;
    }
}

var f = curry(1); // f只是一个函数
console.log(f(2));    // 3

其实咱们把实现加法的函数转化成上面实例的函数就称为函数的柯里化。es6

函数柯里化

柯理化函数思想:一个js预先处理的思想;利用函数执行能够造成一个不销毁的做用域的原理,把须要预先处理的内容都储存在这个不销毁的做用域中,而且返回一个新函数,之后咱们执行的都是新函数,在新函数中把以前预先存储的值进行相关的操做处理便可。
并且一般状况下,这些参数是具备类似性质的。 一般是用于求值。因此函数的柯里化又叫作部分求值。一个currying的函数首先会接受一些参数,接受了这些参数以后,该函数并不会当即求值,而是继续返回另一个函数,刚才传入的参数在函数造成的闭包中被保存起来。待到函数被真正须要求值的时候,以前传入的全部参数都会被一次性用于求值。
其实就是 f(a,b,c,d) => g(a)(b)(c)(d)() 的过程
或者 f(a,b,c,d) => g(a,b)(c,d)()数组

柯里化的演示

首先给三个数的加法运算。缓存

function add_three(a, b, c){
    return a + b + c;
}

接着按照公式咱们 将 f(a,b,c) ===> g(a)(b)(c)() 的形式。闭包

我首先来改写成:app

function add(a){ 
    return function(b){
        return function(c){
            return a + b + c;
        }
    }
}

可是这种写法可能不通用。当参数有不少的时候,可能嵌套的层次就更多了。 其实咱们发现是利用闭包来储存了变量,那么咱们换一种思路来存储参数。咱们在改写:dom

var add = (function(){
    var args = [];  // 存放参数
    return function(){
        if(arguments.length === 0){  // 无参数就计算
            var sum = 0;
            for(var i = 0; i < args.length; i++){
                sum += args[i]
            }
            return sum;
        }else{  // 有参数就存起来
            [].push.apply(args, arguments);  // 只是存起来了
            return arguments.callee;  // 返回函数,方便链式调用
        }
    }
})()

在上面咱们能够看到柯里化,仍是重写了一个闭包函数,返回新函数来调用,并且咱们的柯里化函数和被柯里化函数结合很紧密。 那么咱们可不能够建立一个函数,这个函数就是用来柯里化其余函数。这样就能够作到解耦的作用了。函数

function currying(fn){
    var args = []; // 存放参数
    return function(){// 返回新函数,也就是柯里化以后的函数
        if(arguments.length === 0){  // 计算
            return fn.apply(fn,args);  // 调用以前的函数
        }else{ //  存储
            Array.prototype.push.apply(args, arguments);
            return arguments.callee; // 方便链式调用 g()()  这样的形式
        }
    } 
}

function add_three(a, b, c){
    return a + b + c;
}

var add = currying(add_three);
add(1);
console.log(add(2)(3)())

固然为了更加通用 咱们还能够改为学习

function currying(fn, ...arg_init){
    let args = arg_init || [];  // 存储参数   而且保存 柯里化时传入的参数。
    return function() {
        if(arguments.length === 0){ // 判断计算时机
            Let _args = args;
            args = []; // 将参数组置空, 避免重复计算。
            return fn.apply(this, _args); // 调用柯里化的函数
        }else{ // 缓存参数
            [].push.apply(args, arguments);
            return arguments.callee;
        }
    }
}

function add_(...args){
    var sum = 0;
    for(var i = 0; i < args.length; i++){
        sum += args[i];
    }
    return sum;
}

var add = currying(add_three,1);
add(2);
add(3,4);
add(5)(6);
console.log(add())

柯里化的做用

其实,在上面过程当中咱们也看到了,函数柯里化的特色了。this

  1. 参数是具备类似性的。
  2. 缓存数据,须要是在计算。
  3. 能够将某些参数提早固定。(var add = currying(add_three,1);) 就像这样的。。提早固定参数

那么接下来咱们看看柯里化函数有哪些应用场景

  1. 缩小函数适用范围,提升函数适用性(减小重复传递不变的部分参数)
function simpleURL(protocol, domain, path) {
    return protocol + "://" + domain + "/" + path;
}
var myurl = simpleURL('http', 'mysite', 'home.html');
var myurl2 = simpleURL('http', 'mysite', 'aboutme.html');

经过分析
就会发现有时候,咱们在用一些函数,会重复传递一些相同的参数,可是咱们又不能由于本身就去改为函数,那么咱们就能够利用柯里化来限制参数,返回新函数来调用。

myURL = currying(simpleURL,"http",'mysite');
myURL('home.html');
myURL('aboutme.html')
  1. 延迟执行
    就是以前求和的案例

  2. 固定异变因素
    提早把易变因素,传参固定下来,生成一个更明确的应用函数。最典型的表明应用,是bind函数用以固定this这个易变对象。
Function.prototype.bind = function(context) {
    var _this = this;
    var _args = Array.prototype.slice.call(arguments, 1);        
    return function() {
        return _this.apply(context, _args.concat( Array.prototype.slice.call(arguments)));
    }
}

反柯里化

Array.prototype上的方法本来只能用来操做array对象。但用call和apply能够把任意对象看成this传入某个方法,这样一来,方法中用到this的地方就再也不局限于原来规定的对象,而是加以泛化并获得更广的适用性。有没有办法把泛化this的过程提取出来呢?反柯里化(uncurrying)就是用来解决这个问题的。反柯里化主要用于扩大适用范围,建立一个应用范围更广的函数。使原本只有特定对象才适用的方法,扩展到更多的对象。
这里也就是要把obj.fun(arg1, arg2) 转换成为 fun1(obj, arg1, arg2) 的形式。其实就是,调用 uncurrying 并传入一个现有函数 fn, 反柯里化函数会返回一个新函数,该新函数接受的第一个实参将绑定为 fn 中 this的上下文,其余参数将传递给 fn 做为参数。

代码实现以下;

  1. 第一种:
function uncurrying(fn){
    return function(){  // 返回一个 反柯里化函数的新函数
        var args = [].slice.call(arguments,1); // 获取参数数组, 由于第一参数是咱们传入的对象自己。
        return fn.apply(arguments[0],args); // 其实本质仍是对象调用本来的函数
    }
}

举个例子来验证一下(数组的push)

push = uncurrying(Array.prototype.push);
var arr = [1];
push(arr,2);
console.log(arr); // [1,2]

其实,咱们想要的是第一个参数和后面的参数分割开。 因此上述的反柯里化函数还能够该改写成为:

function uncurrying(fn){
    return function(){  // 返回一个 反柯里化函数的新函数
        var context=[].shift.call(arguments);  // arguments去除第一个参数, 并赋值给新变量
        return fn.apply(context, arguments); // 其实本质仍是对象调用本来的函数
    }
}
  1. 第二种:
    那么我知道 call 方法和apply方法功能同样,只不过参数不同。因此能够写成:
var uncurrying= function (fn) {
    return function () {        
        return fn.call(...arguments); // es6的语法规则, 自动作参数展开,正好划分了参数
    }
};
  1. 第三种
    一样这是你常见的一种形式:(就是call也能够调用 apply 函数, 作参数的拆分)
var uncurrying= function (fn) {
    return function () {        
        return Function.prototype.call.apply(fn,arguments);
    }
};

因而可知 Function.prototype.call.apply(fn,arguments) 至关于 fn.call(arguments[0],argumnet[1]....)

  1. 第四种
    接下来,咱们知道bind是会返回一个函数,他和call相似,只不过不当即执行而是返回函数。那么咱们先去内层函数包裹(也就是第一个return),改写(对第2种):
    咱们知道bind也是具备和call以及apply的性质,并且他还返回一个函数。以前咱们也介绍过了,bind实际上是柯里化的函数,具备指定参数的做用,返回的新函数具备接收剩余参数的功能。
    针对第二种的改写(这个是方便你去理解这种形式的。。你能够参考 bind的 实现源码来看,上文有介绍)
var uncurrying= function (fn) {
    return fn.call.bind(arguments[0]); // es6的语法规则
};

第三种的改写,去掉内层函数

// 用函数表达式
var uncurrying = function(fn) {
    return Function.prototype.call.bind(fn);    
};

其实,也就是你常常见到的一种形式, push = Function.prototype.call.bind(Array.prototype.push); 的一层函数封装。

其实仍是一种你常见的简洁形式。

  1. 第五种
var uncurrying = Function.prototype.bind.bind(Function.prototype.call);

.
.
.
不解释了。。。。

反柯里化适用场景。

  1. 让对象的方法变共有函数, 这样其余函数就能够适用这个方法。 (也就是扩展了函数的适用范围)
  2. 固定函数反柯里化以后的 this 的指向....this指向的是第一个参数。

总结

其实学习是相互印证的过程,以前也写过bind的用法,可能仍是停留在一个初级阶段,记忆和简单会用,直到写完这篇才算是对bind有了一个深入的认识吧。尤为是最后的反柯里化函数的简写形式,真的是隐藏了不少东西。
阿姨不知道你能不能看明白...

算了仍是解释下第五种吧:
var uncurrying = Function.prototype.bind.bind(Function.prototype.call);
其实利用bind的源码实现原理,来去掉最后一个bind
就是变成下面的代码了,

var uncurrying = function(){
    return Function.prototype.bind.apply(Function.prototype.call,arguments)
}

咱们以前介绍过了apply的特性了,其实就至关于给第一个对象身上绑定了一个方法,调用结束以后删除该方法。 同理,上述代码就至关于给call绑定了bind的方法。call去调用bind的方法。 且将agrguments参数展开传入(这里也是有细节的), 因此就变成了下面的样子。

var uncurrying = function(){
    return Function.prototype.call.bind(...arguments);
}

由于咱们传入的参数是一个,因此就变成这样了, 是否是就是和第三种同样了?

var uncurrying = function(fn){
    return Function.prototype.call.bind(fn);
}

接着在去掉bind

var uncurrying = function(fn){
    return function(){
        Function.prototype.call.apply(fn,arguments);
    }
}

也就是(是否是很熟悉了) 这里之因此能够这么写,利用了一点技巧,偷换了一点概念。

var uncurrying = function(fn){
    return function(){
        fn.call(...arguments);
    }
}
相关文章
相关标签/搜索