深入剖析JavaScript中的函数currying柯里化


Posted in Javascript onApril 29, 2016

curry化来源与数学家 Haskell Curry的名字 (编程语言 Haskell也是以他的名字命名)。
 
柯里化通常也称部分求值,其含义是给函数分步传递参数,每次传递参数后部分应用参数,并返回一个更具体的函数接受剩下的参数,这中间可嵌套多层这样的接受部分参数函数,直至返回最后结果。

因此柯里化的过程是逐步传参,逐步缩小函数的适用范围,逐步求解的过程。 

柯里化一个求和函数
按照分步求值,我们看一个简单的例子

var concat3Words = function (a, b, c) { 
  return a+b+c; 
}; 
 
var concat3WordsCurrying = function(a) { 
  return function (b) { 
    return function (c) { 
      return a+b+c; 
    }; 
  }; 
}; 
console.log(concat3Words("foo ","bar ","baza"));      // foo bar baza 
console.log(concat3WordsCurrying("foo "));         // [Function] 
console.log(concat3WordsCurrying("foo ")("bar ")("baza")); // foo bar baza

可以看到, concat3WordsCurrying("foo ") 是一个 Function,每次调用都返回一个新的函数,该函数接受另一个调用,然后又返回一个新的函数,直至最后返回结果,分布求解,层层递进。(PS:这里利用了闭包的特点) 
 

那么现在我们更进一步,如果要求可传递的参数不止3个,可以传任意多个参数,当不传参数时输出结果? 

首先来个普通的实现:

var add = function(items){ 
  return items.reduce(function(a,b){ 
    return a+b 
  }); 
}; 
console.log(add([1,2,3,4]));

但如果要求把每个数乘以10之后再相加,那么:

var add = function (items,multi) { 
  return items.map(function (item) { 
    return item*multi; 
  }).reduce(function (a, b) { 
    return a + b 
  }); 
}; 
console.log(add([1, 2, 3, 4],10));

好在有 map 和 reduce 函数,假如按照这个模式,现在要把每项加1,再汇总,那么我们需要更换map中的函数。 

下面看一下柯里化实现:

var adder = function () { 
  var _args = []; 
  return function () { 
    if (arguments.length === 0) { 
      return _args.reduce(function (a, b) { 
        return a + b; 
      }); 
    } 
    [].push.apply(_args, [].slice.call(arguments)); 
    return arguments.callee; 
  } 
};   
var sum = adder(); 
 
console.log(sum);   // Function 
 
sum(100,200)(300);  // 调用形式灵活,一次调用可输入一个或者多个参数,并且支持链式调用 
sum(400); 
console.log(sum());  // 1000 (加总计算)

上面 adder是柯里化了的函数,它返回一个新的函数,新的函数接收可分批次接受新的参数,延迟到最后一次计算。 
 

通用的柯里化函数

更典型的柯里化会把最后一次的计算封装进一个函数中,再把这个函数作为参数传入柯里化函数,这样即清晰,又灵活。

例如 每项乘以10, 我们可以把处理函数作为参数传入:

var currying = function (fn) { 
  var _args = []; 
  return function () { 
    if (arguments.length === 0) { 
      return fn.apply(this, _args); 
    } 
    Array.prototype.push.apply(_args, [].slice.call(arguments)); 
    return arguments.callee; 
  } 
}; 
 
var multi=function () { 
  var total = 0; 
  for (var i = 0, c; c = arguments[i++];) { 
    total += c; 
  } 
  return total; 
}; 
 
var sum = currying(multi);  
  
sum(100,200)(300); 
sum(400); 
console.log(sum());   // 1000 (空白调用时才真正计算)

这样 sum = currying(multi),调用非常清晰,使用效果也堪称绚丽,例如要累加多个值,可以把多个值作为做个参数 sum(1,2,3),也可以支持链式的调用,sum(1)(2)(3)
 
柯里化的基础

上面的代码其实是一个高阶函数(high-order function), 高阶函数是指操作函数的函数,它接收一个或者多个函数作为参数,并返回一个新函数。此外,还依赖与闭包的特性,来保存中间过程中输入的参数。即:
 
函数可以作为参数传递
函数能够作为函数的返回值
闭包
柯里化的作用
延迟计算。上面的例子已经比较好低说明了。

参数复用。当在多次调用同一个函数,并且传递的参数绝大多数是相同的,那么该函数可能是一个很好的柯里化候选。

动态创建函数。这可以是在部分计算出结果后,在此基础上动态生成新的函数处理后面的业务,这样省略了重复计算。或者可以通过将要传入调用函数的参数子集,部分应用到函数中,从而动态创造出一个新函数,这个新函数保存了重复传入的参数(以后不必每次都传)。例如,事件浏览器添加事件的辅助方法:

var addEvent = function(el, type, fn, capture) { 
   if (window.addEventListener) { 
     el.addEventListener(type, function(e) { 
       fn.call(el, e); 
     }, capture); 
   } else if (window.attachEvent) { 
     el.attachEvent("on" + type, function(e) { 
       fn.call(el, e); 
     }); 
   } 
 };

每次添加事件处理都要执行一遍 if...else...,其实在一个浏览器中只要一次判定就可以了,把根据一次判定之后的结果动态生成新的函数,以后就不必重新计算。

var addEvent = (function(){ 
  if (window.addEventListener) { 
    return function(el, sType, fn, capture) { 
      el.addEventListener(sType, function(e) { 
        fn.call(el, e); 
      }, (capture)); 
    }; 
  } else if (window.attachEvent) { 
    return function(el, sType, fn, capture) { 
      el.attachEvent("on" + sType, function(e) { 
        fn.call(el, e); 
      }); 
    }; 
  } 
})();

