js高阶函数应用—函数柯里化和反柯里化

在Lambda演算(一套数理逻辑的形式系统,具体我也没深入研究过)中有个小技巧:假如一个函数只能收一个参数,那么这个函数怎么实现加法呢,因为高阶函数是可以当参数传递和返回值的,所以问题就简化为:写一个只有一个参数的函数,而这个函数返回一个带参数的函数,这样就实现了能写两个参数的函数了(具体参见下边代码)——这就是所谓的柯里化(Currying,以逻辑学家Hsakell Curry命名),也可以理解为一种在处理函数过程中的逻辑思维方式。

 1 function add(a, b) {
 2     return a + b;
 3 }
 4
 5 //函数只能传一个参数时候实现加法
 6 function curry(a) {
 7     return function(b) {
 8         return a + b;
 9     }
10 }
11 var add2 = curry(2); //add2也就是第一个参数为2的add版本
12 console.log(add2(3))//5

通过以上简单介绍我们大概了解了,函数柯里化基本是在做这么一件事情:只传递给函数一部分参数来调用它,让它返回一个函数去处理剩下的参数。用公式表示就是我们要做的事情其实是

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改为通用版本:

 1 const curry = (fn, ...arg) => {
 2     let all = arg;
 3     return (...rest) => {
 4         all.push(...rest);
 5         return fn.apply(null, all);
 6     }
 7 }
 8 let add2 = curry(add, 2)
 9 console.log(add2(8));    //10
10 add2 = curry(add);
11 console.log(add2(2,8)); //10

如果你想给函数执行绑定执行环境也很简单,可以多传入个参数:

1 const curry = (fn, constext, ...arg) => {
2     let all = arg;
3     return (...rest) => {
4         all.push(...rest);
5         return fn.apply(constext, all);
6     }
7 }

不过到目前我们并没有实现柯里化,就是类似fn(a,b,c,d)=>fn(a)(b)(c)(d),这样的转化,原因也很明显,我们curry之后的add2函数只能执行一次,不能够sdd2(5)(8)这样执行,因为我们没有在函数第一次执行完后返回一个函数,而是返回的值,所以无法继续调用。

所以我们继续实现我们的curry函数,要实现的点也明确了,柯里化后的函数在传入参数未达到柯里化前的个数时候我们不能返回值,应该返回函数让它继续执行(如果你阅读到这里可以试着自己实现一下),下面给出一种简单的实现方式:

 1 const curry = (fn, ...arg) => {
 2     let all = arg || [],
 3         length = fn.length;
 4     return (...rest) => {
 5         let _args = all.slice(0); //拷贝新的all,避免改动公有的all属性,导致多次调用_args.length出错
 6         _args.push(...rest);
 7         if (_args.length < length) {
 8             return curry.call(this, fn, ..._args);
 9         } else {
10             return fn.apply(this, _args);
11         }
12     }
13 }
14 let add2 = curry(add, 2)
15 console.log(add2(8));
16 add2 = curry(add);
17 console.log(add2(2, 8));
18 console.log(add2(2)(8));
19 let test = curry(function(a, b, c) {
20 console.log(a + b + c);
21 })
22 test(1, 2, 3);
23 test(1, 2)(3);
24 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还未实现,对于这两个公式其实是一样的,写法不同而已,对比之前的实现就是多了一个要素,函数执行返回值的触发时机和被柯里化函数的参数的不确定性,好了我们来简单修改一下代码:

 1 const curry = (fn, ...arg) => {
 2     let all = arg || [],
 3         length = fn.length;
 4     return (...rest) => {
 5         let _args = all;
 6         _args.push(...rest);
 7         if (rest.length === 0) {
 8        all=[];
 9             return fn.apply(this, _args);
10         } else {
11             return curry.call(this, fn, ..._args);
12         }
13     }
14 }
15 let test = curry(function(...rest) {
16     let args = rest.map(val => val * 10);
17     console.log(args);
18 })
19 test(2);
20 test(2);
21 test(3);
22 test();
23 test(5);
24 test();
25 test(2)(2)(2)(3)(4)(5)(6)();
26 test(2, 3, 4, 5, 6, 7)();

现在我们这个test函数的参数就可以任意传,可多可少,至于在什么时候执行返回值,控制权在我们(这里是设置的传入参数为空时候触发函数执行返回值),当然根据这逻辑我们能改造出来很多我们期望它按我们需求传参、执行的函数——这里我们就体会到了高阶函数的灵活多变,让使用者有更多发挥空间。

到这里我们科里化基本说完了,下面我们顺带说一下偏函数,如果你上边柯里化的代码都熟悉了,那么对于偏函数的这种转化形式应该得心应手了:

fn(a,b,c,d)=>fn(a);

fn(a,b,c,d)=>fn(a,b);

我们还是先来看代码吧

 1 function part(fn, ...arg) {
 2     let all = arg || [];
 3     return (...rest) => {
 4         let args = all.slice(0);
 5         args.push(...rest);
 6         return fn.apply(this, args)
 7     }
 8 }
 9
10 function add(a = 0, b = 0, c = 0) {
11     console.log(a + b + c);
12 }
13 let addPart = part(add);
14 addPart(9); //9
15 addPart(9, 11);//20

