5种方法让你的JavaScript更具功能性
在本文中,我们将简要解释什么是函数式编程,然后通过五种方法使JavaScript更具函数式风格。
什么是函数式编程?
函数式编程是一种使用函数及其应用程序,而不是使用命令列表的编程风格吗必要的编程语言。
这是一种更抽象的编程风格,它起源于数学——特别是数学的一个分支<年代trong>微积分它是数学家阿朗佐·丘奇(Alonzo Church)在1936年设计出来的,作为可计算性的正式模型。它由表达式和函数组成,将一个表达式映射到另一个表达式。基本上,这就是我们在函数式编程中所做的:我们使用函数将值转换为不同的值。
近年来,本文作者已经爱上了函数式编程。我们开始使用JavaScript库,鼓励更函数式的风格,然后通过学习如何编写代码直接进入深度Haskell.
Haskell是上世纪90年代开发的一种纯函数式编程语言,类似于Scala和Clojure。使用这些语言,您必须以函数式风格进行编码。学习Haskel让我们真正了解了函数式编程所提供的所有优势。
JavaScript是一个<年代trong>多维语言,因为它可以用于命令式、面向对象或函数式风格的编程。不过,它确实特别适合于函数式风格,就像函数一样<年代trong>一流的对象,这意味着它们可以被赋值给变量。它还意味着函数可以作为参数传递给其他函数(通常称为<年代trong>回调)以及作为其他函数的返回值。返回其他函数或接受它们作为参数的函数称为<年代trong>高阶函数,它们是函数式编程的基本部分。
近年来,以函数式风格编写JavaScript变得越来越流行,特别是随着React的兴起。React使用了一种适合函数式方法的声明性API,因此对函数式编程原则有深刻的理解将会改进React代码。
为什么函数式编程这么好?
简而言之,函数式编程语言通常会产生简洁、清晰和优雅的代码。代码通常更容易测试,并且可以在多线程环境中应用而不会出现任何问题。
如果你和很多不同的程序员交谈,你可能会从每个人那里得到完全不同的关于函数式编程的观点——从那些绝对讨厌它的人到那些绝对喜欢它的人。我们(本文的作者)站在“喜欢它”的一端,但我们完全理解它不是每个人的茶,特别是因为它与通常的编程教学方法非常不同。
然而,一旦你掌握了函数式编程的诀窍,一旦你的思维过程成功了,它就会成为你的第二天性,并改变你编写代码的方式。
规则1:净化你的功能
函数式编程的一个关键部分是确保你编写的函数是“纯的”。如果你是这学期的新手,a<年代trong>纯函数本质上满足以下条件:
- 它具有参照透明度.这意味着,给定相同的参数,函数将总是返回相同的值。任何函数调用都可以用返回值替换,程序仍然以相同的方式运行。
- 它没有副作用.这意味着函数不会在函数作用域之外进行任何更改。这可以包括更改全局值、登录到控制台或更新DOM。
纯函数必须至少有一个参数和必须返回一个值。如果你想一下,如果它们不接受任何参数,它们就没有任何数据可以处理,如果它们不返回值,函数的意义是什么?
从一开始,纯函数可能看起来并不完全必要,但不纯函数可能导致程序中的整个更改,从而导致一些严重的逻辑错误!
例如:
/ /不洁净的让最低<年代pan class="token operator">=21常量核对=年龄= >年龄<年代pan class="token operator">> =最低<年代pan class="token comment">/ /纯常量核对=年龄= >{常量最低<年代pan class="token operator">=21返回年龄<年代pan class="token operator">> =最低<年代pan class="token punctuation">}
在非纯函数中,核对
函数依赖于可变变量最低
.例如,如果最低
变量将在稍后的程序中更新核对
函数可能返回具有相同输入的布尔值。
想象一下如果我们运行这个:
核对(20.)>>假
现在,让我们想象一下,在后面的代码中,achangeToUK ()
函数更新的值最低
到18。
然后,想象我们运行这个:
核对(20.)>>真正的
现在看函数核对
得到不同的值,尽管输入相同。
纯函数使您的代码更易于移植,因为它们不依赖于作为参数提供的值之外的任何其他值。返回值永不改变的事实使得纯函数更容易测试。
始终如一地编写纯函数还可以消除突变和副作用发生的可能性。
在函数式编程中,突变是一个很大的危险信号,如果你想了解更多原因,你可以在JavaScript变量赋值和变异指南.
要使函数更可移植,请确保始终保留函数<年代trong>纯.
规则2:保持变量不变
声明变量是任何程序员学习的第一件事之一。它变得微不足道,但在使用函数式编程风格时却非常重要。
函数式编程的关键原则之一是,一旦设置了一个变量,它在整个程序中都保持该状态。
这是一个最简单的例子,说明了在代码中重新分配/重新声明变量是如何造成灾难的:
常量n<年代pan class="token operator">=10n<年代pan class="token operator">=11TypeError:"试图分配给只读属性。"
如果你想一下,的价值n
不能同时10
而且11
;这在逻辑上讲不通。
命令式编程中常见的编码实践是使用以下代码增加值:
让x<年代pan class="token operator">=5x<年代pan class="token operator">=x<年代pan class="token operator">+1
在数学中,这种表述X = X + 1
是不合逻辑的,因为如果你减去x
两边都是0 = 1
这显然是不正确的。
因此,在Haskell中,你不能将一个变量赋值给一个值,然后再将它赋值给另一个值。要在JavaScript中实现这一点,您应该遵循以下规则<年代trong>总是使用常量
.
规则3:使用箭头函数
在数学中,函数的概念是将一组值映射到另一组值的函数。下图显示了将左边的值集通过平方映射到右边的值集的函数:
这是用箭头符号写出来的数学形式:F: x→x²
.这意味着函数f
映射值x
来x²
.
我们可以使用箭头函数几乎相同地写出这个函数:
常量f=x= >x<年代pan class="token operator">**2
在JavaScript中使用函数式样式的一个关键特性是使用箭头函数与常规函数相反。当然,这确实归结为风格,使用箭头函数而不是常规函数实际上并不会影响代码的“功能性”。
然而,在使用函数式编程风格时,最难适应的事情之一是每个函数都是输入到输出的映射。根本就没有所谓的程序。我们发现使用箭头函数可以帮助我们更好地理解函数的过程。
箭头函数有一个隐式的返回值,这确实有助于可视化这个映射。
箭头函数的结构——尤其是它们的隐式返回值——有助于鼓励纯函数的编写,因为它们的结构字面上是“输入映射到输出”:
arg游戏= >returnValue
我们要强调的另一件事,特别是在写箭头函数时,是三元操作符的使用。如果你不熟悉<年代trong>三元运算符,它们是内联的如果其他……
陈述和形式的条件?true为值:false为值
.
你可以在快速提示:如何在JavaScript中使用三元操作符.
函数式编程中使用三元运算符的主要原因之一是其他的
声明。这个项目必须知道如果原来的条件不满足该怎么办。例如,Haskell强制执行其他的
语句,如果没有给出,则返回错误。
使用三元运算符的另一个原因是<年代trong>表达式总是返回一个值,而不是if - else
可用于执行具有潜在副作用的操作的语句。这对于箭头函数特别有用,因为这意味着您可以确保有一个返回值,并保持输入到输出的映射图像。如果你不确定语句和表达式之间的细微区别,这是关于语句和表达式的指南很值得一读。
为了说明这两个条件,这里有一个使用三元操作符的简单箭头函数示例:
常量行动=状态= >状态<年代pan class="token operator">= = =“饿”?“吃蛋糕”:“睡眠”
的行动
函数将返回值“eat”或“sleep”,这取决于状态
论点。
因此,总结一下:在让你的代码更具功能性时,你应该遵循以下两条规则:
- 使用箭头符号编写函数
- 取代
如果其他……
带有三元运算符的语句
规则4:删除For循环
考虑到使用为
循环编写迭代代码在编程中很常见,说要避免循环似乎很奇怪。事实上,当我们第一次发现哈斯克尔根本没有任何为
循环操作,我们努力理解一些标准操作是如何实现的。然而,有一些很好的理由为
循环不会出现在函数式编程中,我们很快就发现每种类型的迭代过程都可以不使用而实现为
循环。
不使用的最重要的原因为
循环依赖于可变状态。让我们看一个简单的总和
功能:
函数总和(n){让k<年代pan class="token operator">=0为(让我<年代pan class="token operator">=1;我<年代pan class="token operator"><n<年代pan class="token operator">+1;我<年代pan class="token operator">++){k<年代pan class="token operator">=k<年代pan class="token operator">+我<年代pan class="token punctuation">}返回k<年代pan class="token punctuation">}总和(5)=15// 1 + 2 + 3 + 4 + 5
如你所见,我们必须使用让
在为
循环本身,对于我们要更新的变量为
循环。
如前所述,这在函数式编程中是典型的坏习惯,因为函数式编程中的所有变量都应该是不可变的。
如果我们想要编写所有变量都是不可变的代码,我们可以使用递归:
常量总和=n= >n<年代pan class="token operator">= = =1?1:n<年代pan class="token operator">+总和(n<年代pan class="token operator">-1)
如您所见,没有变量被更新过。
我们中的数学家显然知道所有这些代码都是不必要的,因为我们可以使用漂亮的和公式0.5 * n * (n + 1)
.但这是一个很好的方法来说明变异之间的区别为
循环与递归。
递归并不是可变性问题的唯一解决方案,特别是当我们处理数组时。JavaScript有很多内置的高阶数组方法,这些方法在数组中的值中循环,而不会改变任何变量。
例如,假设我们想对数组中的每个值加1。使用命令式方法和为
循环,我们的函数看起来是这样的:
函数addOne(数组){为(让我<年代pan class="token operator">=0;我<年代pan class="token operator"><数组<年代pan class="token punctuation">.长度;我<年代pan class="token operator">++){数组<年代pan class="token punctuation">[我<年代pan class="token punctuation">]=数组<年代pan class="token punctuation">[我<年代pan class="token punctuation">]+1}返回数组<年代pan class="token punctuation">}addOne([1,2,3.])= = =[2,3.,4]
然而,取而代之的是为
循环,我们可以使用JavaScript的内置地图
方法,并编写如下所示的函数:
常量addOne=数组= >数组<年代pan class="token punctuation">.地图(x= >x<年代pan class="token operator">+1)
如果你从未见过地图
函数之前,绝对值得学习它们——以及JavaScript内置的所有高阶数组方法,例如过滤器
,特别是如果你真的对JavaScript函数式编程感兴趣的话。你可以在不可变数组方法:如何编写非常干净的JavaScript代码.
哈斯克尔没有为
完全是循环。为了使JavaScript更具功能性,应尽量避免使用for循环,而是使用递归和内置的高阶数组方法。
规则5:避免类型强制转换
当使用JavaScript等不需要类型声明的语言进行编程时,很容易忘记数据类型的重要性。JavaScript中使用的七种基本数据类型是:
- 数量
- 字符串
- 布尔
- 象征
- 长整型数字
- 未定义的
- 零
哈斯克尔是个<年代trong>强类型需要类型声明的语言。这意味着,在任何函数之前,您需要指定输入数据的类型和输出数据的类型,使用Hindley-Milner系统.
例如:
添加::整数->整数->整数添加xy=x+y
这是一个非常简单的函数,将两个数字相加(x
而且y
).必须向程序解释每个函数的数据类型,包括像这样非常简单的函数,这似乎有点可笑,但最终它有助于显示函数的工作方式以及期望返回什么。这使得代码多更容易调试,特别是当它开始变得更加复杂时。
类型声明遵循以下结构:
functionName<年代pan class="token operator">::inputType(年代<年代pan class="token punctuation">)->outputType
当使用JavaScript时,类型强制可能是一个大问题,因为JavaScript有各种各样的hack可以使用(甚至滥用)来解决数据类型不一致的问题。以下是一些最常见的错误,以及如何避免这些错误:
- 连接.
“你好”+ 5
计算结果为“Hello5”
,这并不一致。如果要将字符串与数值连接起来,则应该写入"Hello" +字符串(5)
. - 布尔语句和0.在JavaScript中,的值
0
在一个如果
语句是等价的假
.这可能导致懒惰编程技术,忽略检查数值数据是否等于0
.
例如:
常量甚至=n= >!(n<年代pan class="token operator">%2)
这是一个计算数字是否为偶数的函数。它使用!
符号胁迫的结果n % 2 ?
转换为布尔值,但结果n % 2
不是布尔值,而是一个数字(也是)0
或1
).
像这样的技巧,虽然看起来很聪明,减少了你编写的代码量,但打破了函数式编程的类型一致性规则。因此,写这个函数最好的方式是这样的:
// even:: Number ->号码常量甚至=n= >n<年代pan class="token operator">%2= = =0
另一个重要的概念是确保数组中的所有数据值都是相同的类型。JavaScript并没有强制执行这一点,但是当您想使用高阶数组方法时,没有相同的类型会导致问题。
例如,产品
将数组中的所有数字相乘并返回结果的函数可以使用以下类型声明注释来编写:
// product:: [Number] ->号码常量产品=数字= >数字<年代pan class="token punctuation">.减少((年代<年代pan class="token punctuation">,x)= >x<年代pan class="token operator">*年代<年代pan class="token punctuation">,1)
这里,类型声明清楚地表明,函数的输入是一个包含该类型元素的数组数量
,但它只返回一个数字。类型声明明确了该函数的输入和输出是什么。显然,如果数组不只是由数字组成,这个函数就不能工作。
Haskell是一种强类型语言,而JavaScript是弱类型语言,但是为了使JavaScript更具功能性,您应该在声明函数之前编写类型声明注释,并确保避免使用类型强制快捷方式。
我们还应该在这里提到,你显然可以求助于打印稿如果你想要一个强类型的替代JavaScript来为你加强类型一致性。
结论
总结一下,这里有5条规则可以帮助你实现函数式代码:
- 保留函数纯.
- 声明变量和函数时总是使用常量.
- 使用箭头函数的符号。
- 避免使用
为
循环. - 使用类型声明注释,避免类型强制快捷方式。
虽然这些规则不能保证你的代码是纯功能性的,但它们会让你的代码更功能性,更简洁,更清晰,更容易测试。
我们真的希望这些规则能像帮助我们一样帮助你!我们都是函数式编程的忠实粉丝,我们强烈鼓励任何程序员使用它。
如果您想深入了解函数式JavaScript,我们强烈推荐您阅读弗里斯比教授的《函数式编程基本指南》这本书可以在网上免费下载。如果你想学习Haskell,我们建议你使用尝试Haskell互动教程和精彩阅读为了更大的利益,学习哈斯克尔这本书也可以在网上免费阅读。