浅谈函数柯里化

  关于函数柯里化的定义,我摘抄一段来自百度百科的原话:在计算机科学中,柯里化(Currying)是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果的新函数的技术。

  这段话听起来可能有一些抽象,但是如果用实际例子来解释可能会帮助我们更好地理解何为函数柯里化。看看下面这个问题,是一道前端面试中常考的题:

如何实现add(2)(3)(4) = 9

  当我第一次看到这个题目的时候我就在思考,add(2)后面为什么还能带(3)(4)呢?是不是因为add(2)返回的是一个函数,所以它后面可以继续接收参数,可是如果这样的话它要怎么返回具体数值呢?于是我想到了一个可以实现储存参数的关键 - 闭包!是经过自己无数次尝试以及尝试失败之后,最终向命运妥协,开始到网上查阅资料,于是发现了以下这段代码,读者可以先仔细阅读一下下面这段代码,后面我们再来慢慢解析究竟何为函数柯里化,为什么函数柯里化就能解决这个问题?

 1 let myAdd = (a, b, c) => a+b+c;
 2 function curry(fn, args){
 3     let len = fn.length;
 4     let _this = this;
 5     let _args = args || [];
 6     return function(){
 7         let args = Array.prototype.slice.apply(arguments);
 8         args = Array.prototype.concat.call(_args, args);
 9         // 当接收到的参数小于fn所需参数个数时,继续接收参数
10         if(args.length < len){
11             return curry.call(_this, fn, args);
12         }
13         return fn.apply(this, args);
14     }
15 }
16 let add = curry(myAdd);
17 console.log(add(2)(3)(4));  // 9
18 console.loh(add(2,3)(4));   // 9
19 console.log(add(2,3,4));    // 9

  在上面的代码中,myAdd是一个可以接收三个参数并且返回三数之和的函数。通过一顿操作之后,它可以不用一次性接收三个参数,而是慢慢接收,当发现接收到的参数达到3个之后再返回结果。这就涉及到函数柯里化的第一个特点 --- 参数复用(后面再进行详解)。通过例子我们可以看到,实现这个特点主要原因是利用了闭包,将接收到的参数存于_args中,由于闭包的原因这些参数在函数执行完之后并不会被释放掉。

  上面的curry方法,将每次调用fn时读入的参数用args来保存,并将本次读入的参数args与当前一共拥有的所有参数_args用concat方法连接起来,当参数个数符合fn的参数个数要求时,则调用fn。(笔者不才,没办法想到用什么通俗易懂的语言来描述这个过程,只能将自己的理解表述出来)

  但是这个例子并不具有普遍性,如果我需要传入的参数不为3个那怎么办,所以对上面的例子进行修改之后可以得到下面这段代码

function add() {
    // 第一次执行时,定义一个数组专门用来存储所有的参数
    var _args = Array.prototype.slice.call(arguments);

    // 在内部声明一个函数,利用闭包的特性保存_args并收集所有的参数值
    var _adder = function() {
        _args.push(...arguments);
        return _adder;
    };

    // 利用toString隐式转换的特性,当最后执行时隐式转换,并计算最终的值返回
    _adder.toString = function () {
        return _args.reduce(function (a, b) {
            return a + b;
        });
    }
    return _adder;
}

add(1)(2)(3)                // 6
add(1, 2, 3)(4)             // 10
add(1)(2)(3)(4)(5)          // 15
add(2, 6)(1)                // 9

  上面这种写法存在一个弊端,就是返回值的类型是函数而不是数值,但是笔者目前也没找到什么更合适的方法了。

  解析完例子,我们来看看究竟什么是函数柯里化。柯里化一共有三大作用,分别是:

  • 参数复用
  • 提前确认
  • 延迟运行

  一、参数复用

  所谓参数复用,就是利用闭包的原理,让我们前面传输过来的参数不要被释放掉。看一下下面这段代码就显而易见了:

 1 // 正常封装check函数进行字符串正则匹配
 2 function check(reg, txt) {
 3     return reg.test(txt)
 4 }
 5
 6 check(/\d+/g, ‘test‘)        //false
 7 check(/[a-z]+/g, ‘test‘)     //true
 8
 9 // 使用柯里化函数进行字符串正则匹配
10 function curryingCheck(reg) {
11     return function(txt) {
12         return reg.test(txt)
13     }
14 }
15
16 var hasNumber = curryingCheck(/\d+/g)
17 var hasLetter = curryingCheck(/[a-z]+/g)
18
19 hasNumber(‘test1‘)      // true
20 hasNumber(‘testtest‘)   // false
21 hasLetter(‘21212‘)      // false

  二、 提前确认

  这一特性经常是用来对浏览器的兼容性做出一些判断并初始化api,比如说我们目前用来监听事件大部分情况是使用addEventListener来实现的,但是一些较久的浏览器并不支持该方法,所以在使用之前,我们可以先做一次判断,之后便可以省略这个步骤了。

 1 var on = (function() {
 2     if (document.addEventListener) {
 3         return function(element, event, handler) {
 4             if (element && event && handler) {
 5                 element.addEventListener(event, handler, false);
 6             }
 7         };
 8     } else {
 9         return function(element, event, handler) {
10             if (element && event && handler) {
11                 element.attachEvent(‘on‘ + event, handler);
12             }
13         };
14     }
15 })();

  三、 延迟运行

  js中的bind这个方法,用到的就是柯里化的这个特征。

