js高阶函数应用—函数柯里化和反柯里化
在Lambda演算(一套数理逻辑的形式系统,具体我也没深入研究过)中有个小技巧:假如一个函数只能收一个参数,那么这个函数怎么实现加法呢,因为高阶函数是可以当参数传递和返回值的,所以问题就简化为:写一个只有一个参数的函数,而这个函数返回一个带参数的函数,这样就实现了能写两个参数的函数了(具体参见下边代码)——这就是所谓的柯里化(Currying,以逻辑学家Hsakell Curry命名),也可以理解为一种在处理函数过程中的逻辑思维方式。
- function add(a, b) {
- return a + b;
- }
- //函数只能传一个参数时候实现加法
- function curry(a) {
- return function(b) {
- return a + b;
- }
- }
- var add2 = curry(); //add2也就是第一个参数为2的add版本
- console.log(add2())//
通过以上简单介绍我们大概了解了,函数柯里化基本是在做这么一件事情:只传递给函数一部分参数来调用它,让它返回一个函数去处理剩下的参数。用公式表示就是我们要做的事情其实是
fn(a,b,c,d)=>fn(a)(b)(c)(d);
fn(a,b,c,d)=>fn(a,b)(c)(d);
fn(a,b,c,d)=>fn(a)(b,c,d);
......
再或者这样:
fn(a,b,c,d)=>fn(a)(b)(c)(d)();
fn(a,b,c,d)=>fn(a);fn(b);fn(c);fn(d);fn();
但不是这样:
fn(a,b,c,d)=>fn(a);
fn(a,b,c,d)=>fn(a,b);
......
这类不属于柯里化内容,它也有个专业的名字叫偏函数,这个之后我们也会提到。
下面我们继续把之前的add改为通用版本:
- const curry = (fn, ...arg) => {
- let all = arg;
- return (...rest) => {
- all.push(...rest);
- return fn.apply(null, all);
- }
- }
- let add2 = curry(add, )
- console.log(add2()); //10
- add2 = curry(add);
- console.log(add2(2,)); //10
如果你想给函数执行绑定执行环境也很简单,可以多传入个参数:
- const curry = (fn, constext, ...arg) => {
- let all = arg;
- return (...rest) => {
- all.push(...rest);
- return fn.apply(constext, all);
- }
- }
不过到目前我们并没有实现柯里化,就是类似fn(a,b,c,d)=>fn(a)(b)(c)(d),这样的转化,原因也很明显,我们curry之后的add2函数只能执行一次,不能够sdd2(5)(8)这样执行,因为我们没有在函数第一次执行完后返回一个函数,而是返回的值,所以无法继续调用。
所以我们继续实现我们的curry函数,要实现的点也明确了,柯里化后的函数在传入参数未达到柯里化前的个数时候我们不能返回值,应该返回函数让它继续执行(如果你阅读到这里可以试着自己实现一下),下面给出一种简单的实现方式:
- const curry = (fn, ...arg) => {
- let all = arg || [],
- length = fn.length;
- return (...rest) => {
- let _args = all.slice(); //拷贝新的all,避免改动公有的all属性,导致多次调用_args.length出错
- _args.push(...rest);
- if (_args.length < length) {
- return curry.call(this, fn, ..._args);
- } else {
- return fn.apply(this, _args);
- }
- }
- }
- let add2 = curry(add, )
- console.log(add2());
- add2 = curry(add);
- console.log(add2(, ));
- console.log(add2()());
这里代码逻辑其实很简单,就是判断参数是否已经达到预期的值(函数柯里化之前的参数个数),如果没有继续返回函数,达到了就执行函数然后返回值,唯一需要注意的点我在注释里写出来了all相当于闭包引用的变量是公用的,需要在每个返回的函数里拷贝一份;
好了到这里我们基本实现了柯里化函数,我们来看文章开始罗列的公式,细心的同学应该能发现:
fn(a,b,c,d)=>fn(a)(b)(c)(d)();//mod1
fn(a,b,c,d)=>fn(a);fn(b);fn(c);fn(d);fn();//mod2
这两种我们的curry还未实现,对于这两个公式其实是一样的,写法不同而已,对比之前的实现就是多了一个要素,函数执行返回值的触发时机和被柯里化函数的参数的不确定性,好了我们来简单修改一下代码:
- const curry = (fn, ...arg) => {
- let all = arg || [],
- length = fn.length;
- return (...rest) => {
- let _args = all;
- _args.push(...rest);
- if (rest.length === ) {
all=[];- return fn.apply(this, _args);
- } else {
- return curry.call(this, fn, ..._args);
- }
- }
- }
- let test = curry(function(...rest) {
- let args = rest.map(val => val * );
- console.log(args);
- })
- test();
- test();
- test();
- test();
- test();
- test();
- test()()()()()()()();
- test(, , , , , )();
现在我们这个test函数的参数就可以任意传,可多可少,至于在什么时候执行返回值,控制权在我们(这里是设置的传入参数为空时候触发函数执行返回值),当然根据这逻辑我们能改造出来很多我们期望它按我们需求传参、执行的函数——这里我们就体会到了高阶函数的灵活多变,让使用者有更多发挥空间。
到这里我们科里化基本说完了,下面我们顺带说一下偏函数,如果你上边柯里化的代码都熟悉了,那么对于偏函数的这种转化形式应该得心应手了:
fn(a,b,c,d)=>fn(a);
fn(a,b,c,d)=>fn(a,b);
我们还是先来看代码吧
- function part(fn, ...arg) {
- let all = arg || [];
- return (...rest) => {
- let args = all.slice();
- args.push(...rest);
- return fn.apply(this, args)
- }
- }
- function add(a = , b = , c = ) {
- console.log(a + b + c);
- }
- let addPart = part(add);
- addPart(); //9
- addPart(, );//20
很简单了,我们现在的addPar就能随便传参都能调用了,当然我们也能控制函数只调用某一个或者多个参数,例如这样:
如上图所示,我们想用parseInt帮我们转化个数组,但是我们有没发改动parseInt的代码,所以控制一下传参就行了,这样我们map就传入的参数只取到第一个,得到了我们的期望值。
接着我们说一说反柯里化
鉴于时间较晚了,本来想写完的,还是打算先休息明晚再补上吧……
js高阶函数应用—函数柯里化和反柯里化的更多相关文章
- JS高阶---变量与函数提升
大纲: 主体: 案例1: 接下来在控制台source里进行断点测试 打好断点后,在控制台测试window .
- JS 函数的柯里化与反柯里化
===================================== 函数的柯里化与反柯里化 ===================================== [这是一篇比较久之前的总 ...
- JS高阶函数的理解(函数作为参数传递)
JS高阶函数的理解 高阶函数是指至少满足下列条件之一的函数. · 函数可以作为参数被传递 · 函数可以作为返回值输出 一个例子,我们想在页面中创建100个div节点,这是一种写法.我们发现并不是所有用 ...
- React.js高阶函数的定义与使用
/* 高阶函数的简单定义与使用 一: 先定义一个普通组件 二: 用function higherOrder(WrappendComponent) { return } 将组件包裹起来,并用export ...
- JS的防抖,节流,柯里化和反柯里化
今天我们来搞一搞节流,防抖,柯里化和反柯里化吧,是不是一看这词就觉得哎哟wc,有点高大上啊.事实上,我们可以在不经意间用过他们但是你却不知道他们叫什么,没关系,相信看了今天的文章你会有一些收获的 节流 ...
- JS高阶函数与函数柯里化
高阶函数 满足下列条件之一的函数: 函数作为参数被传递(如回调函数): 函数可以作为返回值输出: 一些内置高阶函数的例子: Array.prototype.map map()方法通过调用对输入数组中的 ...
- js高阶函数应用—函数柯里化和反柯里化(二)
第上一篇文章中我们介绍了函数柯里化,顺带提到了偏函数,接下来我们继续话题,进入今天的主题-函数的反柯里化. 在上一篇文章中柯里化函数你可能需要去敲许多代码,理解很多代码逻辑,不过这一节我们讨论的反科里 ...
- js 高阶函数 闭包
摘自 https://www.cnblogs.com/bobodeboke/p/5594647.html 建议结合另外一篇关于闭包的文章一起阅读:http://www.cnblogs.com/bob ...
- js高阶函数应用—函数防抖和节流
高阶函数指的是至少满足下列两个条件之一的函数: 1. 函数可以作为参数被传递:2.函数可以作为返回值输出: javaScript中的函数显然具备高级函数的特征,这使得函数运用更灵活,作为学习js必定会 ...
随机推荐
- 使用SQLiteOpenHelper类对数据库简单操作
实现数据库基本操作 数据库创建的问题解决了,接下来就该使用数据库实现应用程序功能的时候了.基本的操作包括创建.读取.更新.删除,即我们通常说的CRUD(Create, Read, Upda ...
- MySQL升级-5.6升级到5.7版本&切换GTID模式
目前未在生产环境中升级过数据库版本,倒是在测试环境跟开发环境升级过. 可以通过mysqldump sql文件进行升级,也可以通过mysql_upgrade升级,前者耗时较长,且 ...
- 有序的map LinkedHashMap
HashMap是无序的,HashMap在put的时候是根据key的hashcode进行hash然后放入对应的地方.所以在按照一定顺序put进HashMap中,然后遍历出HashMap的顺序跟put的顺 ...
- JavaScript(第二十天)【DOM操作表格及样式】
DOM在操作生成HTML上,还是比较简明的.不过,由于浏览器总是存在兼容和陷阱,导致最终的操作就不是那么简单方便了.本章主要了解一下DOM操作表格和样式的一些知识. 一.操作表格 <table& ...
- C语言第六次博客作业--数据类型
一.PTA实验作业 题目1:区位码输入法 1. 本题PTA提交列表 2. 设计思路 (1)定义整型变量code放区位码,areacode放区码,digitcode放位码,one放个位数,two放十位数 ...
- Spring Boot jar包linux服务器部署
Spring Boot 部署 一.使用命令行java -jar 常驻 nohup java -jar spring-boot-1.0-SNAPSHOT.jar > log.file 2>& ...
- BEM 中文翻译
BEM 原文请看 getBEM Introduction(介绍) Block 独立实体,独立的意义 Examples:header, container, menu, checkbox, input ...
- Centos6.7下面配置vim及其插件
Vim是在vi的基础上升级而来的,比vi更强大,提供代码补全,编译功能 [4]vim Vim是从 vi 发展出来的一个文本编辑器.代码补完.编译及错误跳转等方便编程的功能特别丰富,在程序员中被广泛使用 ...
- Spring Security 入门(3-10)Spring Security 的四种使用方式
原文链接: http://www.360doc.com/content/14/0724/17/18637323_396779659.shtml 下面是作者的一个问题处理
- 详解k8s零停机滚动发布微服务 - kubernetes
1.前言 在当下微服务架构盛行的时代,用户希望应用程序时时刻刻都是可用,为了满足不断变化的新业务,需要不断升级更新应用程序,有时可能需要频繁的发布版本.实现"零停机"." ...