一文搞懂Javascript中的函数柯里化(currying)

您所在的位置:网站首页 函数科里化技巧 一文搞懂Javascript中的函数柯里化(currying)

一文搞懂Javascript中的函数柯里化(currying)

2024-07-10 17:58| 来源: 网络整理| 查看: 265

目录

1. 什么是柯里化

2.柯里化的用处

(1) 延迟计算

(2) 参数复用

(3) 动态生成函数

4.将普通函数柯里化的函数

5.总结

1. 什么是柯里化

在使用JavaScript编写代码的时候,有一种函数式编程的思想,而提到函数式编程,一定绕不开一个概念,那就是柯里化。柯里化是编程语言中的一个通用的概念(不只是Js,其他很多语言也有柯里化),是指把接收多个参数的函数变换成接收单一参数的函数,嵌套返回直到所有参数都被使用并返回最终结果。更简单地说,柯里化是一个函数变换的过程,是将函数从调用方式:f(a,b,c)变换成调用方式:f(a)(b)(c)的过程。柯里化不会调用函数,它只是对函数进行转换。

柯里化是一个把具有较多 arity 的函数转换成具有较少 arity 函数的过程 -- Kristina Brainwave

下面先看一个最简单的例子,就能更加直观的认识什么是柯里化。

实现一个求三个数的加和的函数:

function addThreeNum (a, b, c) { return a + b + c; }

非常简单的一个函数,接收三个参数,返回最终的加和值

addTreeNum(6, 9 ,10);// 返回结果为25

下面对addThreeNume进行柯里化

function addhTreeNumCurry(a) { return function(b) { return function(c) { return a + b + c; } } }

新的调用方式:

addThreeNumCurry(6)(9)(10);// 返回结果同样是25 // 分部调用柯里化后的函数 const add1 = addThreeNumCurry(6);// 返回的是一个函数 const add2 = add1(9);// 返回的是一个函数 const add3 = add2(10);// 已接收到所有的参数,返回最终的计算结果 console.log(add3);// 25

在以上过程中,柯里化后的加和函数,每次都是传入单个参数,返回的函数都会保留之前传入的所有参数,并在最后一个函数传入后进行最终的计算。这就等于说,函数一直保留着之前的所有状态,等到所有条件都满足后,执行最终的操作。

2.柯里化的用处

理解了柯里化的基本概念和用法,那么下面讲讲柯里化在实际场景中的用处:

(1) 延迟计算 (2) 参数复用

这两个场景非常好理解,柯里化函数将在接收到最后一个参数的时候才进行最后的计算,与普通一次性接收所有参数的函数相比,延迟了最终计算,并且,前面传入的参数还可以被后续的调用所复用。

假设你是一个商家,要出售商品,为了卖出去更多的商品,今天决定打9折进行售卖,我们可以使用以下函数进行折扣后的售出价格计算:

