• 售前

  • 售后

热门帖子
入门百科

怎样用Javascript实现函数柯里化与反柯里化

[复制链接]
C丶sunshine 显示全部楼层 发表于 2021-10-26 14:25:58 |阅读模式 打印 上一主题 下一主题
函数柯里化(黑人问号脸)???Currying(黑人问号脸)???妥妥的中式翻译既视感;下面来一起看看毕竟什么是函数柯里化:
维基百科的表明是:把吸收多个参数的函数变更成吸收一个单一参数(最初函数的第一个参数)的函数,并返回接受剩余的参数而且返回效果的新函数的技能。其由数学家Haskell Brooks Curry提出,并以curry定名。
概念通常都是干涩且难懂的,让我们用人话来表明就是:如果我们不确定这个函数有多少个参数,我们可以先给它传入一个参数,然后通过JS闭包(如若不懂JS闭包,请先学习闭包知识点再来学习本篇博文https://www.cnblogs.com/dengyao-blogs/p/11475575.html)来进行返回一个函数,内部函数吸收除开第一个参数外的别的参数进行操纵并输出,这个就是函数的柯里化;
举个小例子:
场景(需求):
众所周知程序员每天加班的时间还是比较多的,如果我们必要计算一个程序员每天的加班时间,那么我们的第一反应应该是如许;
  1. var overtime=0;
  2. function time(x){
  3.     return overtime+=x;
  4. }
  5. time(1);  //1
  6. time(2);  //3
  7. time(3);  //6
复制代码
上面的代码固然没有问题,但是必要每天调用都算加一下当天的时间,很贫困,而且每调用一次函数都要进行肯定的操纵,如果数据量巨大,有大概会有影响性能的风险,那么有没有可以偷懒又能解决问题的办法呢?有的!
  1. function time(x){
  2.   return function(y){
  3.         return x+y;
  4.     }      
  5. }
  6. var times=time(0);
  7. times(3);
复制代码
但是上面代码依然存在问题,在实际开发中很多时间我们的参数是不确定的,上面代码固然简朴的实现了柯里化的根本操纵,但是对于参数不确定的情况是处理惩罚不了的;以是存在着函数参数的范围性;不过我们从上面的代码中根本可以知道函数柯里化是个啥意思了;就是一个函数调用的时间只答应传入一个参数,然后通过闭包返回内部函数行止理惩罚和吸收剩余参数,返回的函数通过闭包的方式记住了time的第一个参数;
我们再来把代码改造一下:
  1. //  首先定义一个变量接收函数
  2. var overtime = (function() {
  3. //定义一个数组用来接收参数
  4.   var args = [];
  5. //这里运用闭包,调用外部函数返回一个内部函数
  6.   return function() {
  7.   //arguments是浏览器内置对象,专门用来接收参数
  8.   //如果参数的长度为0即没有参数的时候
  9.     if(arguments.length === 0) {
  10.     //定义变量用来累加
  11.       var time = 0;
  12.     //循环累加,用i和args的长度进行比较
  13.       for (var i = 0, l = args.length; i < l; i++) {
  14.     //进行累加操作   等价于time=time+args[i]
  15.         time += args[i];
  16.       }
  17.     // 返回累加的结果
  18.       return time;
  19.     //如果arguments对象参数长度不为零,即有参数的时候
  20.     }else {
  21.     //定义的空数组添加arguments参数作为数组项,第一个参数古args作为改变this指向,第二个参数arguments把剩余参数作为数组形式添加至空数组中
  22.       [].push.apply(args, arguments);
  23.     }
  24.   }
  25. })();
  26. overtime(3.5);    // 第一天
  27. overtime(4.5);    // 第二天
  28. overtime(2.1);    // 第三天
  29. //...
  30. console.log( overtime() );    // 10.1
复制代码
代码颠末我们的改造已经实现了功能,但是这不是一个函数柯里化的完整实现,那么我们要怎么完整实现呢?下面我们来先容一种通用的实现方式:
  1. //定义方法currying,先传入一个参数
  2. var currying=function(fn){
  3.   //定义空数组装arguments对象的剩余参数
  4.   var args=[];
  5.   //利用闭包返回一个函数处理剩余参数
  6.   return function (){
  7.     //如果arguments的参数长度为0,即没有剩余参数
  8.     if(arguments.length===0){
  9.     //执行上面方法
  10.     //这里的this指向下面的s,类似于s(),代表参数长度为0的时候直接调用函数
  11.       return fn.apply(this,args)
  12.     }
  13.     console.log(arguments)
  14.   //如果arguments的参数长度不为0,即还有剩余参数
  15.   //在数组的原型对象上添加数组,apply用来更改this的指向为args
  16.   //将[].slice.call(arguments)的数组添加到原型数组上
  17.   //这里的[].slice.call(arguments)===Array.prototype.slice.call(arguments)实质上就是将arguments对象转成数组并具有slice功能
  18.    Array.prototype.push.apply(args,[].slice.call(arguments))
  19.     //args.push([].slice.call(arguments))
  20.     console.log(args)
  21.   //这里返回的arguments.callee是返回的闭包函数,callee是arguments对象里面的一个属性,用于返回正被执行的function对象
  22.     return arguments.callee
  23.   }
  24. }
  25.   //这里调用currying方法并传入add函数,结果会返回闭包内部函数
  26.   var s=currying(add);
  27.   //调用闭包内部函数,当有参数的时候会将参数逐步添加到args数组中,待没有参数传入的时候直接调用
  28.   //调用的时候支持链式操作
  29.   s(1)(2)(3)();
  30. //也可以一次性传入多个参数
  31.    s(1,2,3);
  32.   console.log(s());
复制代码
JS函数柯里化的优点:
       
  • 可以延长计算,即如果调用柯里化函数传入参数是不调用的,会将参数添加到数组中存储,等到没有参数传入的时间进行调用;   
  • 参数复用,当在多次调用同一个函数,而且转达的参数绝大多数是雷同的,那么该函数大概是一个很好的柯里化候选。
世间万物相对,有因必有果,当然了,有柯里化必然有反柯里化;
反柯里化(uncurrying),从字面意思上来讲就是跟柯里化的意思相反;实在真正的反柯里化的作用是扩大适用范围,就是说当我们调用某个方法的时间,不必要考虑这个对象自身在计划的过程中有没有这个方法,只要这个方法适用于它,我们就可以使用;(这里引用的是动态语言中的鸭子类型的头脑)
在学习JS反柯里化之前,我们先学习一下动态语言的鸭子类型头脑,以助于我们更好的明白:
动态语言鸭子类型头脑(维基百科表明):
在程序计划中,鸭子类型(duck typing)是动态类型的一种风格。
在这种风格中,一个对象有效的语义,不是由继续自特定的类或实现特定的接口,而是由当火线法和属性的聚集决定。
这个概念的名字泉源于由 James Whitcomb Riley 提出的鸭子测试,“鸭子测试”可以如许表述:
当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。
理论上的表明通常干涩难懂,换成人话来说就是:你是你妈妈的儿子/女儿,不管你是否良好,是否漂亮,只要你是你妈亲生的,那么你就是你妈的儿子/女儿;换成鸭子类型就是,只要你会呱呱叫,走起来像鸭子,只要你拥有的举动像鸭子,不管你是不是鸭子,那么你就可以被称为鸭子;
在Javascript中有很多鸭子类型的引用,比如我们在对一个变量进行赋值的时间,显然是不必要考虑变量的类型的,正是由于如此,Javascript才更加的机动,以是Javascript是一门典范的动态类型语言;
我们来看一下反柯里化中是怎么引用鸭子类型的:
  1. //函数原型对象上添加uncurring方法
  2. Function.prototype.uncurring = function() {
  3. //改变this的指向   
  4. //这里的this指向是Array.prototype.push
  5.   var self = this;
  6.     //这里的闭包用来返回内部函数的执行
  7.   return function() {
  8.     //创建一个变量,在数组的原型对象上添加shift上面删除第一个参数
  9.     //改变数组this的指向为arguments
  10.     var obj = Array.prototype.shift.call(arguments);
  11.     //最后返回执行并给方法改变指向为obj也就是arguments
  12.    // 并传入arguments作为参数
  13.     return self.apply(obj, arguments);
  14.   };
  15. };
  16. //数组原型对象上添加uncurrying方法
  17. var push = Array.prototype.push.uncurring();
  18. //测试一下
  19. //匿名函数自执行
  20. (function() {
  21.     //这里的push就是一个函数方法了
  22.     //相当于传入参数arguments和4两个参数,但是在上面shift方法中删除第一个参数,这里的arguments参数被截取了,所以最后实际上只传入了4
  23.   push(arguments, 4);
  24.   console.log(arguments); //[1, 2, 3, 4]
  25. //匿名函数自调用并带入参数1,2,3
  26. })(1, 2, 3)
复制代码
到这里各人可以想一想arguments是一个吸收参数的对象,里面是没有push方法的,那么arguments为什么能调用push方法呢?
这是由于代码var push = Array.prototype.push.uncurring();在数组的原型对象的push方法上添加了uncurring方法,然后在实行匿名函数的方法push(arguments, 4);时间实质上是在调用上面的方法在Function的原型对象上添加uncurring方法并返回一个闭包内部函数实行,在实行的过程中由于Array原型对象上的shift方法会把push(arguments, 4);中的arguments截取,以是实在方法的实际调用是push(4),以是最终的效果才是[1,2,3,4]
在《JavaScript计划模式与开发实践》一书中,JS函数的反柯里化的案例是如许写的:
  1. //定义一个对象
  2. var obj = {
  3.     "length":1,
  4.     "0":1
  5. }
  6. //在Function原型对象定义方法uncurrying
  7. Function.prototype.uncurrying = function() {
  8.     //this指向Array.prototype.push
  9.     var self = this;
  10.     //闭包返回一个内部函数
  11.     return function() {
  12.     // 这里可以拆开理解
  13.     //首先执行apply return
  14.     //Function.prototype.call(Array.prototype.push[obj,2])
  15.    //然后Array.prototype.push.call(obj,2)
  16.     //call改变指向  obj.push(2)
  17.     //所以最后结果就是  {0: 1, 1: 2, length: 2}
  18.         return Function.prototype.call.apply(self, arguments);
  19. }
  20. }
  21. //在
  22. var push = Array.prototype.push.uncurrying()
  23. push(obj, 2)
  24. console.log(obj);
  25. //{0: 1, 1: 2, length: 2}
复制代码
上面的方式不好明白?不要紧,咱们来个好明白的:
  1. Function.prototype.unCurrying = function () {
  2.     var self = this;
  3.     return function () {
  4.     //[].slice.call(arguments,1)===Array.prototype.push.slice.call(arguments,1)===arguments.slice(1)
  5. return self.apply(arguments[0], [].slice.call(arguments, 1));
  6.     };
  7. };
  8. var push = Array.prototype.push.uncurrying()
  9. console.log(push);
  10. push(obj,2) //{0: 1, 1: 2, length: 2}
  11. console.log(obj);
复制代码
分析一下:
1、起首在Function原型对象上添加uncurrying方法,如许所有的Function都可以借用;
2、返回一个闭包内部函数
3、闭包函数返回的效果中返回的是调用方法,self指向Array.prototype.push,apply方法中第一个参数是更改指向,对应下面push(obj,2)相当于更改指向为obj.push(2)
4、apply方法中第二个参数的call方法是更改指向为arguments,而且arguments中能使用slice方法,便是arguments.slice(1)
以上就是怎样用Javascript实现函数柯里化与反柯里化的具体内容,更多关于Javascript函数柯里化与反柯里化的资料请关注草根技能分享别的相关文章!

帖子地址: 

回复

使用道具 举报

分享
推广
火星云矿 | 预约S19Pro,享500抵1000!
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

草根技术分享(草根吧)是全球知名中文IT技术交流平台,创建于2021年,包含原创博客、精品问答、职业培训、技术社区、资源下载等产品服务,提供原创、优质、完整内容的专业IT技术开发社区。
  • 官方手机版

  • 微信公众号

  • 商务合作