在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. attr与prop html与text

  2. 03-第一个脚本程序以及输入输出_Python编程之路

    上节课已经教大家安装了Python的解释器,那么这节课我们就可以正式来写代码了 说明:在下面的代码演示中,我将大部分使用python交互器演示代码的输入输出,注意">>>& ...

  3. ES5和ES6两个值的比较

    ES5比较两个值是否相等 1)相等运算符 (==):比较两个数值是否相等,自动转换类型后再进行比较 2)全等运算符(===):比较两个比较值的数值和类型是否相等 ES5的特殊: ES6提出" ...

  4. 总结随笔(Beta)

    听说 -- beta冲刺总结 beta冲刺成员名单 姓名 学号 负责方向 个人主页 周龙荣 031402543 前端页面.跳转 http://www.cnblogs.com/ZHOULR/ 李家鹏 0 ...

  5. 201621123060《JAVA程序设计》第十一周学习总结

    1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结多线程相关内容. 2. 书面作业 本次PTA作业题集多线程 1. 源代码阅读:多线程程序BounceThread 1.1 BallR ...

  6. 201621123043 《Java程序设计》第6周学习总结

    1.1 面向对象学习暂告一段落,请使用思维导图,以封装.继承.多态为核心概念画一张思维导图或相关笔记,对面向对象思想进行一个总结. 注1:关键词与内容不求多,但概念之间的联系要清晰,内容覆盖面向对象的 ...

  7. android 广播安装指定下载的apk

    // 广播出去,由广播接收器来处理下载完成的文件   Intent sendIntent = new Intent("com.test.downloadComplete");    ...

  8. js判断IE浏览器版本(IE8及以下)

    var DEFAULT_VERSION = 8.0; var ua = navigator.userAgent.toLowerCase(); var isIE = ua.indexOf("m ...

  9. 读论文系列:Object Detection ECCV2016 SSD

    转载请注明作者:梦里茶 Single Shot MultiBox Detector Introduction 一句话概括:SSD就是关于类别的多尺度RPN网络 基本思路: 基础网络后接多层featur ...

  10. Python内置函数(8)——bool

    英文文档: class bool([x]) Return a Boolean value, i.e. one of True or False. x is converted using the st ...