如果代码界有 "性格测试",纯函数绝对是公认的 "老实人"------ 你给它什么输入,它就一定返回什么结果,从不偷偷摸摸干别的事。而不纯的函数呢?可能表面上返回结果,背地里却偷偷改了你的全局变量、发了个请求,甚至偷偷修改了 DOM。今天我们就来聊聊纯函数这个 "老实人" 的特性、优势,以及在实际开发中如何用好它。
什么是纯函数?用两个例子说清楚
纯函数的定义很简单,满足两个条件即可:
- 相同的输入,必然得到相同的输出;
- 没有副作用(不修改函数外部的状态,不影响函数外的世界)。
(1)纯函数:像数学公式一样靠谱
javascript
// 纯函数:add(a, b)
function add(a, b) {
return a + b;
}
// 无论调用多少次,只要输入相同,结果就一定相同
console.log(add(2, 3)); // 永远是5
console.log(add(2, 3)); // 还是5
这个函数就像数学里的 "加法公式"------ 输入2
和3
,输出必然是5
,而且它不会对函数外部的任何变量、状态产生影响。
(2)非纯函数:偷偷摸摸干 "坏事"
javascript
// 非纯函数:addToTotal(a)
let total = 0; // 外部变量
function addToTotal(a) {
total += a; // 修改了外部变量(副作用)
return total;
}
// 相同的输入,输出可能不同!
console.log(addToTotal(2)); // 输出2(此时total=2)
console.log(addToTotal(2)); // 输出4(此时total=4)
这个函数就 "不老实" 了:
- 它修改了外部的
total
变量(这就是 "副作用"); - 同样输入
2
,第一次返回2
,第二次返回4
,违反了 "相同输入必同输出" 的规则。

"副作用" 到底是什么?纯函数的红线
纯函数的核心禁忌是 "副作用",它指的是函数对外部环境产生的任何影响,常见的副作用包括:
- 修改全局变量、外部对象的属性;
- 发送网络请求(
fetch
/axios
); - 操作 DOM(如
document.getElementById
修改内容); - 调用
Math.random()
、Date.now()
等 "不确定性" 函数(输入相同,输出可能不同)。
举个例子,下面这些都是非纯函数:
javascript
// 1. 修改外部对象
const obj = { name: '张三' };
function changeName(newName) {
obj.name = newName; // 副作用:修改了外部对象
return obj.name;
}
// 2. 发送网络请求
function fetchUser(id) {
return fetch(`/api/user/${id}`); // 副作用:发请求
}
// 3. 依赖随机数
function getRandom() {
return Math.random(); // 相同输入(无输入),输出不同
}
为什么要在意纯函数?它的 3 个核心优势
纯函数看似 "限制多",但在实际开发中却能带来巨大好处,尤其是在大型项目中。
(1)可预测性:代码行为完全可控
纯函数的 "相同输入必同输出" 特性,让代码的行为变得可预测。比如调用add(2, 3)
,你不用看函数内部实现,就知道返回5
;而调用非纯函数addToTotal(2)
,你必须跟踪total
变量的变化才能知道结果。
这种可预测性让调试变得简单 ------ 如果结果不符合预期,只需检查输入和函数内部逻辑,不用怀疑外部因素。
(2)可缓存性:重复计算可以 "偷懒"
由于纯函数的输出只依赖输入,对于相同的输入,可以缓存结果,避免重复计算。这在处理复杂计算时能大幅提升性能。
javascript
// 纯函数:计算平方
function square(n) {
console.log('计算平方...');
return n * n;
}
// 缓存函数:缓存纯函数的结果
function memoize(fn) {
const cache = {};
return function (arg) {
if (cache[arg] !== undefined) {
console.log('从缓存获取');
return cache[arg];
}
const result = fn(arg);
cache[arg] = result;
return result;
};
}
// 包装纯函数
const memoizedSquare = memoize(square);
memoizedSquare(3); // 输出"计算平方...",返回9
memoizedSquare(3); // 输出"从缓存获取",返回9(无需重复计算)
(3)可测试性:单元测试变得简单
测试纯函数时,不需要搭建复杂的环境(如模拟 DOM、网络请求),只需直接断言 "输入→输出" 是否符合预期。
javascript
// 测试纯函数add
test('add(2, 3) should return 5', () => {
expect(add(2, 3)).toBe(5); // 简单直接,无需准备环境
});
// 测试非纯函数addToTotal(麻烦)
test('addToTotal(2) should return 2 first', () => {
total = 0; // 必须先重置外部变量
expect(addToTotal(2)).toBe(2);
});
误区:纯函数不是 "银弹"
纯函数虽好,但不能解决所有问题。实际开发中,我们需要 "纯函数" 和 "非纯函数" 配合:
- 纯函数:负责处理数据计算、状态转换等 "无副作用" 的逻辑;
- 非纯函数:负责处理 IO 操作(发请求、操作 DOM)、管理外部状态等 "有副作用" 的逻辑。