这个例子,第一次 if...else... 判断之后,完成了部分计算,动态创建新的函数来处理后面传入的参数,这是一个典型的柯里化。
 
Function.prototype.bind 方法也是柯里化应用

与 call/apply 方法直接执行不同,bind 方法 将第一个参数设置为函数执行的上下文,其他参数依次传递给调用方法(函数的主体本身不执行,可以看成是延迟执行),并动态创建返回一个新的函数, 这符合柯里化特点。

var foo = {x: 888}; 
var bar = function () { 
  console.log(this.x); 
}.bind(foo);        // 绑定 
bar();           // 888

下面是一个 bind 函数的模拟,testBind 创建并返回新的函数,在新的函数中将真正要执行业务的函数绑定到实参传入的上下文,延迟执行了。

Function.prototype.testBind = function (scope) { 
  var fn = this;          //// this 指向的是调用 testBind 方法的一个函数, 
  return function () { 
    return fn.apply(scope); 
  } 
}; 
var testBindBar = bar.testBind(foo); // 绑定 foo,延迟执行 
console.log(testBindBar);       // Function (可见,bind之后返回的是一个延迟执行的新函数) 
testBindBar();            // 888

这里要注意 prototype 中 this 的理解。

以上这篇深入剖析JavaScript中的函数currying 柯里化就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
Ext javascript建立超链接,进行事件处理的实现方法
Mar 22 Javascript
ExtJS 2.0实用简明教程之应用ExtJS
Apr 29 Javascript
用JS写的一个TableView控件代码
Jan 23 Javascript
在Linux上用forever实现Node.js项目自启动
Jul 09 Javascript
深入理解JavaScript系列(48):对象创建模式(下篇)
Mar 04 Javascript
js表单元素checked、radio被选中的几种方法(详解)
Aug 22 Javascript
JS简单去除数组中重复项的方法
Sep 13 Javascript
JavaScript实现通过select标签跳转网页的方法
Sep 29 Javascript
node+experss实现爬取电影天堂爬虫
Nov 20 Javascript
jQuery each和js forEach用法比较
Feb 27 jQuery
详解React项目如何修改打包地址(编译输出文件地址)
Mar 21 Javascript
vue + typescript + video.js实现 流媒体播放 视频监控功能
Jul 07 Javascript
javascript中利用柯里化函数实现bind方法【推荐】
Apr 29 #Javascript
jQuery Ajax 实例代码 ($.ajax、$.post、$.get)
Apr 29 #Javascript
一个字符串中出现次数最多的字符 统计这个次数【实现代码】
Apr 29 #Javascript
JS弹出层遮罩,隐藏背景页面滚动条细节优化分析
Apr 29 #Javascript
老生常谈遮罩层 滚动条的问题
Apr 29 #Javascript
弹出遮罩层后禁止滚动效果【实现代码】
Apr 29 #Javascript
一系列Bootstrap导航条使用方法分享
Apr 29 #Javascript
You might like
php smarty的预保留变量总结
2008/12/04 PHP
Apache环境下PHP利用HTTP缓存协议原理解析及应用分析
2010/02/16 PHP
PHP递归返回值时出现的问题解决办法
2013/02/19 PHP
PHP实现根据浏览器跳转不同语言页面代码
2013/08/02 PHP
详细解读php的命名空间(二)
2018/02/21 PHP
javascript实现div的拖动并调整大小类似qq空间个性编辑模块
2012/12/12 Javascript
js实现时间显示几天前、几小时前或者几分钟前的方法集锦
2015/05/29 Javascript
基于jQuery实现淡入淡出效果轮播图
2020/07/31 Javascript
Vue关于数据绑定出错解决办法
2017/05/15 Javascript
深究AngularJS中ng-drag、ng-drop的用法
2017/06/12 Javascript
jQuery实现对网页节点的增删改查功能示例
2017/09/18 jQuery
vue+socket.io+express+mongodb 实现简易多房间在线群聊示例
2017/10/21 Javascript
jQuery实现表单动态添加数据并提交的方法
2018/07/19 jQuery
JavaScript中的事件与异常捕获详析
2019/02/24 Javascript
JS中的算法与数据结构之链表(Linked-list)实例详解
2019/08/20 Javascript
Vue中的循环及修改差值表达式的方法
2019/08/29 Javascript
解决Vue调用springboot接口403跨域问题
2019/09/02 Javascript
js贪心算法 钱币找零问题代码实例
2019/09/11 Javascript
小程序中使用css var变量(使js可以动态设置css样式属性)
2020/03/31 Javascript
详解如何在vue+element-ui的项目中封装dialog组件
2020/12/11 Vue.js
[47:45]DOTA2-DPC中国联赛 正赛 Phoenix vs Dragon BO3 第一场 2月26日
2021/03/11 DOTA
Python实现把回车符\r\n转换成\n
2015/04/23 Python
python之virtualenv的简单使用方法(必看篇)
2017/11/25 Python
Python3 中文文件读写方法
2018/01/23 Python
bluepy 一款python封装的BLE利器简单介绍
2019/06/25 Python
python海龟绘图之画国旗实例代码
2020/11/11 Python
带你认识HTML5中的WebSocket
2015/05/22 HTML / CSS
葡萄牙航空官方网站:TAP Air Portugal
2019/10/31 全球购物
贝佳斯官方网站:Borghese
2020/05/08 全球购物
如何用Java判断一个文件或目录是否存在
2012/11/19 面试题
刚毕业大学生自荐信范文
2014/02/20 职场文书
学校端午节活动方案
2014/08/23 职场文书
四风问题对照检查材料思想汇报
2014/10/07 职场文书
大学生自我推荐信范文
2015/03/24 职场文书
2016个人廉洁自律承诺书
2016/03/25 职场文书
自荐信范文
2019/05/20 职场文书