纯函数:相同输入必出相同输出?这才是代码界的 “老实人”

如果代码界有 "性格测试",纯函数绝对是公认的 "老实人"------ 你给它什么输入,它就一定返回什么结果,从不偷偷摸摸干别的事。而不纯的函数呢?可能表面上返回结果,背地里却偷偷改了你的全局变量、发了个请求,甚至偷偷修改了 DOM。今天我们就来聊聊纯函数这个 "老实人" 的特性、优势,以及在实际开发中如何用好它。

什么是纯函数?用两个例子说清楚

纯函数的定义很简单,满足两个条件即可:

  1. 相同的输入,必然得到相同的输出
  2. 没有副作用(不修改函数外部的状态,不影响函数外的世界)。

(1)纯函数:像数学公式一样靠谱

javascript 复制代码
// 纯函数:add(a, b)
function add(a, b) {
  return a + b;
}

// 无论调用多少次,只要输入相同,结果就一定相同
console.log(add(2, 3)); // 永远是5
console.log(add(2, 3)); // 还是5

这个函数就像数学里的 "加法公式"------ 输入23,输出必然是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)、管理外部状态等 "有副作用" 的逻辑。
相关推荐
薛定谔的算法1 分钟前
# 从0到1构建React项目:一个仓库展示应用的架构实践
前端·react.js
Tina学编程26 分钟前
HTML基础P1 | HTML基本元素
服务器·前端·html
一只小风华~2 小时前
Web前端:JavaScript和CSS实现的基础登录验证功能
前端
90后的晨仔2 小时前
Vue Router 入门指南:从零开始实现前端路由管理
前端·vue.js
LotteChar2 小时前
WebStorm vs VSCode:前端圈的「豆腐脑甜咸之争」
前端·vscode·webstorm
90后的晨仔2 小时前
零基础快速搭建 Vue 3 开发环境(附官方推荐方法)
前端·vue.js
洛_尘2 小时前
Java EE进阶2:前端 HTML+CSS+JavaScript
java·前端·java-ee
孤独的根号_2 小时前
Vite背后的技术原理🚀:为什么选择Vite作为你的前端构建工具💥
前端·vue.js·vite
吹牛不交税3 小时前
Axure RP Extension for Chrome插件安装使用
前端·chrome·axure
薛定谔的算法3 小时前
# 前端路由进化史:从白屏到丝滑体验的技术突围
前端·react.js·前端框架