深入剖析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 相关文章推荐
JS 无限级 Select效果实现代码(json格式)
Aug 30 Javascript
js自动闭合html标签(自动补全html标记)
Oct 04 Javascript
Bootstrap实现input控件失去焦点时验证
Aug 04 Javascript
Node.js的文件权限及读写flag详解
Oct 11 Javascript
BootStrap树状图显示功能
Nov 24 Javascript
基于JavaScript中标识符的命名规则介绍
Jan 06 Javascript
AngularJs用户输入动态模板XSS攻击示例详解
Apr 21 Javascript
node实现爬虫的几种简易方式
Aug 22 Javascript
Vue使用NProgress进度条的方法
Sep 21 Javascript
vue 通过 Prop 向子组件传递数据的实现方法
Oct 30 Javascript
浅析VUE防抖与节流
Nov 24 Vue.js
Node快速切换版本、版本回退(降级)、版本更新(升级)
Jan 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笔记之常用文件操作
2010/10/12 PHP
Windows下利用Gvim写PHP产生中文乱码问题解决方法
2011/04/20 PHP
在Ubuntu 14.04上部署 PHP 环境及 WordPress
2014/09/02 PHP
PHP中把数据库查询结果输出为json格式简单实例
2015/04/09 PHP
Ext对基本类型的扩展 ext,extjs,format
2010/12/25 Javascript
一个JavaScript处理textarea中的字符成每一行实例
2014/09/22 Javascript
浅析Node.js的Stream模块中的Readable对象
2015/07/29 Javascript
jquery常用函数与方法汇总
2015/09/01 Javascript
jQuery实例—选项卡的简单实现(js源码和jQuery)
2016/06/14 Javascript
jQuery 检查某个元素在页面上是否存在实例代码
2016/10/27 Javascript
原生JS上传大文件显示进度条 php上传文件代码
2020/03/27 Javascript
React Native 通告消息竖向轮播组件的封装
2020/08/25 Javascript
微信小程序promsie.all和promise顺序执行
2017/10/27 Javascript
JavaScript闭包与作用域链实例分析
2019/01/21 Javascript
微信小程序如何使用globalData的方法
2019/06/06 Javascript
vue瀑布流组件实现上拉加载更多
2020/03/10 Javascript
Vue SSR 即时编译技术的实现
2020/05/06 Javascript
vue中用 async/await 来处理异步操作
2020/07/18 Javascript
vant组件中 dialog的确认按钮的回调事件操作
2020/11/04 Javascript
[03:53]2016国际邀请赛中国区预选赛第三日TOP10精彩集锦
2016/06/29 DOTA
python简单获取本机计算机名和IP地址的方法
2015/06/03 Python
Python基于Socket实现的简单聊天程序示例
2017/08/05 Python
解决Pycharm无法import自己安装的第三方module问题
2018/05/18 Python
浅谈numpy生成数组的零值问题
2018/11/12 Python
Python检查和同步本地时间(北京时间)的实现方法
2018/12/03 Python
基于Python实现天天酷跑功能
2021/01/06 Python
html5 input输入实时检测以及延时优化
2018/07/18 HTML / CSS
葡萄牙航空官方网站:TAP Air Portugal
2019/10/31 全球购物
Nike墨西哥官网:Nike MX
2020/08/30 全球购物
技术副厂长岗位职责
2013/12/26 职场文书
酒店秘书求职信范文
2014/02/17 职场文书
新闻工作者先进事迹
2014/05/26 职场文书
乡镇党的群众路线教育实践活动制度建设计划
2014/11/03 职场文书
律师催款函范文
2015/06/24 职场文书
高一语文教学反思
2016/02/16 职场文书
浅析Redis Sentinel 与 Redis Cluster
2021/06/24 Redis