函数式编程三要素
本文会分为三大块来记录开发中函数响应式编程的作用, 及其各种形态。
在看响应式之前,先看下函数式。
函数式编程
开头见山,函式编程三要素:
- 声明式(Declarative)
- 纯函数(Pure Function)
- 数据不可变性(Immutability)
JaveScript从语言角度来讲,不能算纯粹的函数式编程语言,但是JavaScript中函数有第⼀公民的⾝份,因为函数本⾝就是⼀个对象,可以被赋值给⼀个变量,可以作为参数传递,由此可以很⽅便地应⽤函数式编程的许多思想。(取自《深入浅出Rxjs》)
声明式
与声明式相对的编程方式是命令式编程(Imperative Programming),命令式编程也是最常见的⼀种编程⽅式。
先看一个命令式编程的例子,我们有一个新需求,将⼀个数组中每个元素的数值乘以2。这样⼀个功能可以实现为⼀个叫double的函数,代码如下:
1 | function double(arr) { |
第二天需求增加,要求把数组每个元素加1,小case对吧,代码如下:
1 | function addOne(arr) { |
除了第四行 push
函数入参计算逻辑有出入以外,其他代码如出一辙。你可能总感觉哪里不舒服,没错重复代码的出现,造就了屎山的第一步,因为,“重复代码可能是所有软件中邪恶的根源[1]。
这是命令式编程的⼀个⼤问题,我们可以利用 js
的 map
函数,来重新实现 double
和 addOne
函数,代码如下:
1 | function double(arr) { |
代码减少了 8 行,这就是声明式编程的好处,只需要知道 map
函数的作用,就能使这段代码更容易阅读和维护。
很显然在不考虑效率问题, map
的声明式调用在这种场景下更优秀。
在上面的例子中,我们实现的double和addOne函数,表面上看似把传入的每个元素乘以2(或者加1),实际并没有真的去修改作为参数的数组,而是产生了一个新的数组,这就涉及函数式编程的另⼀个重要特性:纯函数。
纯函数
所谓纯函数,指的是满⾜下⾯两个条件的函数。
- 函数的执⾏过程完全由输⼊参数决定,不会受除参数之外的任何数据影响。
- 函数不会修改任何外部状态,⽐如修改全局变量或传⼊的参数对象。
与纯函数相对的有“不纯函数”(Impure Function), 一个函数之所以不纯,可能做了下面这些事:
- 改变全局变量
- 改变输入参数引用的对象,比如
Object.assign
函数有两个参数的情况下 - 读取用户输入,比如调用
alert
或者comfirm
函数。 - 抛出一个异常。
- 网络输入/输出操作,比如通过
AJAX
调用一个服务器API
。 - 操作浏览器
DOM
。
这还只是一部分表现,其实有个简单的方法来判断纯不纯,就是假设将⼀个函数调⽤替换为⼀个预期返回的常数,程序运⾏结果是否⼀样。
例如下面arrayPush
函数的实现,与预期结果的直接赋值,很明显不一样。
1 | let arr = [1,2,3]; |
满⾜纯函数的特性也叫做引⽤透明度(ReferentialTransparency),这是更加正式的说法。怎么称呼不重要,重要的是开发者要理解,所谓的纯函数,做的事情就是输⼊参数到返回结果的⼀个映射,不要产⽣副作⽤(SideEffect)。
这时 arrayPush 函数影响了外部 arr
的值,因此 arrayPush 产生了副作用,也可以称做 Effect
函数。
数据不可变
数据不可变(Immutable
)是函数式编程⾮常重要的⼀个概念,从字面意思来说,可能会理解为函数返回的数据是不可变的,这么理解不全对。这里的数据不可变指的是原有数据不可变,而产生一个新的数据。
JavaScript并非纯粹的函数式编程预言,比如数组的基础函数push
、pop
、sort
都会改变数组内容,由此引发的bug可不少。
值得一提的是,在Javascript中const
关键字虽然有常数(constant
)的意思,但其实只是规定⼀个变量引⽤的对象不能改变,却没有规定这个const
变量引⽤的对象⾃⾝不能发⽣改变,所以,这个“常量”依然是变量。
以上就是函数式编程的几个要点。
$The\,End$