第七章 纯度、不变性和更改策略

本章要探索完全函数式以及实用风格。

函数式编程不仅只关心函数,也是思考尽量降低软件复杂性的一种方式。

7.1 纯度

说道纯度,我们首先要介绍一个函数式编程中的概念 —— 纯函数。

纯函数是指 不依赖于且不改变它作用域之外的变量状态 的函数。

也就是说,纯函数的返回值只由调用它时的参数所决定的 。

纯函数最大的好处就是无副作用,如果不出意外,这应该不是第一次你看见它了吧,什么是副作用呢?我们来举一个不太好的例子,但你能理解就好。

const obj = {a: 1};

function unpure(obj) {
  return obj.a;
}

上面的函数就是不纯的,假如我将 obj 中的 a 改变,那么该函数的结果就会改变,也就是说,该函数依赖了别的变量。

那么什么是纯的呢?

function pure(value) {
  return value;
}

上面的函数就是纯函数,它的输出结果只由输入的值来判定,调用时,我们可以这样:

pure(obj.a);  // 1
obj.a = 2;
pure(obj.a);  // 2

也就是说,纯函数什么也不关心,它仅仅关心传入的值是什么。

再举一个例子,我们做一个最简单的加法:

function add(x, y) {
  return x + y;
}

我们做一个测试:

add('1', 2);

没错,会输出 12 。这是我们想要的结果吗?显然并不是,那么我们可以说,这个函数是有副作用的,因为它没有任何的提示,输出了一个错误的值(当然,也不是错误,对于十进制来说是错误的)。接着我们简单地修改一下:

function addTwo(x, y) {
  x = +x;
  y = +y;

  return x + y;
}

addTwo('1', 2);  // 3

此时,就会输出正确的结果了。

这里再穿插一个概念叫做 —— 幂等。

所谓幂等,就是指,该函数执行任意多次,都只会产生同一个结果。

就好比 1 + 1 = 2,它不会等于 3 一样。

这也算是检验纯函数的一个方法。

7.2 不变性

在 JavaScript 中,我们知道,很少有默认不可变的数据类型。

即便是字符串,你可以认为它是可变的:

let str = 'hello';
str = 'world';  // world

但是这种可变性是函数式编程中最大的敌人,因为它不可控,假使一个函数是可变的,那么使用它的函数将会变成可变的函数,这样,你的程序可能不会走的很远。

我们需要用纯函数组成另一个函数,那么另一个函数也就自然是纯函数了。

还记得我们前面说的函数组合的概念吗?就是那个寻找数组中最大的数或最小的数。我们使用函数分离的策略,将耦合在一起的函数分离为若干个纯函数,再将它们组合在一起,这样,就会减少突变。

比如在一个对象中:

const obj = {
  a: 1,
};

Object.freeze(obj);

obj.a = 2;
console.log(obj);  // {a: 1}

我们使用了 Object.freeze() 函数来将 obj 冻结,这样,它就不可被更改了。

当然,这只是举一个例子,具体是否使用它需要看情况。

7.3 更改策略

一般来说,我们需要面对现实。虽然控制函数是十分完美的,但是偶尔需要遇到状态改变的情况,那么,就会导致相关联的函数都发生改变,这是我们不想看到的。

因此,我们需要找到一个折中的点。

那就是,当我们需要更改的时候,我们更改的是容器,而不是本身。

比如,我么现在拥有一个对象:

const obj = {
  a: 1
};
obj.a = 2;
obj;  // {a: 2}

显而易见的,它会被改变,也就是说,原对象被改变了。那么此后所发生的的一切应该怎么办?依赖于此对象构建的函数全部会发生改变!

因此,当我们需要作出改变的时候,应该改变中间层,而不是直接改变原对象。

function deepClone(obj) {
  return JSON.parse(JSON.stringify(obj));
}

const obj1 = deepClone(obj);
obj1.a = 2;

console.log(obj);  // {a: 1}
console.log(obj1);  // {a: 2}

此时,我们可以用我们深复制的 obj1 去做我们想要做的任何事,而依赖于原对象 obj 的函数将不会有任何改变。

这不仅是函数式编程,更多的是一种思想,当你要改变的时候,不要直接改变原本的对象或函数,而是间接地去改变,这样才不会造成更大的混乱。

总结

本节的内容不多,但是都很重要。

如何构建和分离纯函数,并且将其组合成更大的函数,这个是我们必须要学会的,当你都不知道你的程序会产生什么结果的时候,就是混乱的开始。

推荐阅读更多精彩内容