深入剖析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 相关文章推荐
ExtJS下grid的一些属性说明
Dec 13 Javascript
jQuery 表格插件整理
Apr 27 Javascript
jquery实现滑动图片自己测试的例子
Nov 05 Javascript
JavaScript函数参数使用带参数名的方式赋值传入的方法
Mar 19 Javascript
JavaScript+CSS实现仿Mootools竖排弹性动画菜单效果
Oct 14 Javascript
JavaScript使用DeviceOne开发实战(二) 生成调试安装包
Dec 01 Javascript
js实现点击图片自动提交action的简单方法
Oct 16 Javascript
jsp 网站引入外部css或者js失效问题解决
Oct 31 Javascript
easyui中combotree循环获取父节点至根节点并输出路径实现方法
Nov 10 Javascript
在Vue中使用echarts的方法
Feb 05 Javascript
微信小程序生成分享海报方法(附带二维码生成)
Mar 29 Javascript
关于vue状态过渡transition不起作用的原因解决
Apr 09 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 intval的测试代码发现问题
2008/07/27 PHP
Laravel中间件实现原理详解
2016/10/09 PHP
Laravel中的chunk组块结果集处理与注意问题
2018/08/15 PHP
PHP基于ip2long实现IP转换整形
2020/12/11 PHP
判断字符串的长度(优化版)中文占两个字符
2014/10/30 Javascript
纯javascript实现四方向文本无缝滚动效果
2015/06/16 Javascript
jQuery实现标题有打字效果的焦点图代码
2015/11/16 Javascript
javascript 解决浏览器不支持的问题
2016/09/24 Javascript
jQuery.Form上传文件操作
2017/02/05 Javascript
JS拉起或下载app的实现代码
2017/02/22 Javascript
利用vueJs实现图片轮播实例代码
2017/06/03 Javascript
AngularJS ng-repeat指令及Ajax的应用实例分析
2017/07/06 Javascript
js实现拖拽上传图片功能
2017/08/01 Javascript
vue引入js数字小键盘的实现代码
2018/05/14 Javascript
angularjs性能优化的方法
2018/09/05 Javascript
Vue中CSS动画原理的实现
2019/02/13 Javascript
如何为你的JavaScript代码日志着色详解
2019/04/08 Javascript
Vue 实现html中根据类型显示内容
2019/10/28 Javascript
Node.js API详解之 module模块用法实例分析
2020/05/13 Javascript
[48:45]Ti4 循环赛第二日 NEWBEE vs EG
2014/07/11 DOTA
Python类的多重继承问题深入分析
2014/11/09 Python
使用django-crontab实现定时任务的示例
2018/02/26 Python
Python批量合并有合并单元格的Excel文件详解
2018/04/05 Python
pyecharts绘制中国2020肺炎疫情地图的实例代码
2020/02/12 Python
HTML5拖放API实现拖放排序的实例代码
2017/05/11 HTML / CSS
英国第一的购买便宜玩具和游戏的在线购物网站:Bargain Max
2018/01/24 全球购物
商得四方公司面试题(gid+)
2014/04/30 面试题
Overload和Override的区别
2012/09/02 面试题
财务管理专业应届毕业生求职信
2013/09/22 职场文书
女大学生毕业找工作的自我评价
2013/10/03 职场文书
24岁生日感言
2014/01/13 职场文书
促销活动方案模板
2014/02/24 职场文书
环境卫生标语
2014/06/09 职场文书
施工安全汇报材料
2014/08/17 职场文书
初中班主任工作总结2015
2015/05/13 职场文书
团拜会主持词
2015/07/04 职场文书