javascript实现函数柯里化与反柯里化过程解析


Posted in Javascript onOctober 08, 2019

函数柯里化(黑人问号脸)???Currying(黑人问号脸)???妥妥的中式翻译既视感;下面来一起看看究竟什么是函数柯里化:

维基百科的解释是:把接收多个参数的函数变换成接收一个单一参数(最初函数的第一个参数)的函数,并返回接受剩余的参数而且返回结果的新函数的技术。其由数学家Haskell Brooks Curry提出,并以curry命名。

概念往往都是干涩且难懂的,让我们用人话来解释就是:如果我们不确定这个函数有多少个参数,我们可以先给它传入一个参数,然后通过JS闭包(如若不懂JS闭包,请先学习闭包知识点再来学习本篇博文https://3water.com/article/171398.htm)来进行返回一个函数,内部函数接收除开第一个参数外的其余参数进行操作并输出,这个就是函数的柯里化;

举个小例子:

场景(需求):

众所周知程序员每天加班的时间还是比较多的,如果我们需要计算一个程序员每天的加班时间,那么我们的第一反应应该是这样;

var overtime=0;
function time(x){
  return overtime+=x;
}

time(1); //1
time(2); //3
time(3); //6

上面的代码固然没有问题,可是需要每天调用都算加一下当天的时间,很麻烦,并且每调用一次函数都要进行一定的操作,如果数据量巨大,有可能会有影响性能的风险,那么有没有可以偷懒又能解决问题的办法呢?有的!

function time(x){
 return function(y){
    return x+y;
  }   
}

var times=time(0);
times(3);

但是上面代码依然存在问题,在实际开发中很多时候我们的参数是不确定的,上面代码虽然简单的实现了柯里化的基本操作,但是对于参数不确定的情况是处理不了的;所以存在着函数参数的局限性;不过我们从上面的代码中基本可以知道函数柯里化是个啥意思了;就是一个函数调用的时候只允许传入一个参数,然后通过闭包返回内部函数去处理和接收剩余参数,返回的函数通过闭包的方式记住了time的第一个参数;

我们再来把代码改造一下:

// 首先定义一个变量接收函数
var overtime = (function() {
//定义一个数组用来接收参数
 var args = [];
//这里运用闭包,调用外部函数返回一个内部函数
 return function() {
//arguments是浏览器内置对象,专门用来接收参数

//如果参数的长度为0即没有参数的时候
  if(arguments.length === 0) {


//定义变量用来累加
   var time = 0;


//循环累加,用i和args的长度进行比较
   for (var i = 0, l = args.length; i < l; i++) {


//进行累加操作  等价于time=time+args[i]
    time += args[i];
   }


// 返回累加的结果
   return time;


//如果arguments对象参数长度不为零,即有参数的时候
  }else {


//定义的空数组添加arguments参数作为数组项,第一个参数古args作为改变this指向,第二个参数arguments把剩余参数作为数组形式添加至空数组中
   [].push.apply(args, arguments);
  }
 }
})();

overtime(3.5);  // 第一天
overtime(4.5);  // 第二天
overtime(2.1);  // 第三天
//...

console.log( overtime() );  // 10.1

代码经过我们的改造已经实现了功能,但是这不是一个函数柯里化的完整实现,那么我们要怎么完整实现呢?下面我们来介绍一种通用的实现方式:

通用的实现方式:

//定义方法currying,先传入一个参数
var currying=function(fn){
//定义空数组装arguments对象的剩余参数
 var args=[];

//利用闭包返回一个函数处理剩余参数
 return function (){


//如果arguments的参数长度为0,即没有剩余参数
  if(arguments.length===0){


//执行上面方法


//这里的this指向下面的s,类似于s(),代表参数长度为0的时候直接调用函数
   return fn.apply(this,args)
  }
  console.log(arguments)

//如果arguments的参数长度不为0,即还有剩余参数

//在数组的原型对象上添加数组,apply用来更改this的指向为args

//将[].slice.call(arguments)的数组添加到原型数组上

//这里的[].slice.call(arguments)===Array.prototype.slice.call(arguments)实质上就是将arguments对象转成数组并具有slice功能

  Array.prototype.push.apply(args,[].slice.call(arguments))
  //args.push([].slice.call(arguments))
  console.log(args)

//这里返回的arguments.callee是返回的闭包函数,callee是arguments对象里面的一个属性,用于返回正被执行的function对象
  return arguments.callee
 }
}

//这里调用currying方法并传入add函数,结果会返回闭包内部函数
 var s=currying(add);

//调用闭包内部函数,当有参数的时候会将参数逐步添加到args数组中,待没有参数传入的时候直接调用

//调用的时候支持链式操作
 s(1)(2)(3)();
//也可以一次性传入多个参数
  s(1,2,3);
 console.log(s());

JS函数柯里化的优点:

1.可以延迟计算,即如果调用柯里化函数传入参数是不调用的,会将参数添加到数组中存储,等到没有参数传入的时候进行调用;

2.参数复用,当在多次调用同一个函数,并且传递的参数绝大多数是相同的,那么该函数可能是一个很好的柯里化候选。

世间万物相对,有因必有果,当然了,有柯里化必然有反柯里化;

反柯里化(uncurrying)

从字面意思上来讲就是跟柯里化的意思相反;其实真正的反柯里化的作用是扩大适用范围,就是说当我们调用某个方法的时候,不需要考虑这个对象自身在设计的过程中有没有这个方法,只要这个方法适用于它,我们就可以使用;(这里引用的是动态语言中的鸭子类型的思想)

在学习JS反柯里化之前,我们先学习一下动态语言的鸭子类型思想,以助于我们更好的理解:

动态语言鸭子类型思想(维基百科解释):

在程序设计中,鸭子类型(duck typing)是动态类型的一种风格。

在这种风格中,一个对象有效的语义,不是由继承自特定的类或实现特定的接口,而是由当前方法和属性的集合决定。

这个概念的名字来源于由 James Whitcomb Riley 提出的鸭子测试,“鸭子测试”可以这样表述:

当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。

理论上的解释往往干涩难懂,换成人话来说就是:你是你妈妈的儿子/女儿,不管你是否优秀,是否漂亮,只要你是你妈亲生的,那么你就是你妈的儿子/女儿;换成鸭子类型就是,只要你会呱呱叫,走起来像鸭子,只要你拥有的行为像鸭子,不管你是不是鸭子,那么你就可以被称为鸭子;

在Javascript中有很多鸭子类型的引用,比如我们在对一个变量进行赋值的时候,显然是不需要考虑变量的类型的,正是因为如此,Javascript才更加的灵活,所以Javascript是一门典型的动态类型语言;

我们来看一下反柯里化中是怎么引用鸭子类型的:

//函数原型对象上添加uncurring方法
Function.prototype.uncurring = function() {
//改变this的指向  
//这里的this指向是Array.prototype.push
 var self = this;
  //这里的闭包用来返回内部函数的执行
 return function() {
  //创建一个变量,在数组的原型对象上添加shift上面删除第一个参数
  //改变数组this的指向为arguments
  var obj = Array.prototype.shift.call(arguments);
  //最后返回执行并给方法改变指向为obj也就是arguments
  // 并传入arguments作为参数
  return self.apply(obj, arguments);
 };
};

//数组原型对象上添加uncurrying方法
var push = Array.prototype.push.uncurring();

//测试一下
//匿名函数自执行
(function() {
  //这里的push就是一个函数方法了
  //相当于传入参数arguments和4两个参数,但是在上面shift方法中删除第一个参数,这里的arguments参数被截取了,所以最后实际上只传入了4
 push(arguments, 4);
 console.log(arguments); //[1, 2, 3, 4]
//匿名函数自调用并带入参数1,2,3
})(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函数的反柯里化的案例是这样写的:

//定义一个对象
var obj = {
  "length":1,
  "0":1
}
//在Function原型对象定义方法uncurrying
Function.prototype.uncurrying = function() {
  //this指向Array.prototype.push
  var self = this;
  //闭包返回一个内部函数
  return function() {
  // 这里可以拆开理解
  //首先执行apply return 
  //Function.prototype.call(Array.prototype.push[obj,2])
  //然后Array.prototype.push.call(obj,2)
  //call改变指向 obj.push(2)
  //所以最后结果就是 {0: 1, 1: 2, length: 2}
    return Function.prototype.call.apply(self, arguments);
}
}

//在
var push = Array.prototype.push.uncurrying()

push(obj, 2) 
console.log(obj);
//{0: 1, 1: 2, length: 2}

上面的方式不好理解?没关系,咱们来个好理解的:

Function.prototype.unCurrying = function () {
  var self = this;
  return function () {

//[].slice.call(arguments,1)===Array.prototype.push.slice.call(arguments,1)===arguments.slice(1)
return self.apply(arguments[0], [].slice.call(arguments, 1));
  };
};
var push = Array.prototype.push.uncurrying()
console.log(push);
push(obj,2) //{0: 1, 1: 2, length: 2}
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 相关文章推荐
SOSO地图API使用(一)在地图上画圆实现思路与代码
Jan 15 Javascript
Javascript设计模式之观察者模式(推荐)
Mar 29 Javascript
聊一聊jQuery插件uploadify使用方法
Aug 24 Javascript
微信小程序 列表的上拉加载和下拉刷新的实现
Apr 01 Javascript
vue.js内部自定义指令与全局自定义指令的实现详解(利用directive)
Jul 11 Javascript
Vue自定义事件(详解)
Aug 19 Javascript
详解让sublime text3支持Vue语法高亮显示的示例
Sep 29 Javascript
arctext.js实现文字平滑弯曲弧形效果的插件
May 13 Javascript
Vue简单封装axios之解决post请求后端接收不到参数问题
Feb 16 Javascript
JS控制下拉列表左右选择实例代码
May 08 Javascript
JS实现多选框的操作
Jun 24 Javascript
Vue中正确使用Element-UI组件的方法实例
Oct 13 Javascript
新手入门js闭包学习过程解析
Oct 08 #Javascript
关于JSON解析的实现过程解析
Oct 08 #Javascript
解决webpack多页面内存溢出的方法示例
Oct 08 #Javascript
javascript简单实现深浅拷贝过程详解
Oct 08 #Javascript
webpack HappyPack实战详解
Oct 08 #Javascript
简单了解vue中的v-if和v-show的区别
Oct 08 #Javascript
在Koa.js中实现文件上传的接口功能
Oct 08 #Javascript
You might like
PHP数组交集的优化代码分析
2011/03/06 PHP
解析thinkphp中的M()与D()方法的区别
2013/06/22 PHP
PHP实现的策略模式简单示例
2017/08/25 PHP
javascript学习(一)构建自己的JS库
2013/01/02 Javascript
js加载之使用DOM方法动态加载Javascript文件
2013/11/08 Javascript
深入分析escape()、encodeURI()、encodeURIComponent()的区别及示例
2014/08/04 Javascript
基于NodeJS的前后端分离的思考与实践(六)Nginx + Node.js + Java 的软件栈部署实践
2014/09/26 NodeJs
jQuery多个input求和的实现方法
2015/02/12 Javascript
JavaScript中的toLocaleDateString()方法使用简介
2015/06/12 Javascript
javascript实现将文件保存到本地方法汇总
2015/07/26 Javascript
jQuery Form 表单提交插件之formSerialize,fieldSerialize,fieldValue,resetForm,clearForm,clearFields的应用
2016/01/23 Javascript
JS中sort函数排序用法实例分析
2016/06/16 Javascript
浅谈Angularjs link和compile的使用区别
2016/10/21 Javascript
jQuery实现页面滚动时智能浮动定位
2017/01/08 Javascript
简单实现js倒计时功能
2017/02/13 Javascript
javascript数组常见操作方法实例总结【连接、添加、删除、去重、排序等】
2019/06/13 Javascript
JavaScript实现五子棋游戏的方法详解
2019/07/08 Javascript
Layer UI表格列日期格式化及取消自动填充日期的实现方法
2020/05/10 Javascript
python使用post提交数据到远程url的方法
2015/04/29 Python
如何利用pygame实现简单的五子棋游戏
2019/12/29 Python
Python填充任意颜色,不同算法时间差异分析说明
2020/05/16 Python
安装不同版本的tensorflow与models方法实现
2021/02/20 Python
意大利大型购物中心:Oliviero.it
2017/10/19 全球购物
荷兰超市:DEEN
2018/03/14 全球购物
Godiva巧克力英国官网:比利时歌帝梵巧克力
2018/08/28 全球购物
abstract是什么意思
2012/02/12 面试题
广告学专业毕业生自荐信
2013/09/24 职场文书
会计主管岗位职责范文
2013/11/08 职场文书
党员承诺书范文
2014/05/19 职场文书
不遵守课堂纪律的检讨书
2014/09/24 职场文书
Python爬虫基础之简单说一下scrapy的框架结构
2021/06/26 Python
Go语言基础切片的创建及初始化示例详解
2021/11/17 Golang
Win11 Build 21996.1 Dev版怎么样? win11系统截图欣赏
2021/11/21 数码科技
介绍一下28个JS常用数组方法
2022/05/06 Javascript
numpy array找出符合条件的数并赋值的示例代码
2022/06/01 Python
MySQL详细讲解变量variables的用法
2022/06/21 MySQL