函数柯里化

原创 2016年05月30日 11:38:57

在介绍函数柯里化之前,先来学一下函数绑定。

函数绑定

函数绑定要创建一个函数,可以在特定的this环境中以指定参数调用另一个函数,该技巧常常和回调函数与事件处理程序一起调用,以便在将函数作为变量传递的同时保留代码执行环境。看个例子:

var handler = {
    message: "Event handled",

    handleClick: function  (event) {
        alert(this.message);
    }
}

var btn = document.getElementById("my-btn");
EventUtil.addHandler(btn, "click", handler.handleClick);

在上面的例子中,创建了一个叫做handler的对象。handler.handleClick()方法被分配为一个DOM按钮的事件处理程序,当按下该按钮时就调用该函数,显示一个警告框,虽然貌似警告框应该显示Event handled,然而实际上显示的是undefined。这个问题在于没有保存handler.handleClick()的环境,所以this对象最后是指向 dom按钮而非handler可以如下面例子修正这个问题:

var handler = {
    message: "Event handled",

    handleClick: function  (event) {
        alert(this.message);
    }
}

var btn = document.getElementById("my-btn");
EventUtil.addHandler(btn, "click", function  (event) {
    handler.handlerClick(event);
})

这个解决方案在onclick事件处理程序内使用了一个闭包直接调用handler.handleClick(),当然,这是特定于这段代码的解决方案。创建多个闭包可能会令代码难于理解和调试,因此,很多javaScript库实现了一个可以将函数绑定到指定环境的函数,这个函数一般叫bind(),一个简单的bind函数接收一个函数和一个环境,并返回一个在给定环境中调用给定函数的函数,并且将所有参数原封不动的传递进去。

function bind (fn, context) {
    return function  () {
        return fn.apply(context, arguments);
    }
}

这个函数似乎简单,但其功能是非常强大的,在bind()中创建一个闭包,闭包使用apply()调用传入的函数,并给apply()传递context对象和参数。注意这里使用的arguments对象是内部函数的,而非bind()的,当调用返回的函数时,他会在给定环境中执行被传入的函数并给出所有参数。bind()函数按如下方式使用

var handler = {
    message: "Event handled",

    handleClick: function  (event) {
        alert(this.message);
    }
}

var btn = document.getElementById("my-btn");
EventUtil.addHandler(btn, "click", bind(handled.handlerClick, handler));

在这个例子中,我们用bind()函数创建了一个保持了执行环境的函数,并将其传给EventUtil.addHandler()。event对象也被传给了该函数,如下所示

var handler = {
    message: "Event handled",

    handleClick: function  (event) {
        alert(this.message+":"+event.type);
    }
}

var btn = document.getElementById("my-btn");
EventUtil.addHandler(btn, "click", bind(handled.handlerClick, handler));

handler.handleClick()方法和平时一样获得了event对象,因为所有的参数都通过函数被绑定的函数直接传给了它。
es5为所有函数定义了一个原生的bind()方法,换句话说,你不用再自己定义bind()函数了,而是可以直接在函数上调用这个方法,例如:

var handler = {
    message: "Event handled",

    handleClick: function  (event) {
        alert(this.message+":"+event.type);
    }
}

var btn = document.getElementById("my-btn");
EventUtil.addHandler(btn, "click", handler.handlerClick.bind(handler));

原生的bind()方法与前面介绍的自定义bind()方法类似,都是要传入作为this值的对象。
只要是将某个函数指针以值的方式进行传递,同时该函数必须在特定环境中执行,被绑定的函数效用就突显出来了。他们主要用于事件处理程序以及setTimeout()和setInterval(),然而,被绑定函数与普通函数相比有更多的开销,他们需要更多内存,同时也因为多重函数调用稍微慢一点,所以最好只在必要时调用。

函数柯里化

与函数绑定紧密相关的主题是函数柯里化,他用于创建已经设置好了一个或多个参数的函数,函数柯里化的基本方法和函数绑定是一样的,使用一个闭包返回一个函数。两者的区别在于,当函数被调用时,返回的函数还需要设置一些传入的参数,

function add (num1, num2) {
    return num1 + num2;
}
function curriedAdd (num2) {
    return add(5, num2);
}
alert(add(2, 3))     //5
alert(curriedAdd(3));//8

这段代码定义了两个函数add和curriedAdd(),后者本质上是在任何情况下第一个参数为5的add()版本,尽管从技术上来说curriedAdd()并非柯里化的函数,但它很好的展示了其概念。下面是创建柯里化函数的通用方式

function curry (fn) {
    var args = Array.prototype.slice.call(arguments, 1);
    return function  () {
        var innerArgs = Array.prototype.slice.call(arguments);
        var finalArgs = args.concat(innerArgs);
        return fn.apply(null, finalArgs);
    }
}

curry()函数的主要工作就是将被返回函数的参数进行排序,curry()的第一个参数是要进行柯里化的函数,其他参数是要传入的值,为了获取第一个参数之后的所有参数,在argumens对象上调用了slice()方法,并传入了参数1表示被返回的数组包含从第二个参数开始的所有参数,然后args数组包含了来自外部的函数的参数。在内部函数中,创建了innerArgs数组用来存放所有传入的参数,(又一次用到了slice),有了存放来自外部函数和内部函数的参数数组后,就可以使用concat()方法将他们组合为finalArgs,然后使用apply()将结果传递给该函数,注意这个函数并没有考虑到执行环境,所以调用apply()时第一个参数是null,curry()函数就可以按以下方式应用。

