在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()());
let test = curry(function(a, b, c) {
console.log(a + b + c);
})
test(1, 2, 3);
test(1, 2)(3);
test(1)(2)(3);

这里代码逻辑其实很简单,就是判断参数是否已经达到预期的值(函数柯里化之前的参数个数),如果没有继续返回函数,达到了就执行函数然后返回值,唯一需要注意的点我在注释里写出来了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高阶函数应用—函数柯里化和反柯里化的更多相关文章

  1. JS高阶---变量与函数提升

    大纲: 主体: 案例1: 接下来在控制台source里进行断点测试 打好断点后,在控制台测试window .

  2. JS 函数的柯里化与反柯里化

    ===================================== 函数的柯里化与反柯里化 ===================================== [这是一篇比较久之前的总 ...

  3. JS高阶函数的理解(函数作为参数传递)

    JS高阶函数的理解 高阶函数是指至少满足下列条件之一的函数. · 函数可以作为参数被传递 · 函数可以作为返回值输出 一个例子,我们想在页面中创建100个div节点,这是一种写法.我们发现并不是所有用 ...

  4. React.js高阶函数的定义与使用

    /* 高阶函数的简单定义与使用 一: 先定义一个普通组件 二: 用function higherOrder(WrappendComponent) { return } 将组件包裹起来,并用export ...

  5. JS的防抖,节流,柯里化和反柯里化

    今天我们来搞一搞节流,防抖,柯里化和反柯里化吧,是不是一看这词就觉得哎哟wc,有点高大上啊.事实上,我们可以在不经意间用过他们但是你却不知道他们叫什么,没关系,相信看了今天的文章你会有一些收获的 节流 ...

  6. JS高阶函数与函数柯里化

    高阶函数 满足下列条件之一的函数: 函数作为参数被传递(如回调函数): 函数可以作为返回值输出: 一些内置高阶函数的例子: Array.prototype.map map()方法通过调用对输入数组中的 ...

  7. js高阶函数应用—函数柯里化和反柯里化(二)

    第上一篇文章中我们介绍了函数柯里化,顺带提到了偏函数,接下来我们继续话题,进入今天的主题-函数的反柯里化. 在上一篇文章中柯里化函数你可能需要去敲许多代码,理解很多代码逻辑,不过这一节我们讨论的反科里 ...

  8. js 高阶函数 闭包

    摘自  https://www.cnblogs.com/bobodeboke/p/5594647.html 建议结合另外一篇关于闭包的文章一起阅读:http://www.cnblogs.com/bob ...

  9. js高阶函数应用—函数防抖和节流

    高阶函数指的是至少满足下列两个条件之一的函数: 1. 函数可以作为参数被传递:2.函数可以作为返回值输出: javaScript中的函数显然具备高级函数的特征,这使得函数运用更灵活,作为学习js必定会 ...

随机推荐

  1. redis 相关知识

    1. 什么是Redis Redis是由意大利人Salvatore Sanfilippo(网名:antirez)开发的一款内存高速缓存数据库.Redis全称为:Remote Dictionary Ser ...

  2. Struts2 之 Action 类访问 WEB 资源

    接着上次博客的内容我继续分享我所学到的知识,和自己在学习过程中所遇到问题以及解决方案.当然,如果读者发现任何问题均可以在下方评论告知我,先谢! 在 Action 中访问 WEB 资源 web 资源 所 ...

  3. (译文)开始学习Webpack-应用TypeScript,配置热加载和Source Map

    项目初始化:采用TypeScript 我们的版本是: $ node --version v8.5.0 $ npm --version 5.5.1 npm版本升级了,因为npm最近带来了新特性,本地会生 ...

  4. 敏捷开发每日报告--day4

    1 团队介绍 团队组成: PM:齐爽爽(258) 小组成员:马帅(248),何健(267),蔡凯峰(285)  Git链接:https://github.com/WHUSE2017/C-team 2 ...

  5. Alpha冲刺Day10

    Alpha冲刺Day10 一:站立式会议 今日安排: 由林静完成第三方机构的用户信息管理模块 由张梨贤完成第三方机构的委托授权管理模块 由黄腾飞和周静平完成政府人员模块下风险管控子模块下的核实企业风险 ...

  6. Python choice() 函数

    Python choice() 函数  Python 数字 描述 choice() 方法返回一个列表,元组或字符串的随机项. 语法 以下是 choice() 方法的语法: import random ...

  7. 怎么去理解JAVA中类与对象的关系

    首先要明确,在现实生活中,每一个物体都有自己的基本特征,专业一点也可以说成是属性有些甚至还有一定的行为.例如 汽车的特征:有车门.有轮胎.颜色各一等等,行为:有行驶,开车门,开车灯,等等.有这些属性和 ...

  8. 前端之bootstrap模态框

    简介:模态框(Modal)是覆盖在父窗体上的子窗体.通常,目的是显示来自一个单独的源的内容,可以在不离开父窗体的情况下有一些互动.子窗体可提供信息.交互等. Modal简介 Modal实现弹出表单 M ...

  9. CISCO路由器练习

    前言: 总结了昨天的学习和今天的单臂路由 写了今天的文章. 目录: 路由器的基本配置 单臂路由的练习 正文: 路由器基本配置 环境要求 cisco模拟器 2台交换机 2台PC 1台路由器 路由器介绍: ...

  10. 部分和问题 nyoj

    部分和问题 时间限制:1000 ms  |  内存限制:65535 KB 难度:2   描述 给定整数a1.a2........an,判断是否可以从中选出若干数,使它们的和恰好为K.   输入 首先, ...