详解JS中的柯里化(currying)


Posted in Javascript onAugust 17, 2017

何为Curry化/柯里化?

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)

柯里化的作用

  • 延迟计算。上面的例子已经比较好低说明了。
  • 参数复用。当在多次调用同一个函数,并且传递的参数绝大多数是相同的,那么该函数可能是一个很好的柯里化候选。
  • 动态创建函数。

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

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

与 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();

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

实例

实例1:

var currying = function(fn) {
 // fn 指官员消化老婆的手段
 var args = [].slice.call(arguments, 1);
 // args 指的是那个合法老婆
 return function() {
  // 已经有的老婆和新搞定的老婆们合成一体,方便控制
  var newArgs = args.concat([].slice.call(arguments));
  // 这些老婆们用 fn 这个手段消化利用,完成韦小宝前辈的壮举并返回
  return fn.apply(null, newArgs);
 };
};
// 下为官员如何搞定7个老婆的测试
// 获得合法老婆
var getWife = currying(function() {
 var allWife = [].slice.call(arguments);
 // allwife 就是所有的老婆的,包括暗渡陈仓进来的老婆
 console.log(allWife.join(";"));
}, "合法老婆");
// 获得其他6个老婆
getWife("大老婆","小老婆","俏老婆","刁蛮老婆","乖老婆","送上门老婆");
// 换一批老婆
getWife("超越韦小宝的老婆");
 结果:
合法老婆;大老婆;小老婆;俏老婆;刁蛮老婆;乖老婆;送上门老婆
合法老婆;超越韦小宝的老婆
实例2:
var curryWeight = function(fn) {
 var _fishWeight = [];
 return function() {
  if (arguments.length === 0) {
   return fn.apply(null, _fishWeight);
  } else {
   _fishWeight = _fishWeight.concat([].slice.call(arguments));
  }
 }
};
var fishWeight = 0;
var addWeight = curryWeight(function() {
 var i=0; len = arguments.length;
 for (i; i<len; i+=1) {
  fishWeight += arguments[i];
 }
});
addWeight(2.3);
addWeight(6.5);
addWeight(1.2);
addWeight(2.5);
addWeight(); // 这里才计算
console.log(fishWeight); // 12.5

总结

以上所述是小编给大家介绍的JS中的函数柯里化(currying),希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!

Javascript 相关文章推荐
javascript Select标记中options操作方法集合
Oct 22 Javascript
Javascript将string类型转换int类型
Dec 09 Javascript
jquery ajax同步异步的执行最终解决方案
Apr 26 Javascript
轻松学习jQuery插件EasyUI EasyUI实现树形网络基本操作(2)
Nov 30 Javascript
Jquery修改image的src属性,图片不加载问题的解决方法
May 17 Javascript
Bootstrap字体图标无法正常显示的解决方法
Oct 08 Javascript
js实现适合新闻类图片的轮播效果
Feb 05 Javascript
jquery滚动条插件slimScroll使用方法
Feb 09 Javascript
重新理解JavaScript的六种继承方式
Mar 24 Javascript
微信小程序中页面FOR循环和嵌套循环
Jun 21 Javascript
基于vue的短信验证码倒计时demo
Sep 13 Javascript
JS中通过url动态获取图片大小的方法小结(两种方法)
Oct 31 Javascript
JavaScript实现简单评论功能
Aug 17 #Javascript
vue绑定class与行间样式style详解
Aug 16 #Javascript
Vue的MVVM实现方法
Aug 16 #Javascript
利用JS制作万年历的方法
Aug 16 #Javascript
原生JavaScript来实现对dom元素class的操作方法(推荐)
Aug 16 #Javascript
React Native 集成jpush-react-native的示例代码
Aug 16 #Javascript
jQuery实现全选、反选和不选功能
Aug 16 #jQuery
You might like
PHP中ADODB类详解
2008/03/25 PHP
很好用的PHP数据库类
2009/05/27 PHP
具有时效性的php加密解密函数代码
2013/06/19 PHP
PHP实现的多彩标签效果代码分享
2014/08/21 PHP
使用Thinkphp框架开发移动端接口
2015/08/05 PHP
PHP+MySQL之Insert Into数据插入用法分析
2015/09/27 PHP
解决form中action属性后面?传递参数 获取不到的问题
2017/07/21 PHP
PHP Redis扩展无法加载的问题解决方法
2019/08/22 PHP
javascript对象之内置对象Math使用方法
2010/04/16 Javascript
jquery ajax的success回调函数中实现按钮置灰倒计时
2013/11/19 Javascript
javascript实现复制与粘贴操作实例
2014/10/16 Javascript
基于JavaScript实现随机颜色输入框
2016/12/10 Javascript
JavaScript中this的用法实例分析
2016/12/19 Javascript
如何将 jQuery 从你的 Bootstrap 项目中移除(取而代之使用Vue.js)
2017/07/17 jQuery
Angular移动端页面input无法输入的解决方法
2017/11/14 Javascript
jQuery实现手机号正则验证输入及自动填充空格功能
2018/01/02 jQuery
详解Vue.js项目API、Router配置拆分实践
2018/03/16 Javascript
详解vue-cli 构建项目 vue-cli请求后台接口 vue-cli使用axios、sass、swiper
2018/05/28 Javascript
详解微信小程序canvas圆角矩形的绘制的方法
2018/08/22 Javascript
用python登录Dr.com思路以及代码分享
2014/06/25 Python
解决Django模板无法使用perms变量问题的方法
2017/09/10 Python
TensorFlow 滑动平均的示例代码
2018/06/19 Python
详解python分布式进程
2018/10/08 Python
python实现对图片进行旋转,放缩,裁剪的功能
2019/08/07 Python
Python编写打字训练小程序
2019/09/26 Python
感知器基础原理及python实现过程详解
2019/09/30 Python
什么是python的必选参数
2020/06/21 Python
ProBikeKit英国:在线公路自行车之家
2017/02/10 全球购物
美国厨房和园艺工具网上商店:Nestneed
2019/08/24 全球购物
安全责任书怎么写
2014/07/28 职场文书
幼儿园教师师德师风演讲稿:我自豪我是一名幼师
2014/09/10 职场文书
2015年教师党员公开承诺书
2015/01/22 职场文书
《确定位置》教学反思
2016/02/18 职场文书
MySQL 亿级数据导入导出及迁移笔记
2021/06/18 MySQL
Nginx+Tomcat负载均衡多实例详解
2022/04/11 Servers
MySQL运行报错:“Expression #1 of SELECT list is not in GROUP BY clause and contains nonaggre”解决方法
2022/06/14 MySQL