在本文中,我们将讨论 javascript 中的函数柯里化,这是函数式编程中的一个高级概念。
JavaScript 是 Web 的核心技术之一。大多数网站都使用它,所有现代网络浏览器都支持它,无需插件。在本系列中,我们将讨论有助于您进行日常 javaScript 开发的不同技巧和窍门。
什么是函数柯里化?
函数柯里化是一种用于处理 JavaScript 函数的高级技术。事实上,它不仅限于 JavaScript——它也用于其他编程语言。
根据维基百科:
柯里化是将一个接受多个参数的函数转换为一系列函数的技术,每个函数接受一个参数。
换句话说,柯里化只是将一个接受多个参数的函数转换为一个接受单个参数的嵌套函数序列。例如,对于f
接受三个参数的函数,您可以将其称为f(arg1, arg2, arg3)
. 当您使用函数柯里化时,您可以将其称为f(arg1)(arg2)(arg3)
.
假设您有一个接受三个参数的函数,如以下代码段所示。
function fooBar(arg1, arg2, arg3) { … }
要调用上述函数,您将使用以下语法。
fooBar(1,2,3);
现在,让我们看一下上述函数的柯里化版本的简单实现:
function fooBarCurriedVersion(arg1) { return (arg2) => { return (arg3) => { return fooBar(arg1, arg2, arg3) } } }
然后,您可以fooBarCurriedVersion
使用以下语法调用该函数:
fooBarCurriedVersion(arg1)(arg2)(arg3);
这样做的一个优点是您可以将每个参数传递给函数。例如,如果您只知道arg1
代码中某个点的值,则可以仅使用该参数调用 curried 函数并将结果函数传递给代码的其余部分。
让我们尝试了解它是如何执行的。
首先,fooBarCurriedVersion(arg1)
执行该语句,并返回带有单个参数的可调用对象。接下来,使用参数调用函数返回的可调用函数fooBarCurriedVersion
,arg2
并再次返回带有单个参数的可调用函数。最后,使用参数调用上一步返回的可调用函数arg3
。
需要注意的是,由于所有函数都是闭包,因此传递的参数值会在函数调用之间保留。因此,如果您在初始化 curried 函数的参数后调用,则该函数实例将固定该参数,无论您稍后创建什么其他实例。
您还可以调用 curried 函数,如以下代码段所示。它的工作原理与fooBar(arg1)(arg2)(arg3)
.
let callableOne = fooBarCurriedVersion(arg1); let callableTwo = callableOne(arg2); let result = callableTwo(arg3);
如您所见,当我们使用函数柯里化时,函数接受一个参数并返回一个可调用函数,该函数接受下一个参数并返回另一个可调用函数,依此类推,直到所有参数都用完。
实际上,您也可以按照以下代码段所示调用它。
let callable = fooBarCurriedVersion(1); let result = callable(2)(3);
以上就是 JavaScript 中函数柯里化的基础知识。在下一节中,我们将通过一个真实的示例来演示它是如何工作的。
一个真实的例子
现在,您知道函数柯里化在 JavaScript 中是如何工作的了。在本节中,我们将了解如何在日常 JavaScript 开发中使用它。
首先,让我们看看下面的函数,它在添加必要的费用并应用折扣后计算产品的最终价格。
function calFinalPrice(actualPrice, charges, discountRate) { var finalPrice; finalPrice = actualPrice + charges - (actualPrice * discountRate/100); }
现在,您可以调用此函数,如以下代码段所示。
const actualPrice = 100; const charges = 20; // get discount rate from the configuration const discountRate = getDiscountRateFromConfig(); calFinalPrice(actualPrice, charges, discountRate);
如您所见,我们从配置中获取折扣率,因此每次都会固定。calFinalPrice
因此,如果我们创建函数的柯里化版本,如下面的代码片段所示,我们可以避免每次调用函数时在第三个参数中传递它。
首先,我们实现了calFinalPriceWithDiscount
函数,它是函数的柯里化版本calFinalPrice
。在calFinalPriceWithDiscount
函数的第一个参数中,我们传递了折扣率,稍后将用于计算产品的最终价格。
该discVersionFunc
变量包含可调用对象,它接受两个参数:实际价格和费用。由于该discVersionFunc
版本将折扣率包装在一个闭包中,因此您无需在每次需要计算产品的最终价格时都传递它。
最后,您可以通过传递价格和费用值来计算产品的最终价格,如上面的代码片段所示。
现在,假设您想在一段时间内提供特别折扣。您仍然可以使用该calFinalPriceWithDiscount
功能,如以下代码段所示。
const speicalDiscountRate = getSpecialDiscountRateFromConfig(); const specialDiscVersionFunc = calFinalPriceWithDiscount(specialDiscountRate); alert(specialDiscVersionFunc(100)(25)); alert(specialDiscVersionFunc(200)(15));
如您所见,函数柯里化的主要好处是,当您需要重复调用具有相同参数的函数时,您可以重用和重构您的代码,随着时间的推移,这变得更容易维护。
何时使用函数柯里化
在某些情况下,函数柯里化可能会有所帮助,但默认情况下,您并不想对所有函数执行此操作。当它可以帮助您实现以下目标之一时,请考虑使用函数柯里化:
编写更干净的代码
去除昂贵的计算
map
为or创建一个单参数函数forEach
用更少的重复编写更简洁的代码
有时函数柯里化可以简化你的代码。假设您有一个记录功能logToFile(filename, appname, text)
,可以将一些文本记录到文件中。为了更容易在代码中添加日志语句,您可能希望设置文件名和应用程序名称一次,然后简单地编写类似log(text)
. 为此,您可以创建日志记录函数的柯里化版本:
//a curried version of the logging function const logCurried = filename => appname => text => logToFile(filename, appname, text) //create logging functions for a specific file and app let log = logCurried("somepath/filename-error.log")("My App") let logWarning = logCurried("somepath/filename-warning.log")("My App") //now we can use the functions like this: log("an error occurred") logWarning("just a warning")
删除昂贵的计算
柯里化函数的另一个用途是节省昂贵的计算——比如文件 I/O 或数据库读取。例如,假设您具有以下功能:
//get attributes from an item in the database function getItemAttribute (id, attribute) { //read the item from the database let item = databaseRead(id) //return the requested attribute value return item[attribute] } //get some attributes from a particular item let color=getItemAttribute("item001", "color") let shape=getItemAttribute("item001", "shape") let size=getItemAttribute("item001", "size")
假设我们想连续读取同一个函数的多个属性。这意味着多次读取同一个数据库记录,既浪费又缓慢。我们可以用 currying 重写getItemAttribute
函数以提高效率:
//get attributes from an item in the database function getItemAttribute (id) { //read the item from the database let item = databaseRead(id) //and return a function to get attributes from that item return attribute => item[attribute] } //get some attributes from a particular item const item001 = getItemAttributes("item001") let color=item001("color") let shape=item001("shape") let size=item001("size")
map
为or创建一个单参数函数forEach
使用map
andforEach
方法,您可以将函数应用于数组的每个元素。但是,这些方法希望函数具有特定的签名——通常您只想使用带有单个参数的方法。使用函数柯里化,您可以创建可与map
or一起使用的任何函数的版本forEach
。
结论
今天,我们讨论了函数柯里化在 JavaScript 中的工作原理。我们还通过几个实际示例来了解函数柯里化在 JavaScript 中的实际应用。
- 用更少的重复编写更简洁的代码
- 删除昂贵的计算
- map为or创建一个单参数函数forEach