浅析JS标识纯函数的作用及纯函数理解和为什么要构建纯函数

  PS:vue3 的源码里大量使用了纯函数注释,那么我们就来了解下:使用 /*#__PURE__*/ 可标注纯函数,那么纯函数是什么,有什么作用?

一、什么是纯函数

1、纯函数(pure function)

(1)返回结果只依赖它的参数,传入的参数一样,返回的结果也是一样的

(2)执行时不改变作用域外的数据

(3)不产生副作用

2、优点:

(1)代码阅读性提高

(2)提高了代码的复用性

(3)IO简单,测试简单

(4)可作缓存或记忆功能

3、了解下什么是可观察的副作用?

  一个可以被观察的副作用指的是在函数内部与其外部的任意交互。这个交互可能是在函数内修改外部的变量,或者在函数里调用另外一个函数等。

注: 如果纯函数调用纯函数,则不产生副作用,依旧是纯函数。

  副作用来自,但不限于:

  • 进行一个 HTTP 请求
  • 输出数据到屏幕或者控制台(如 canvas 或 console.log )
  • DOM 查询/操作
  • Math.random()
  • 获取的当前时间

  副作用本身并不是毒药,某些时候往往是必需的。 但是,就纯函数而言是不能包含任何副作用的。当然,并非所有函数都必须是纯函数。

二、纯函数应用

  纯函数是函数式编程里面非常重要的概念。简单来说,一个函数的返回结果只依赖于它的参数,并且在执行过程里面没有副作用,我们就把这个函数叫做纯函数。这么说肯定比较抽象,我们把它掰开来看:

1、函数的返回结果只依赖于它的参数

const a = 1
const foo = (b) => a + b
foo(2) // => 3

  foo 函数不是一个纯函数,因为它返回的结果依赖于外部变量 a,我们在不知道 a 的值的情况下,并不能保证 foo(2) 的返回值是 3。

  虽然 foo 函数的代码实现并没有变化,传入的参数也没有变化,但它的返回值却是不可预料的,现在 foo(2) 是 3,可能过了一会就是 4 了,因为 a 可能发生了变化变成了 2。

const a = 1
const foo = (x, b) => x + b
foo(1, 2) // => 3

  现在 foo 的返回结果只依赖于它的参数 x 和 bfoo(1, 2) 永远是 3。今天是 3,明天也是 3,在服务器跑是 3,在客户端跑也 3,不管你外部发生了什么变化,foo(1, 2) 永远是 3。只要 foo 代码不改变,你传入的参数是确定的,那么 foo(1, 2) 的值永远是可预料的。

  这就是纯函数的第一个条件:一个函数的返回结果只依赖于它的参数。

2、函数执行过程没有副作用

  一个函数执行过程对产生了外部可观察的变化,那么就说这个函数是有副作用的。我们修改一下 foo

const a = 1
const foo = (obj, b) => {
  return obj.x + b
}
const counter = { x: 1 }
foo(counter, 2) // => 3
counter.x // => 1

  我们把原来的 x 换成了 obj,我现在可以往里面传一个对象进行计算,计算的过程里面并不会对传入的对象进行修改,计算前后的 counter 不会发生任何变化,计算前是 1,计算后也是 1,它现在是纯的。但是我再稍微修改一下它:

const a = 1
const foo = (obj, b) => {
  obj.x = 2
  return obj.x + b
}
const counter = { x: 1 }
foo(counter, 2) // => 4
counter.x // => 2

  现在情况发生了变化,我在 foo 内部加了一句 obj.x = 2,计算前 counter.x 是 1,但是计算以后 counter.x 是 2。foo 函数的执行对外部的 counter 产生了影响,它产生了副作用,因为它修改了外部传进来的对象,现在它是不纯的。

  但是你在函数内部构建的变量,然后进行数据的修改不是副作用:

const foo = (b) => {
  const obj = { x: 1 }
  obj.x = 2
  return obj.x + b
}

  虽然 foo 函数内部修改了 obj,但是 obj 是内部变量,外部程序根本观察不到,修改 obj 并不会产生外部可观察的变化,这个函数是没有副作用的,因此它是一个纯函数。

  除了修改外部的变量,一个函数在执行过程中还有很多方式产生外部可观察的变化,比如说调用 DOM API 修改页面,或者你发送了 Ajax 请求,还有调用 window.reload 刷新浏览器,甚至是 console.log 往控制台打印数据也是副作用。

  纯函数很严格,也就是说你几乎除了计算数据以外什么都不能干,计算的时候还不能依赖除了函数参数以外的数据

3、总结:一个函数的返回结果只依赖于它的参数,并且在执行过程里面没有副作用,我们就把这个函数叫做纯函数。

三、为什么要煞费苦心地构建纯函数?

  因为纯函数非常“靠谱”,执行一个纯函数你不用担心它会干什么坏事,它不会产生不可预料的行为,也不会对外部产生影响。不管何时何地,你给它什么它就会乖乖地吐出什么。如果你的应用程序大多数函数都是由纯函数组成,那么你的程序测试、调试起来会非常方便。

  最后一个问题来了,那么我可以将所有函数都设为纯函数吗?

  是的,从技术上讲,你可以。 但是只有纯函数的应用程序可能做不了多少。 您的应用程序会产生副作用,例如 HTTP 调用、IO 操作等等。 请在尽可能多的地方使用纯函数。 尽可能隔离不纯函数(副作用)。 它将大大提高程序的可读性、可调试性和可测试。

  纯函数在函数式编程中被大量使用,而且诸如 ReactJS 和 Redux 等优质的库都要求使用纯函数。当然,纯函数也可以用在平常的JS开发中使用,不一定要限死在某个编程范式中。你可以自由地混合纯的和非纯的函数,这完全没问题。

  事实上,并非所有函数都一定要是纯函数。 例如,操作 DOM 的按钮按下的事件处理程序就不适合纯函数。 不过,这种事件处理函数可以调用其他纯函数来处理,以此减少项目中不纯函数的数量。

  另一个使用纯函数的原因是测试以及重构。

  使用纯函数的一个主要好处是它们可以直接测。 如果传入相同的参数,它们将始终产生相同的结果,这对于测试的编写是非常友好的。

  同时纯函数还使得维护和重构代码变得更加容易。你可以放心地重构一个纯函数,不必操心没注意到的副作用搞乱了整个应用而导致终调试地狱。

  如果项目中充斥着副作用,那么函数/模块之间的逻辑可能互相交织耦合,在后期新增逻辑时可能由于依赖复杂而难以重构,更常见的是开发为了应付需求而不断的引入新的副作用到原本的逻辑上从而导致代码变得越来越糟糕。

  正确地使用纯函数可以产生更加高质量的代码,并且也是一种更加干净的编码方式。

posted @ 2022-05-12 22:04  古兰精  阅读(255)  评论(0编辑  收藏  举报