「本文已参与好文召集令活动,点击查看:后端、大前端双赛道投稿,2万元奖池等你挑战!」
关于 函数式编程 整理的一些笔记,都统一记录在👉 函数式编程专栏
graph LR
纯函数--> A(纯函数的概念)
纯函数--> B(如何理解'无可观察的副作用')
纯函数--> C(纯函数的好处)
纯函数的概念
在程序设计中,如果一个函数符合以下条件,则被称之为纯函数:
- 相同的输入,产生相同的输出
- 无可观察的副作用
- 不依赖于外部状态
例1 - 不纯的函数
let a = 1;
function sum(b){
return a+b
}
console.log(sum(2)) // 3
例1中,违反了规则1和规则3,相同的输入没有产生相同的输出,变量a的变动随时都会影响到内部的计算结果。
例2 - 纯函数
function sum(a,b){
return a+b
}
console.log(sum(1,2)) // 3
例2是个纯函数,相同的输入有相同的输出,也没有依赖于外部变量,更没有可观察到的副作用。
条件1 和 条件3 很好理解,关于纯函数的第2个条件-"无可观察的副作用"如何理解呢?
如何理解"无可观察的副作用"
什么是副作用
- “作用”是一切除结果计算之外发生的事情,它本身并没有坏处。往往 “一只蚂蚁搅乱一锅粥”,正是因为“副”作用中“副”的存在,导致衍生出奇奇怪怪的问题。
- 总结:副作用是在计算结果的过程中,系统状态的一种变化,或者与外部世界进行的可观察的交互。
副作用来源
只要是跟函数外部环境发生交互的都是副作用
- 更改文件系统
- 往数据库插入记录
- 发送一个http请求
- 可变数据
- 打印/Log
- 获取用户输入
- DOM查询
- 访问系统状态
所有的外部交互都有可能带来副作用,副作用也使得方法通用性下降不适合扩展和可重用性,同时副作用会给程序中带来安全隐患给程序带来不确定性,但是副作用不可能完全禁止,我们不能禁止用户输入用户名和密码,只能尽可能控制它们在可控范围内发生。
纯函数的好处
可缓存
因为对于相同的输入始终有相同的结果,那么可以把纯函数的结果缓存起来,可以提高性能。
举个🌰:计算一个数值绝对值的纯函数
// 计算一个数的绝对值
function getABS(x) {
console.log(x);
return Math.abs(x)
}
console.log(getABS(-6)); // 6
console.log(getABS(-6)); // 6
console.log(getABS(-6)); // 6
在上面的例子中,我们看到最终的打印结果是:三个6。
结果没有缓存处理,所以getABS()
函数内部会打印三次x
:
-6
-6
-6
接下来,我们写一个记忆函数(模拟Lodash
中的memoize)
这里做了简化版,创建一个会缓存 func 结果的函数, 如果缓存中有值就把值返回, 没有值就调用func函数并且把参数传递给它
// 一个简易版本的记忆函数
function memoize (func) {
if (typeof func != 'function') {
throw new TypeError('Expected a function');
}
var memoized = function() {
var args = arguments,
key = args[0],
cache = memoized.cache;
// 如果缓存中有值就把值返回
if (cache.has(key)) {
return cache.get(key);
}
// 没有值就调用func函数并且把参数传递给它
var result = func.apply(this, args);
memoized.cache = cache.set(key, result) || cache;
return result;
};
memoized.cache = new Map()
return memoized;
}
const m = memoize(getABS)
console.log(m(-6)); // 6
console.log(m(-6)); // 6
console.log(m(-6)); // 6
最终的打印结果,和上面(没缓存过)的一样。
区别在于,函数内部经过缓存,只会打印一次 x
:
-6
可测试
- 纯函数让测试更加容易。
- 不需要伪造一个“真实的”支付网关,或者每一次测试之前都要配置、之后都要断言状态(assert the state)。只需简单地给函数一个输入,然后断言输出就好了。
并行处理
- 多线程环境下并行操作共享的内存数据很可能会出现意外情况。纯函数不需要访问共享的内存数据,所以在并行环境下可以任意运行纯函数
- 虽然JS是单线程,但是ES6以后有一个Web Worker,可以开启一个新线程