深入剖析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 相关文章推荐
jQuery ajax BUG:object doesn't support this property or method
Jul 06 Javascript
javascript跨域刷新实现代码
Jan 01 Javascript
Jquery中对数组的操作代码
Aug 12 Javascript
js修改input的type属性及浏览器兼容问题探讨与解决
Jan 23 Javascript
javascript页面加载完执行事件代码
Feb 11 Javascript
Jquery实现纵向横向菜单
Jan 24 Javascript
js HTML5多图片上传及预览实例解析(不含前端的文件分割)
Aug 26 Javascript
AngularJS过滤器filter用法实例分析
Nov 04 Javascript
前端html中jQuery实现对文本的搜索功能并把搜索相关内容显示出来
Nov 14 jQuery
Vue keepAlive 数据缓存工具实现返回上一个页面浏览的位置
May 10 Javascript
浅谈vuex中store的命名空间
Nov 08 Javascript
JavaScript语句错误throw、try及catch实例解析
Aug 18 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 mssql 时间格式问题
2009/01/13 PHP
php 空格,换行,跳格使用说明
2009/12/18 PHP
Linux下实现PHP多进程的方法分享
2012/08/16 PHP
PHP中根据IP地址判断城市实现城市切换或跳转代码
2012/09/04 PHP
PHP的preg_match匹配字符串长度问题解决方法
2014/05/03 PHP
php生成mysql的数据字典
2016/07/07 PHP
微信随机生成红包金额算法php版
2016/07/21 PHP
PHP如何获取Cookie并实现模拟登录
2020/07/16 PHP
ie和firefox不兼容的解决方法集合
2009/04/28 Javascript
jquery如何通过name名称获取当前name的value值
2013/12/20 Javascript
jQuery中prev()方法用法实例
2015/01/08 Javascript
javascript实现日期时间动态显示示例代码
2015/09/08 Javascript
Wireshark基本介绍和学习TCP三次握手
2016/08/15 Javascript
bootstrap table插件的分页与checkbox使用详解
2017/07/23 Javascript
angularjs 获取默认选中的单选按钮的value方法
2018/02/28 Javascript
在vue-cli 3中给stylus、sass样式传入共享的全局变量
2019/08/12 Javascript
微信小程序sessionid不一致问题解决
2019/08/30 Javascript
JS相册图片抖动放大展示效果的示例代码
2021/01/29 Javascript
[52:22]EG vs VG Supermajor小组赛B组 BO3 第一场 6.2
2018/06/03 DOTA
Python入门篇之字典
2014/10/17 Python
详解python中 os._exit() 和 sys.exit(), exit(0)和exit(1) 的用法和区别
2017/06/23 Python
Python中一些不为人知的基础技巧总结
2018/05/19 Python
如何通过python实现全排列
2020/02/11 Python
Python更新所有已安装包的操作
2020/02/13 Python
结合 CSS3 transition transform 实现简单的跑马灯效果的示例
2018/02/07 HTML / CSS
极简的HTML5模版
2015/07/09 HTML / CSS
Surfdome西班牙:世界上最受欢迎的生活方式品牌
2019/02/13 全球购物
欧缇丽加拿大官方网站:Caudalie加拿大
2019/07/18 全球购物
网络安全方面的面试题
2016/01/07 面试题
电子邮箱格式怎么写
2014/01/12 职场文书
本科毕业生求职自荐信
2014/02/03 职场文书
毕业自我鉴定书
2014/03/24 职场文书
旅游安全协议书
2014/04/21 职场文书
设计专业毕业生求职信
2014/06/25 职场文书
回复函格式及范文
2015/07/14 职场文书
python神经网络 使用Keras构建RNN训练
2022/05/04 Python