很简单了,我们现在的addPar就能随便传参都能调用了,当然我们也能控制函数之调用某一个或者多个参数,例如这样:

如上图所示,我们想用parseInt帮我们转化个数组,但是我们有没发改动parseInt的代码,所以控制一下传参就行了,这样我们map就传入的参数只取到第一个,得到了我们的期望值。

原文地址:https://www.cnblogs.com/johnhery/p/9805931.html

时间: 2024-09-30 15:15:23

js高阶函数应用—函数柯里化和反柯里化的相关文章

js之柯里化与反柯里化

先给大家介绍什么是柯里化与反柯里化 百度翻译: 在计算机科学中,柯里化(Currying)是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果的新函数的技术.这个技术由 Christopher Strachey 以逻辑学家 Haskell Curry 命名的,尽管它是 Moses Schnfinkel 和 Gottlob Frege 发明的. 柯里化 柯里化又称部分求值,其含义是给函数分步传递参数,每次传递参数后部分应用参数,并返回一个更具

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

今天我们来搞一搞节流,防抖,柯里化和反柯里化吧,是不是一看这词就觉得哎哟wc,有点高大上啊.事实上,我们可以在不经意间用过他们但是你却不知道他们叫什么,没关系,相信看了今天的文章你会有一些收获的 节流 首先我们来搞一下节流,啥叫节流,就是将高频率触发事件变成低频率触发事件,举个简单的例子,但我们用window.onscroll滚动事件的时候你会发现滚轮滑动一次可能会触发好多次事件, 代码: window.onscroll = function(){ console.log("触发")

js 高阶函数 闭包

摘自  https://www.cnblogs.com/bobodeboke/p/5594647.html 建议结合另外一篇关于闭包的文章一起阅读:http://www.cnblogs.com/bobodeboke/p/6127650.html 一.闭包 闭包某种程度上就是函数的内部函数,可以引用外部函数的局部变量.当外部函数退出后,如果内部函数依旧能被访问到,那么内部函数所引用的外部函数的局部变量就也没有消失,该局部变量的生存周期就被延续. 一个经典的例子如下: <script> //thi

浅谈JS高阶函数

引入 我们都知道函数是被设计为执行特定任务的代码块,会在某代码调用它时被执行,获得返回值或者实现其他功能.函数有函数名和参数,而函数参数是当调用函数接收的真实的值. 今天要说的高阶函数的英文为Higher-order function, 高阶函数的高阶是什么意思呢? 定义 至少满足下列一个条件的函数 接收一个或多个函数作为输入 输出一个函数 怎么理解这么个东西呢?引言里说过了,函数实际上都是指向某个变量.既然变量可以是一个向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,这

js高阶函数

map()方法定义在JavaScript的Array中,我们调用Array的map()方法,传入我们自己的函数,就得到了一个新的Array作为结果: function pow(x) { return x * x; } var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9]; arr.map(pow); // [1, 4, 9, 16, 25, 36, 49, 64, 81] reduce()把一个函数作用在这个Array的[x1, x2, x3...]上,这个函数必须接收两个

js高阶函数filter、map、reduce

1 // 高阶函数 filter/map/reduce 2 3 // filter中的回调函数有一个要求:必须返回一个boolean值, 4 // 当返回true时,函数内部会自动将这次回调的 n 加入到新的数组中 5 // 当返回false时,函数内部会自动过滤掉这次的 n 6 const nums=[10,20,60,94,348,57,24,674,645,44,4] 7 let newNums = nums.filter(function (n){ 8 return n<30 9 })

JS高阶编程技巧--compose函数

先看代码: let fn1 = function (x) { return x + 10; }; let fn2 = function (x) { return x * 10; }; let fn3 = function (x) { return x / 10; }; console.log(fn3(fn1(fn2(fn1(6))))); 这是几个简单的运算方法,但想输出的是一个多层函数嵌套的运行结果,即把前一个函数的运行结果赋值给后一个函数,当然我们可以写成一下这样: let x = fn1(

react.js 高阶组件----很简单的实例理解高阶组件思想

/* * 高阶组件其实是一个函数,传进去的一个组件,返回一个新组件 * 实现不同组件中的逻辑复用, * 将一些可以单独抽离的逻辑处理给要返回的新组件里面复用 * 然后将单独的组件,传递给新组件 * */ import React, {Component} from 'react' import ReactDOM from 'react-dom' //高阶组件定义,里面return 返回新组件 function local(Comp,key){ class Proxy extends Compon

JS高阶---显式原型和隐式原型

前言: 1.函数对象即函数的prototype原型属性指向原型对象,在创建函数时便存在,默认为空Object 2.实例对象的__proto__隐式原型在实例化创建实例时产生,值等于构造函数的显式prototype原型属性的值 3.开发时程序员操作显式原型在原型prototype上添加方法,不能直接操作隐式原型__proto__(ES6之前) 大纲: [主体] (1)每个函数都有一个prototype原型属性,称之为显式原型属性或显式原型 (2)每个实例对象都有一个__prop__原型属性,称之为