JavaScript 纯函数

505 阅读4分钟

纯函数是函数式编程的基本组成。因为其简单、可测试而被推崇。 本文提供了快速检查表来判断一个函数是否为纯函数。

检查表

函数必须通过两条检测才能被认为“纯”: 

1.相同的输入总返回相同的输出 

2.无副作用 

让我们扩展开来看看。

 1.相同输入=>相同输出

对比这段:

const add = (x, y) => x + y;

add(2, 4); // 6

和这段:

let x = 2;

const add = (y) => {
  x += y;
};

add(4); // x === 6 (the first time)

纯函数 = 一致的结果

第一个例子根据赋值参数返回一个值,不管你在何时何地调用它。

例如你传入2和4,你总会得到6。

没有其他东西影响该输出。

非纯函数 = 不一致性的结果

第二个例子无返回值。它依赖共享状态完成它的功能,通过在它作用域外部增加一个变量。

这种模式是开发者的噩梦。

共享状态带来时间依赖。你会获得不同的结果,依据你何时调用该方法。第一次你得的结果是6,下一次是10,以此类推。

哪个版本更容易推断呢?

哪个更不容易滋生那些只在特定条件下才发生的bug?

哪个会更容易在多线程环境下成功,该环境下时间依赖可能破坏系统?

显然是第一个。

2.无副作用

这个测试本身就是一份检查表,有几个有副作用的例子:

  1. 修改你的输入
  2. console.log
  3. HTTP请求(ajax/fetch)
  4. 修改文件系统(fs)
  5. 查询DOM

基本上一个函数做的任何与其计算最终输出无关的事情都算。

这是一个有副作用的非纯函数。

没那么糟糕的

const impureDouble = (x) => {
  console.log('doubling', x);

  return x * 2;
};

const result = impureDouble(4);
console.log({ result });

console.log 在这里是一个副作用,大部分情况下,它无害。在赋相同值时,我们也会得到一样的输出。然而,以下可能会有问题:

污染性的更改对象

const impureAssoc = (key, value, object) => {
  object[key] = value;
};

const person = {
  name: 'Bobo'
};

const result = impureAssoc('shoeSize', 400, person);

console.log({
  person,
  result
});

变量person,被永久性的更改掉了,因为我们的函数加了赋值声明。

共享状态意味着impureAssoc的作用不再完全明确。现在也理解他对系统的影响,就涉及跟踪他接触过的每一个变量,并知道他们的历史。

共享状态 = 时间依赖

我们可以通过简单的返回一个包含我们想要的属性的新对象来纯化impureAssoc。

纯化

const pureAssoc = (key, value, object) => ({
  ...object,
  [key]: value
});

const person = {
  name: 'Bobo'
};

const result = pureAssoc('shoeSize', 400, person);

console.log({
  person,
  result
});

目前 pureAssoc返回了可测试的结果,我们不再担心它悄悄的在其他地方修改了什么。

你也可以像下面这样,保持其纯净:

const pureAssoc = (key, value, object) => {
  const newObject = { ...object };

  newObject[key] = value;

  return newObject;
};

const person = {
  name: 'Bobo'
};

const result = pureAssoc('shoeSize', 400, person);

console.log({
  person,
  result
});

更改你的输入是危险的行为,然而修改它的拷贝却不会有问题。我们最终的结果仍然是可测试、可预测的函数。无论你何时何地调用它,它都会正常工作。

修改被限制在那很小的范围,而你也仍然会返回一个值。

对象深度拷贝

当心!使用扩展运算符...会创建对象的浅拷贝。在嵌套修改中浅拷贝并不安全。

不安全的嵌套修改

const person = {
  name: 'Bobo',
  address: { street: 'Main Street', number: 123 }
};

const shallowPersonClone = { ...person };
shallowPersonClone.address.number = 456;

console.log({ person, shallowPersonClone });

person 和 shallowPersonClone 都被修改掉了,因为他们的子元素共享了相同的引用。

安全的嵌套修改

我们需要深度拷贝来进行安全地修改嵌套属性。

const person = {
  name: 'Bobo',
  address: { street: 'Main Street', number: 123 }
};

const deepPersonClone = JSON.parse(JSON.stringify(person));
deepPersonClone.address.number = 456;

console.log({ person, deepPersonClone });

这下你保证安全了,因为他们实际上是两个实例。

总结

  • 如果一个函数没有副作用,当输入相同的值会输出相同的返回值那么它是纯函数。
  • 副作用包括:修改输入,HTTP请求,写磁盘,打印到显示屏。
  • 对于输入,你可以安全的拷贝,并修改它。只要别碰原始值。
  • 扩展运算语法(...syntax)是对象浅拷贝最简单的方式。
  • JSON.parse(JSON.stringify(object))是对象深拷贝最简单的方式。

关于本文

译者:Sandy Liang

译文:juejin.cn/post/684490…

作者:Yazeed Bzadough                             

原文:www.freecodecamp.org/news/what-i…