函数式编程三要素

本文会分为三大块来记录开发中函数响应式编程的作用, 及其各种形态。

在看响应式之前,先看下函数式。

函数式编程

开头见山,函式编程三要素:

  • 声明式(Declarative)
  • 纯函数(Pure Function)
  • 数据不可变性(Immutability)

JaveScript从语言角度来讲,不能算纯粹的函数式编程语言,但是JavaScript中函数有第⼀公民的⾝份,因为函数本⾝就是⼀个对象,可以被赋值给⼀个变量,可以作为参数传递,由此可以很⽅便地应⽤函数式编程的许多思想。(取自《深入浅出Rxjs》)

声明式

与声明式相对的编程方式是命令式编程(Imperative Programming),命令式编程也是最常见的⼀种编程⽅式。

先看一个命令式编程的例子,我们有一个新需求,将⼀个数组中每个元素的数值乘以2。这样⼀个功能可以实现为⼀个叫double的函数,代码如下:

1
2
3
4
5
6
7
function double(arr) {
const results = [];
for (let i = 0; i < arr.length; i++) {
results.push(arr[i] * 2);
}
return results;
}

第二天需求增加,要求把数组每个元素加1,小case对吧,代码如下:

1
2
3
4
5
6
7
function addOne(arr) {
const results = [];
for (let i = 0; i < arr.length; i++) {
results.push(arr[i] + 1);
}
return results;
}

除了第四行 push 函数入参计算逻辑有出入以外,其他代码如出一辙。你可能总感觉哪里不舒服,没错重复代码的出现,造就了屎山的第一步,因为,“重复代码可能是所有软件中邪恶的根源[1]。
这是命令式编程的⼀个⼤问题,我们可以利用 jsmap 函数,来重新实现 doubleaddOne 函数,代码如下:

1
2
3
4
5
6
7
8
9
10
11
function double(arr) {
return arr.map(function(item) {
return item * 2
});
}

function addOne(arr) {
return arr.map(function(item) {
return item + 1
})
}

代码减少了 8 行,这就是声明式编程的好处,只需要知道 map 函数的作用,就能使这段代码更容易阅读和维护。
很显然在不考虑效率问题, map 的声明式调用在这种场景下更优秀。

在上面的例子中,我们实现的double和addOne函数,表面上看似把传入的每个元素乘以2(或者加1),实际并没有真的去修改作为参数的数组,而是产生了一个新的数组,这就涉及函数式编程的另⼀个重要特性:纯函数

纯函数

所谓纯函数,指的是满⾜下⾯两个条件的函数。

  • 函数的执⾏过程完全由输⼊参数决定,不会受除参数之外的任何数据影响。
  • 函数不会修改任何外部状态,⽐如修改全局变量或传⼊的参数对象。

与纯函数相对的有“不纯函数”(Impure Function), 一个函数之所以不纯,可能做了下面这些事:

  • 改变全局变量
  • 改变输入参数引用的对象,比如 Object.assign 函数有两个参数的情况下
  • 读取用户输入,比如调用alert或者comfirm函数。
  • 抛出一个异常。
  • 网络输入/输出操作,比如通过AJAX调用一个服务器API
  • 操作浏览器DOM

这还只是一部分表现,其实有个简单的方法来判断纯不纯,就是假设将⼀个函数调⽤替换为⼀个预期返回的常数,程序运⾏结果是否⼀样。

例如下面arrayPush 函数的实现,与预期结果的直接赋值,很明显不一样。

1
2
3
4
5
6
let arr = [1,2,3];
function arrayPush(arr, newValue){
return arr.push(newValue), arr;
}
let newArr = arrayPush(arr, 4);
let newArr = [1,2,3,4];

满⾜纯函数的特性也叫做引⽤透明度(ReferentialTransparency),这是更加正式的说法。怎么称呼不重要,重要的是开发者要理解,所谓的纯函数,做的事情就是输⼊参数到返回结果的⼀个映射,不要产⽣副作⽤(SideEffect)。
这时 arrayPush 函数影响了外部 arr 的值,因此 arrayPush 产生了副作用,也可以称做 Effect 函数。

数据不可变

数据不可变(Immutable)是函数式编程⾮常重要的⼀个概念,从字面意思来说,可能会理解为函数返回的数据是不可变的,这么理解不全对。这里的数据不可变指的是原有数据不可变,而产生一个新的数据。
JavaScript并非纯粹的函数式编程预言,比如数组的基础函数pushpopsort都会改变数组内容,由此引发的bug可不少。
值得一提的是,在Javascript中const关键字虽然有常数(constant)的意思,但其实只是规定⼀个变量引⽤的对象不能改变,却没有规定这个const变量引⽤的对象⾃⾝不能发⽣改变,所以,这个“常量”依然是变量。

以上就是函数式编程的几个要点。

$The\,End$