前言
在日常开发中,对于封装方法并不陌生,无论是命令式编程还是面向对象编程,亦或是函数式编程,对于一个常用到的方法,我们都会将其封装起来作为公共方法使用,那么这么多封装方法中,到底什么是纯函数呢?
概念
纯函数必须同时满足两个特征:
- 在执行过程中没有语义上可观察的副作用
- 对于相同的输入,总是会得到相同的输出
副作用
单说“函数副作用”,可能会有点抽象,但我相信大家更相信”药物的副作用“:我们为了治疗感冒而服用某种药物,药物在缓解感冒的症状之余,可能会导致发烧。那么”引起发烧“就是这个 药物的副作用
因为当我们服下药物时,它的作用范围不仅仅只有造成感冒的部分,而是全身上下系统
原本我们只想它伸出一只手来解决感冒问题,但它却伸出了两只手,一只手帮你解决问题,另一只手创造更多的问题
所以,函数的副作用就是:
在计算机科学中,函数副作用指当调用函数时,除了返回可能的函数值之外,还对主调用函数产生附加的影响。 ——维基百科 简而言之:对函数来说,它的正常工作任务就是“计算”,而它不应该做计算以外的事,如果一个函数除了计算以外,还对它的执行上下文、执行宿主等外部环境造成了一些其它的影响,那么这些影响就是所谓的“副作用”
举个🌰
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()向控制台发出了打印,此为纵向操作,使函数的流向发生了改变,进而导致函数不纯
总结
纯函数必须同时满足两个特征:
- 在执行过程中没有语义上可观察的副作用
- 对于相同的输入,总是会得到相同的输出