深入剖析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 相关文章推荐
JQUERY1.6 使用方法四 检测浏览器
Nov 23 Javascript
JS TextArea字符串长度限制代码集合
Oct 31 Javascript
JavaScript Ajax Json实现上下级下拉框联动效果实例代码
Nov 23 Javascript
解决jQuery使用JSONP时产生的错误
Dec 02 Javascript
最全面的JS倒计时代码
Sep 17 Javascript
一次围绕setTimeout的前端面试经验分享
Jun 15 Javascript
bootstrap响应式导航条模板使用详解(含下拉菜单,弹出框)
Nov 17 Javascript
layui实现把数据表格时间戳转换为时间格式的例子
Sep 12 Javascript
laravel实现中文和英语互相切换的例子
Sep 30 Javascript
解决Vue @submit 提交后不刷新页面问题
Jul 18 Javascript
vue中封装axios并实现api接口的统一管理
Dec 25 Vue.js
Element-ui Layout布局(Row和Col组件)的实现
Dec 06 Vue.js
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怎么实现网站保存快捷方式方便用户随时浏览
2013/08/15 PHP
php实现按指定大小等比缩放生成上传图片缩略图的方法
2014/12/15 PHP
PHP制作用户注册系统
2015/10/23 PHP
php对微信支付回调处理的方法
2018/08/23 PHP
Jquery优化效率 提升性能解决方案
2010/09/06 Javascript
JQuery 自定义CircleAnimation,Animate方法学习笔记
2011/07/10 Javascript
javascript框架设计读书笔记之模块加载系统
2014/12/02 Javascript
深入理解JavaScript系列(46):代码复用模式(推荐篇)详解
2015/03/04 Javascript
js实现鼠标移到链接文字弹出一个提示层的方法
2015/05/11 Javascript
JS实现超简单的仿QQ折叠菜单效果
2015/09/21 Javascript
AngularJS基础 ng-copy 指令实例代码
2016/08/01 Javascript
jQuery实现可拖拽的许愿墙效果【附demo源码下载】
2016/09/14 Javascript
使用jquery判断一个元素是否含有一个指定的类(class)实例
2017/02/12 Javascript
Angular实现响应式表单
2017/08/04 Javascript
React-router4路由监听的实现
2018/08/07 Javascript
JS实现处理时间,年月日,星期的公共方法示例
2019/05/31 Javascript
vue element upload实现图片本地预览
2019/08/20 Javascript
使用JavaScript计算前一天和后一天的思路详解
2019/12/20 Javascript
node.js使用 http-proxy 创建代理服务器操作示例
2020/02/10 Javascript
vue-cli3配置favicon.ico和title的流程
2020/10/27 Javascript
python3.3使用tkinter开发猜数字游戏示例
2014/03/14 Python
利用Python批量压缩png方法实例(支持过滤个别文件与文件夹)
2017/07/30 Python
opencv3/python 鼠标响应操作详解
2019/12/11 Python
Python实现上下文管理器的方法
2020/08/07 Python
详解基于python的图像Gabor变换及特征提取
2020/10/26 Python
python Timer 类使用介绍
2020/12/28 Python
使paramiko库执行命令时在给定的时间强制退出功能的实现
2021/03/03 Python
Sephora丝芙兰印尼官方网站:购买化妆品和护肤品
2018/07/02 全球购物
文件中有一组整数,要求排序后输出到另一个文件中
2012/01/04 面试题
医学专业个人求职自荐信格式
2013/09/23 职场文书
市三好学生主要事迹
2014/01/28 职场文书
车队司机自我鉴定
2014/03/02 职场文书
新党章心得体会
2014/09/04 职场文书
教师听课学习心得体会
2016/01/15 职场文书
婚礼必备主持词范本!
2019/07/23 职场文书
ubuntu下常用apt命令介绍
2022/06/05 Servers