什么是纯函数,它有什么副作用?

175 阅读5分钟

前言

在日常开发中,对于封装方法并不陌生,无论是命令式编程还是面向对象编程,亦或是函数式编程,对于一个常用到的方法,我们都会将其封装起来作为公共方法使用,那么这么多封装方法中,到底什么是纯函数呢?

概念

纯函数必须同时满足两个特征:

  • 在执行过程中没有语义上可观察的副作用
  • 对于相同的输入,总是会得到相同的输出

副作用

​ 单说“函数副作用”,可能会有点抽象,但我相信大家更相信”药物的副作用“:我们为了治疗感冒而服用某种药物,药物在缓解感冒的症状之余,可能会导致发烧。那么”引起发烧“就是这个 药物的副作用

​ 因为当我们服下药物时,它的作用范围不仅仅只有造成感冒的部分,而是全身上下系统

​ 原本我们只想它伸出一只手来解决感冒问题,但它却伸出了两只手,一只手帮你解决问题,另一只手创造更多的问题

​ 所以,函数的副作用就是

在计算机科学中,函数副作用指当调用函数时,除了返回可能的函数值之外,还对主调用函数产生附加的影响。
                                                                          ——维基百科

​ 简而言之:对函数来说,它的正常工作任务就是“计算”,而它不应该做计算以外的事,如果一个函数除了计算以外,还对它的执行上下文、执行宿主等外部环境造成了一些其它的影响,那么这些影响就是所谓的“副作用”

举个🌰

Case 1

加法函数:

let a = 5;
let b = 10;
function add(){
	return a + b
}

这是不是一个纯函数?

显而易见,并不是,因为对于相同的输入,它无法做到相同的输出

因为 “输入” 指的是函数的入参,而add函数的入参是 void

稍作修改将其转化成 纯函数,方法很简单 只需要把“数据的输入”这件事情完全交给入参来做就可以了

let a = 5;
let b =10;
function add(a,b){
 return a + b
}
add(a,b) // 15

改造后add函数就能满足纯函数的两个条件了:

  • 对于相同的输入,总会得到相同的输出:对于相同的a,b,它们的和总是相等的
  • 在执行过程中没有语义上可观察的副作用:add函数除了做加法计算之外没有做任何事,不会对外部环境造成影响

Case 2

处理函数:

function process(age,name){
    const result = `I am ${name},I am ${age} years old this year`
    console.log(result)
    return result
}
process(18,amy)

这个函数也不是一个纯函数,因为其在执行过程中产生了副作用,要变成纯函数也很简单

function process(age,name){
    const result = `I am ${name},I am ${age} years old this year`
    return result
}
process(18,amy)

将 打印信息去掉就行

Case 3

网络请求:

function requestURL(url){
 	const res = fetch(url);
 	const {data} = res;
 	return data
}

这也不是一个纯函数,因为它引入了网络请求一个引入了网络请求的函数,从原则来说是纯不起来的

为什么呢:

  • 请求获取到的 res 是动态的:需要通过网络请求获取的数据往往是动态的,对于相同的输入,服务端未必能够给到相同的输出
  • 请求可能出错:既然是网络请求,那么它在请求的过程中就会有一定概率失败。例如 网络拥塞、后端删库跑路等问题都可能导致请求过程中 的 err ,而未经捕获的Error 本身就是一种副作用,而使用 “post”,“delete”等方法对数据进行“写”的时候,此时网络请求已经对外部数据造成了影响,更为不纯

“纯”的本质 - 显式数据流

通过上面 修改后的Case1 可以很好的观察到,当我们的数据以入参的形式传入(显示输入数据流),数据以返回值形式数据(显示输出数据),此时的输入输出数据流都是显式的,意思就是数据只能以入参的形式进来,并且只能以返回值的形式出去

显示数据流意味着函数除了入参和返回值之外,不以任何其他形式与外界进行数据交换

“不纯”的元凶 - 隐式数据流

通过上面 修改前的Case1 可以看出它的不纯就是由于隐式的数据输入导致的,a ,b 没有通过参数的形式进入函数,而是让函数从全局变量中抓取a,b变量,这就是典型的隐式数据转换

一个纯函数在执行过程中应该只有横向数据流,而不应该有纵向数据流:

如Case 2,从入参到 return返回为横向,但是其中通过console.log()向控制台发出了打印,此为纵向操作,使函数的流向发生了改变,进而导致函数不纯

总结

纯函数必须同时满足两个特征:

  • 在执行过程中没有语义上可观察的副作用
  • 对于相同的输入,总是会得到相同的输出