简单粗暴详细讲解javascript实现函数柯里化与反柯里化
函数柯里化(黑人问号脸)???Currying(黑人问号脸)???妥妥的中式翻译既视感;下面来一起看看究竟什么是函数柯里化:
维基百科的解释是:把接收多个参数的函数变换成接收一个单一参数(最初函数的第一个参数)的函数,并返回接受剩余的参数而且返回结果的新函数的技术。其由数学家Haskell Brooks Curry提出,并以curry命名。
概念往往都是干涩且难懂的,让我们用人话来解释就是:如果我们不确定这个函数有多少个参数,我们可以先给它传入一个参数,然后通过JS闭包(如若不懂JS闭包,请先学习闭包知识点再来学习本篇博文 https://www.cnblogs.com/dengyao-blogs/p/11475575.html )来进行返回一个函数,内部函数接收除开第一个参数外的其余参数进行操作并输出,这个就是函数的柯里化;
举个小例子:
场景(需求):
众所周知程序员每天加班的时间还是比较多的,如果我们需要计算一个程序员每天的加班时间,那么我们的第一反应应该是这样;
var overtime=0;
function time(x){
return overtime+=x;
} time(1); //
time(2); //
time(3); //
上面的代码固然没有问题,可是需要每天调用都算加一下当天的时间,很麻烦,并且每调用一次函数都要进行一定的操作,如果数据量巨大,有可能会有影响性能的风险,那么有没有可以偷懒又能解决问题的办法呢?有的!
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实现函数柯里化与反柯里化的更多相关文章
- 简单粗暴详细讲解javascript实现函数柯里化
函数柯里化(黑人问号脸)???Currying(黑人问号脸)???妥妥的中式翻译既视感:下面来一起看看究竟什么是函数柯里化: 维基百科的解释是:把接收多个参数的函数变换成接收一个单一参数(最初函数的第 ...
- JS 函数的柯里化与反柯里化
===================================== 函数的柯里化与反柯里化 ===================================== [这是一篇比较久之前的总 ...
- js高阶函数应用—函数柯里化和反柯里化(二)
第上一篇文章中我们介绍了函数柯里化,顺带提到了偏函数,接下来我们继续话题,进入今天的主题-函数的反柯里化. 在上一篇文章中柯里化函数你可能需要去敲许多代码,理解很多代码逻辑,不过这一节我们讨论的反科里 ...
- JS的防抖,节流,柯里化和反柯里化
今天我们来搞一搞节流,防抖,柯里化和反柯里化吧,是不是一看这词就觉得哎哟wc,有点高大上啊.事实上,我们可以在不经意间用过他们但是你却不知道他们叫什么,没关系,相信看了今天的文章你会有一些收获的 节流 ...
- 简单粗暴地理解 JavaScript 原型链 (一个充满歪门邪理的理解方法,有助于新手哦!)
原型链理解起来有点绕了,网上资料也是很多,每次晚上睡不着的时候总喜欢在网上找点原型链和闭包的文章看,效果极好. 不要纠结于那一堆术语了,那除了让你脑筋拧成麻花,真的不能帮你什么.简单粗暴点看原型链吧, ...
- 好文要顶之 --- 简单粗暴地理解 JavaScript 原型链
原型链理解起来有点绕了,网上资料也是很多,每次晚上睡不着的时候总喜欢在网上找点原型链和闭包的文章看,效果极好. 不要纠结于那一堆术语了,那除了让你脑筋拧成麻花,真的不能帮你什么.简单粗暴点看原型链吧, ...
- JavaScript中的函数柯里化与反柯里化
一.柯里化定义 在计算机科学中,柯里化是把 接受多个参数的函数 变换成 接受一个单一参数(最初函数的第一个参数)的函数 并且返回 接受余下参数且返回结果的新函数的技术 高阶函数 高阶函数是实现柯里化的 ...
- js高阶函数应用—函数柯里化和反柯里化
在Lambda演算(一套数理逻辑的形式系统,具体我也没深入研究过)中有个小技巧:假如一个函数只能收一个参数,那么这个函数怎么实现加法呢,因为高阶函数是可以当参数传递和返回值的,所以问题就简化为:写一个 ...
- 简单粗暴地理解 JavaScript 原型链
尼玛!你特么也是够了! Don’t BB! Show me the code! function Person (name) { this.name = name; } function Mother ...
随机推荐
- JavaScript数据结构——栈的实现与应用
在计算机编程中,栈是一种很常见的数据结构,它遵从后进先出(LIFO——Last In First Out)原则,新添加或待删除的元素保存在栈的同一端,称作栈顶,另一端称作栈底.在栈中,新元素总是靠近栈 ...
- Java 设计模式 – Observer 观察者模式
目录 [隐藏] 1 代码 1.1 观察者接口: 1.2 被观察者: 1.3 观众类 : 1.4 电影类: 1.5 效果如下: 代码 说明都在注释: 观察者接口: package ObserverMod ...
- 基于Starling的mask实现
作为一个从c++转过来的程序员,flash原生的自定义mask实在是太好用,能方便实现各种效果,比如新手引导的高亮.viewport效果等.可惜starling的显示对象并不支持mask特性,查阅go ...
- Spring boot实战项目整合阿里云RocketMQ (非开源版)消息队列实现发送普通消息,延时消息 --附代码
一.为什么选择RocketMQ消息队列? 首先RocketMQ是阿里巴巴自研出来的,也已开源.其性能和稳定性从双11就能看出来,借用阿里的一句官方介绍:历年双 11 购物狂欢节零点千万级 TPS.万亿 ...
- Python 数据科学-Numpy
NumPy Numpy :提供了一个在Python中做科学计算的基础库,重在数值计算,主要用于多维数组(矩阵)处理的库.用来存储和处理大型矩阵,比Python自身的嵌套列表结构要高效的多.本身是由C语 ...
- iOS 11 变化
首先我是开发者,更关心对技术的影响,我又需要关注.学习哪些技术,猫神的文章:http://www.cocoachina.com/ios/20170607/19457.html 介绍了 ******** ...
- ZooKeeper实现读写锁
在上一篇文章,我们已经实现了分布式锁.今天更进一步,在分布式锁的基础之上,实现读写锁. 完整代码在 https://github.com/SeemSilly/codestory/tree/master ...
- bootstrap-datetimepicker时间插件使用
html头部引入相关的js和css <link rel="stylesheet" type="text/css" href="css/boots ...
- ajax前台数据到后台
var username = $("#id").val(); var user={"userAccount":username,"userPasswo ...
- Python 命令行之旅:深入 argparse(二)
作者:HelloGitHub-Prodesire HelloGitHub 的<讲解开源项目>系列,项目地址:https://github.com/HelloGitHub-Team/Arti ...