function add (num1, num2) {
    return num1 + num2;
}
var curriedAdd = curry(add, 5);
alert(curriedAdd(3));           //8

在这个例子中,创建了第一个参数绑定为5的add()的柯里化版本。当调用curriedAdd()并传入3时,3会成为add()的第二个参数,同时第一个参数仍然是5,最后结果便是和8,你也可以像下面例子这样给出所有的函数参数:

function add (num1, num2) {
    return num1 + num2;
}
var curriedAdd = curry(add, 5, 12);
alert(curriedAdd());           //17

在这里,柯里化add()函数两个参数都提供了,所以以后就无需再传递他们了。
函数柯里化还常常作为函数绑定的一部分包含其中,构造出更为复杂的bind()函数了,例如:

function bind (fn, context) {
    var args = Array.prototype.slice.call(arguments, 2);
    return function  () {
        var innerArgs = Array.prototype.slice.call(arguments);
        var finalArgs = args.concat(innerArgs);
        return fn.apply(context, finalArgs);
    }
}

对curry()函数的主要更改在于传入的参数个数,以及它如何影响代码的结果,curry()仅仅接收一个要包裹的函数作为参数,而bind()函数同时接收函数和一个object对象。这表示给被绑定的函数的参数是从第三个开始而不是第二个,这就要更改slice()的第一个调用,另一处更改是在倒数第三行将object对象传给了apply()当使用bind()时。他会返回绑定要给定环境的函数,并且可能他其中某些函数参数已经设定好,当你想除了event对象在额外给事件处理程序传递参数时,这非常有用,例如

var handler = {
    message: "Event handled",

    handleClick: function  (name, event) {
        alert(this.message+":"+event.type);
    }
}

var btn = document.getElementById("my-btn");
EventUtil.addHandler(btn, "click", bind(handler.handleClick, handler, "my-btn"));

在这个更新的例子中,handler.handleClick()方法接受了两个参数:要处理的元素的名字和event对象,作为第三个参数传递给bind()函数的名字,又被传递给了,handler.handleClick().而handler.handleClick()也会同时接收到event对象,es5的bind()方法也实现函数柯里化,只要在this的值之后再传入另一个参数即可。

var handler = {
    message: "Event handled",

    handleClick: function  (name, event) {
        alert(this.message+":"+event.type);
    }
}

var btn = document.getElementById("my-btn");
EventUtil.addHandler(btn, "click", handler.handleClick.bind(handler, "my-btn"));

javaScript中的柯里化函数和绑定函数提供改了强大的动态函数创建功能,使用bind()还是curry()要根据是否需要object对象响应开决定,他们都能用于创建复杂的算法和功能,当然两者都不应滥用,因为每个函数都会带来额外的开销。

柯里化的特点是: 降低通用性,提高专用性。,函数柯里化给我们带来的是:解决问题的一种逻辑思维方式。

版权声明:本文为博主原创文章,未经博主允许不得转载。

相关文章推荐

柯里化函数之Javascript

柯里化函数之Javascript 定义 根据定义来说,柯里化就是将一个接收“多个”参数的函数拆分成一个或者许多个接收“单一”参数的函数。定义看起来是比较抽象的,下面来举个例子: ...

从函数的柯里化,看Redux中间件的实现

简介:同步请求时,dispatch(action)发出请求,到接受请求reducer(state,action)是同步的。如果当我们需要异步请求时,状态应该变为dispatch(action)——wa...

详解JavaScript函数柯里化

详解JavaScript函数柯里化百度百科对柯里化的解释:在计算机科学中,柯里化(Currying)是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且...

惰性函数与柯里化函数

首先先说下 什么是惰性函数,顾名思义懒惰的函数,常用于在解决浏览器兼容性方面,举个例子 在一个方法里面可能会涉及到一些兼容性的问题,不同的浏览器对应不同的方法,第一次我们遍历这些方法找到最合适的那个...

Swift函数柯里化(Currying)简谈

大熊猫猪·侯佩原创或翻译作品.欢迎转载,转载请注明出处. 如果觉得写的不好请多提意见,如果觉得不错请多多支持点赞.谢谢! hopy ;) 下面简单说说Swift语言中的函数柯里化简单的说就是把接...
  • mydo
  • mydo
  • 2016-04-04 13:22
  • 1186

【JavaScript 】函数柯里化

柯里化:柯里化是这样的一个转换过程,把接受多个参数的函数变换成接受一个单一参数(译注:最初函数的第一个参数)的函数,如果其他的参数是必要的,返回接受余下的参数且返回结果的新函数。(译注) 函数柯里化用...

前端基础进阶(八):深入详解函数的柯里化

配图与本文无关 柯里化是函数的一个比较高级的应用,想要理解它并不简单。因此我一直在思考应该如何更加表达才能让大家理解起来更加容易。想了很久,决定先抛开柯里化这个概念不管,补充两个重要、但是容...

JAVA 8函数式编程(三):柯里化与惰性求值

百度百科里是这么定义柯里化的: 在计算机科学中,柯里化(Currying)是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果的新函数的技术...

scala柯里化函数

柯里化是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数, 并且返回接受余下的参数而且返回结果的新函数的技术。柯里化其实本身是固定一个可以预期的参数,并返回一个特定的函数,处理...

函数柯里化

含义:柯里化(Currying)是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果的新函数的技术。 因此柯里化的过程是逐步传参,逐步缩...
  • l505_
  • l505_
  • 2017-04-15 17:28
  • 121
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:深度学习:神经网络中的前向传播和反向传播算法推导
举报原因:
原因补充:

(最多只允许输入30个字)