1 Function.prototype.bind = function (context) {
2     var _this = this
3     var args = Array.prototype.slice.call(arguments, 1)
4
5     return function() {
6         return _this.apply(context, args)
7     }
8 }

原文地址:https://www.cnblogs.com/hmchen/p/11564394.html

时间: 2024-10-30 00:41:48

浅谈函数柯里化的相关文章

Swift函数柯里化(Currying)简谈

大熊猫猪·侯佩原创或翻译作品.欢迎转载,转载请注明出处. 如果觉得写的不好请多提意见,如果觉得不错请多多支持点赞.谢谢! hopy ;) 下面简单说说Swift语言中的函数柯里化简单的说就是把接收多个参数的函数划分为若干个"嵌套"的单一参数的函数. 这样说谁也听不懂,我们结合一个例子给大家简单说说. 我们需要定义一个函数A,该函数返回一个函数B,函数B创建一只大蜘蛛.为什么要间接返回大蜘蛛,因为本猫最怕大蜘蛛,所以不敢直接返回大蜘蛛 ;) 首先是蜘蛛的类: class Spider:C

建议75:函数柯里化

柯里化是把接受多个参数的函数变换成接受一个单一参数的函数,并且返回一个新函数,这个新函数能够接受原函数的参数.下面可以通过例子来帮助理解.function adder(num) {    return function(x) {        return num + x;    }}var add5 = adder(5);var add6 = adder(6);print(add5(1));  // 6print(add6(1));  //7 函数adder接受一个参数,并返回一个函数,这个返

【前端学习笔记】函数柯里化(自网易云课堂)

1. 函数柯里化通常是指把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的并且返回一个接受余下的参数而且返回结果的新函数的技术. // 1. 最简单的柯里化 // sum函数接受三个参数,并返回求和结果 var sum = function(a,b,c) { return a+b+c; } // 最简单柯里化的sum函数 var sum_curry = function(a){ return function(b,c){ return a+b+c; } } 2. 更泛化的定义是

【读书笔记】 函数柯里化

这是书上函数柯里化的例子 1 function curry(fn){ 2 var args = Array.prototype.slice.call(arguments, 1);//取出调用curry时除了第一个函数参数的后面所有参数; 3 return function(){ 4 var innerArgs = Array.prototype.slice.call(arguments);//取出第二次调用时的所有参数; 5 var finalArgs = args.concat(innerAr

一道javascript面试题(闭包与函数柯里化)

要求写一个函数add(),分别实现能如下效果: (1)console.log(add(1)(2)(3)(4)());//10 (2)console.log(add(1,2)(3,4)());//10 (3)console.log(add(1,2)(3,4));//10 针对(1)和(2),有两种思路实现:纯闭包思路和函数柯里化思路.一.闭包思路 (1)的解决方案(闭包实现) function add(arg) { // body... let sum = 0; sum+=arg; return

JavaScript函数柯里化的一些思考

1. 高阶函数的坑 在学习柯里化之前,我们首先来看下面一段代码: var f1 = function(x){ return f(x); }; f1(x); 很多同学都能看出来,这些写是非常傻的,因为函数f1和f是等效的,我们直接令var f1 = f;就行了,完全没有必要包裹那么一层. 但是,下面一段代码就未必能够看得出问题来了: var getServerStuff = function(callback){ return ajaxCall(function(json){ return cal

【转载】JS中bind方法与函数柯里化

原生bind方法 不同于jQuery中的bind方法只是简单的绑定事件函数,原生js中bind()方法略复杂,该方法上在ES5中被引入,大概就是IE9+等现代浏览器都支持了(有关ES5各项特性的支持情况戳这里ECMAScript 5 compatibility table),权威指南上提到在ES3中利用apply模拟该方法的实现(JS权威指南中函数那章), 但无法真实还原该方法, 这也是真bind方法中的有趣特性. (原文这边理解有问题, 这段话的意思如果结合犀牛书上下文的意思, 再结合犀牛书中

[转]js函数式变成之函数柯里化

本文转自:https://segmentfault.com/a/1190000003733107 函数柯里化是指参数逐渐求值的过程. 我觉得它是:降低通用性,提高专用性. 通常,柯里化是这样的过程,“如果你固定某些参数,你将得到接受余下参数的一个函数”.所以对于有两个变量的函数y^x,如果固定了 y=2,则得到有一个变量的函数 2^x 通用实现 全选复制放进笔记 function currying(fn) { var slice = Array.prototype.slice; var args

函数柯里化or not

今天leader说需要写一个通用函数,可以实现在不同的情况下都可以仅调用这一函数,而只是传递参数不同,我首先想到的是函数柯里化,实现把不同的参数转化成单一参数的方式,于是,利用了常见的函数柯里化方式并加以改造,利用了我们最常见的参数对象arguments,然后在总函数中写了arguments[1],arguments[2]来实现功能,自我感觉好像还不错. 但是leader看了代码说不行,不利于开发,为什么呢. 我改造的函数柯里化有一定局限性,尽管这个arguments[1]的值并不确定,但是这个