函数式编程
前言
在学习前端时,我们经常会看到这样一句话:
“函数是 JavaScript 中的一等公民”
简单来说,在编程语言中,一等公民就是可以作为参数传递,可以作为结果返回,也可以分配给变量的,支持操作的实体。比如在大多数编程语言中,字符串都是一等公民。而在 JavaScript 中,函数也具有这些特性,因此我们称函数为一等公民。而把函数作为一等公民的编程语言,可以实现函数式编程
。
什么是函数式编程?
函数式编程是一种抽象程度很高的编程范式,函数式编程的特点主要有:
- 函数是一等公民
- 数据是不可变的(Immutable)
- 所有的函数都是纯函数
- 函数支持递归调用
- 函数只接受一个参数
很显然,纯粹的函数式编程是理想化的,几乎是没办法应用到生产中的,但是,函数式编程的思想并非没有益处。
函数式编程有什么优势
函数式编程可以实现函数闭包和高阶函数,也可以实现装饰器和柯里化等,对于代码的抽象和复用具有天然良好的支持。关于这些,我会在后续的博客中详细阐释。
此外,使用函数式编程思想编写纯函数,可以让代码运行更加稳定可控,也更加容易维护。
纯函数与副作用
纯函数的概念
纯粹的函数式编程语言编写的函数不应当依赖任何外部变量,对于这种函数,只要输入是确定的,输出就是确定的,外部任何变量的改变都不会对输出产生任何影响,我们称之为纯函数。
纯函数是没有副作用的函数。而允许使用变量的程序设计语言,由于函数内部的变量状态不确定,同样的输入,可能得到不同的输出,因此,这种函数是有副作用的。
副作用的概念
所谓副作用,就是我们在执行函数时,不得不做的一些事情。比如在前端提交表单之后需要刷新列表;在后端接受请求之后需要写入文件等。副作用并没有一个严格的定义,简单来说,只要是在函数内部与外部环境发生交互的,或是对外界变量进行了修改的,都属于副作用。例如如下代码:
|
|
这个函数中,storage.save
方法就属于副作用,因为它调用了函数外部的方法。对add
方法而言,storage.save
并不是它本身应该做的事情,而是它完成了本职工作之后,不得不做的事情,在这里就是将list
存入localStorage
。如果还有其他方法,如下:
|
|
可以看出,这些对list
进行更改的方法最终都需要调用storage.save
方法,这个storage.save
方法就是一个典型的副作用,因为我们事实上只需要在list
发生改变的时候去执行它而已。
在React
中,我们使用useEffect
去执行副作用:
|
|
在Vue3
中也大同小异:
|
|
但是,我们并不是要规避副作用,如果函数没有副作用,那么函数相当于完全没有与外界交互,那么运行函数也就没有任何意义,我们要消除的是那些不应该出现的副作用,并且将可控的、难以避免的副作用从主逻辑中抽离出来,使他们易于管理和维护。
函数式编程思想在 React 中的体现
只接受一个参数
事实上, React 在函数式组件出现之前,并没有体现太多的函数式编程思想,而反倒是其周边的生态库很多的都使用了函数式编程,比如Redux
。使用过Redux
的小伙伴应该还有印象,Redux
的Actions
是这么写的:
|
|
套了两层箭头函数,事实上, Redux 的中间件也都是这种形式:
|
|
或者写成这样:
|
|
这里就和函数式编程只接受一个参数
的特点相对应了,而由于闭包,最内层的函数仍然可以获取到外层的所有传参,所以内部的代码实现和我们平时写函数没有任何区别,这种技术也被称为函数柯里化(Currying)
,关于柯里化,我会在下一篇文章中详细阐述。
纯函数与副作用
而对于 React 来说,纯函数
和副作用
的概念则显得尤为重要。
而在 React 中,组件的render
函数应该是一个纯函数
,只有这样,组件渲染的结果才只由state
和props
决定。而函数组件在最初设计的时候也是一个纯函数
,在Hooks
出现之后才引入了副作用函数
,所以在编写 React 组件的时候,应当尽可能写成纯函数
,而对于副作用函数,则使用useEffect
进行统一执行。
而在 Redux 中,reducer
必须是一个纯函数,也是函数式编程的要求。
数据是不可变的
如果没有使用过 React 和 Redux ,对这一点的理解可能会有些困扰。在非函数式编程的思想中,通常都会使用变量,而在函数式编程思想中,只存在常量,如果想要对数据进行修改,就必须创建一个新的数据,这一点在 Redux 中体现得非常彻底——我们在写reducer
的时候,对state
进行修改时,必须写成这样的形式:
|
|
每一次调用setBaseInfo
时,都会返回一个新的state
,这也是为什么在进行 React 开发的时候,我们会大量使用...
拓展运算符的原因。你在 React 组件中使用setState
时,也常常会写成这个形式:
|
|
也是遵循了函数式编程的思想。