function discount(price, discount) { return price * (1 - discount);// discount为小数,例如0.1代表优惠10% }

当一个用户买了一件5000元的商品,那么你收到的钱就是:

const price = discount(5000, 0.1);// = 5000 * (1 - 0.1) = 4500

当有一个顾客购买商品就会使用0.1的折扣去调用一次discount方法,那么当有很多顾客的时候,就会每次都变化discount的第一个参数,而第二个参数就一直重复一样为0.1。这里的参数一直重复一样,其实是可以优化的,我们通过对这个函数进行柯里化来进行一次优化:

// 柯里化上面的discount函数 function discountCurry(discount) { return function(price) { return price * (1 - discount); } } // 这样我们只需要先设定一个折扣 const tenPercentDiscount = discountCurry(0.1);// 设定一个10%的优化价格 // 接下来只需要对每一个商品的单价传入进行计算即可得到对应的折扣后的价格 const goodPrice1 = tenPercentDiscount(5000);// 4500 const goodPrice2 = tenPercentDiscount(1000);// 900 const goodPrice3 = tenPercentDiscount(3000);// 2700

上面的分步调用会让我们对与整个代码逻辑更加清晰,接下来我们还可以进步扩展到,我们可以动态设置折扣力度,假设第二天你需要加大折扣力度,变成优惠30%,那么直接调用这个柯里化后的discountCurry函数进行折扣设置,然后再去计算没意见商品的价格即可:

const thirtyPercentDiscount = discountCurry(0.3);// 设置一个7折的折扣力度 // 计算商品的售价 const price = thirtyPercentDiscount(5000);// 3500 (3) 动态生成函数

这里举一个实际例子。我们都知道为了兼容IE和其他浏览器的添加事件方法,通常会以下面代码进行兼容行处理:

const addEvent = (ele, type, fn, capture) => { if (window.addEventListener) { ele.addEventListener(type, (e) => fn.call(ele, e), capture); } else if (window.attachEvent) { ele.attachEvent('on'+type, (e) => fn.call(ele, e)); } }

这里会有一个问题,就是在每一次绑定事件的时候,都需要一次环境的判断,再去进行绑定,如果我们将上面的函数进行柯里化,就能规避这个问题,在使用前是做一次判断即可。

const addEvent = (function() { if (window.addEventListener) { return function(ele) { return function(type) { return function(fn) { return function(capture) { ele.addEventListener(type, (e) => fn.call(ele, e), capture); } } } } } else if (window.attachEvent) { return function(ele) { return function(type) { return function(fn) { return function(capture) { ele.addEventListener(type, (e) => fn.call(ele, e), capture); } } } } } })(); // 调用 addEvent(document.getElementById('app'))('click')((e) => {console.log('click function has been call:', e);})(false); // 分步骤调用会更加清晰 const ele = document.getElementById('app'); // get environment const environment = addEvent(ele) // bind event environment('click')((e) => {console.log(e)})(false);

上面例子虽然显得有点绕,但利用柯里化的函数可以实现动态的生成不同的函数。实际场景这些代码还可以进行优化

利用以上三种柯里化带来的好处,我们可以在实际代码中运用更多柯里化的方式去解决问题。

4.将普通函数柯里化的函数

通过上面的内容,我们知道了什么是柯里化和柯里化的应用,那么更进一步,思考一下如何把一个普通函数转换成柯里化的函数,我们可以使用一个特定的函数去专门做这件事情,那我们就可以非常轻松的把普通函数转换成柯里化后的函数了。

这个神奇的可以将普通函数柯里化的函数实现:

// 柯里化函数 const curry = function (fn) { return function nest(...args) { // fn.length表示函数的形参个数 if (args.length === fn.length) { // 当参数接收的数量达到了函数fn的形参个数,即所有参数已经都接收完毕则进行最终的调用 return fn(...args); } else { // 参数还未完全接收完毕,递归返回judge,将新的参数传入 return function (arg) { return nest(...args, arg); } } } }

接着来看调用的过程:

这段代码是一个实现柯里化(Currying)的函数。柯里化是一种将多个参数的函数转化为一系列只接受一个参数的函数的技术。

代码中定义了一个名为curry的函数,它接受一个函数fn作为参数。curry函数返回一个嵌套函数nest。

当调用nest函数时,它会检查传入的参数数量是否等于函数fn的形参个数(通过fn.length获取)。如果参数数量相等,则直接调用fn函数并传入这些参数,并返回结果。

如果参数数量不等于fn的形参个数,nest函数会返回一个新的函数。这个新函数接受一个参数paramse,并将之前传入的参数args和新参数paramse一起传递给nest函数进行递归调用。

最后,代码通过调用curry函数并传入addNum函数来创建一个新的函数addCurry。这个addCurry函数可以通过连续调用来实现柯里化的效果。

最后一行代码调用addCurry函数,并传入三个参数1、2、3,输出结果为6。这是因为addCurry函数实际上是将addNum函数柯里化后的结果,所以可以通过多次调用传入一个参数的方式来逐步计算最终结果。

当调用addCurry(1)(2)(3)时,会按照以下步骤执行:

首先调用addCurry(1),这会返回一个新的函数,相当于调用nest(1):

args = [1]args.length = 1,不等于addNum函数的形参个数3,所以返回一个新的函数,接受一个参数paramse。

接着调用返回的新函数,即function (paramse) { return nest(...args, paramse); },相当于调用nest(1, 2):

args = [1, 2]args.length = 2,仍然不等于addNum函数的形参个数3,所以继续返回一个新的函数,接受一个参数paramse。

最后再次调用返回的新函数,即function (paramse) { return nest(...args, paramse); },相当于调用nest(1, 2, 3):

args = [1, 2, 3]args.length = 3,此时参数数量等于addNum函数的形参个数3,因此直接调用addNum函数并传入参数1、2、3: addNum(1, 2, 3) = 1 + 2 + 3 = 6返回结果6作为最终输出。

通过这种方式,函数的参数可以逐步传入,每次传入一个参数,直到所有参数都传入完成后才执行最终的函数调用,实现了柯里化的效果。

 

5.总结

本文主要介绍说明函数的柯里化以及在Js编程中的实际应用。个人觉得柯里化是一个在写代码的时候的一种思想,一种指导方法论,它可以出现在任何可以用得到的地方,将这种解决问题的思想带入到平时的代码使用中,势必会增加一条更加好的解决问题的思路。希望通过此篇文章,可以让你搞懂柯里化。



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3