前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
首页程序员面试题库纯函数与副作用 特点
JavaScript函数与作用域

什么是副作用?纯函数有什么特点,为什么要用它?

副作用是函数除了返回值之外,对外面造成的任何影响,比如改了传进来的对象、写了全局变量、发了请求、操作了 DOM。纯函数就是没有副作用、并且同样的输入永远返回同样结果的函数。它好测试、能缓存,结果可以预测。

前端进阶之旅 · 一题精讲更新于 2026.09.29
JavaScript#函数与作用域
先看核心答案读代码示例
理解线索

纯函数的两个条件

  1. 结果确定返回值只由参数决定,不读全局变量、当前时间、随机数这类会变的东西
  2. 没有副作用不改参数、不改外部变量、不发请求、不写 DOM、不打日志
  3. 副作用边界真正需要的副作用放到外层函数里集中做,核心计算写成纯函数

只读不写也可能不纯。函数里读了一个会变的全局变量或者 Date.now(),同样的参数就可能返回不同结果。

核心回答

先记住这个答案

副作用指的是函数在返回结果之外,对函数外部产生的可观察影响:修改传进来的对象或数组、修改全局变量、发网络请求、读写 localStorage、操作 DOM、打印日志都算。纯函数要同时满足两个条件:一是相同的参数永远返回相同的结果,二是没有任何副作用。它的好处是结果可以预测,测试时只要给输入、比对输出,不用准备环境;同样的参数还可以直接缓存结果。

  • 纯函数:同输入同输出,且不改外部状态
  • 修改传入的对象或数组也是副作用
  • 读当前时间、随机数会让结果不确定
  • 副作用无法消灭,要集中放在边缘处理

最常见的副作用:改了传进来的对象

很多人以为只有发请求、改 DOM 才算副作用,其实最容易踩的是悄悄修改了参数。

往购物车里加商品的两种写法JavaScript
function addImpure(cart, item) {
  cart.push(item) // 直接改了调用方传进来的数组
  return cart
}
function addPure(cart, item) {
  return [...cart, item] // 返回新数组,原数组不动
}

const cart = ['book']
const next = addPure(cart, 'pen')
console.log(cart.join(','), '|', next.join(','))

const same = addImpure(cart, 'bag')
console.log(cart.join(','), '|', same === cart)
查看输出与解释
book | book,pen
book,bag | true

addPure 调用后,原来的 cart 还是 book,新结果在 next 里。addImpure 调用后,调用方手里的 cart 被改成了 book,bag,而且返回的就是同一个数组。 在 React 里这正是一个经典 bug:用 addImpure 的方式更新 state,数组引用没变,React 比较前后 state 时认为没有变化,界面就不会重新渲染。Redux 的 reducer 要求是纯函数,也是同样的原因。

读外部状态为什么也不纯

副作用是往外写,还有一类问题是偷偷往里读。比如 function price(n) { return n * rate },rate 是一个会变的全局变量,同样调用 price(10),rate 变了结果就变了。用 Date.now()、Math.random() 的函数也一样,每次调用结果都可能不同。

这类函数虽然没改任何东西,但结果不确定,所以不算纯函数。解决方法是把这些会变的值改成参数传进来,写成 price(n, rate),rate 由调用方读好再传入。

副作用集中到边上
一个应用不可能没有副作用,不发请求、不渲染页面,程序就什么也没做。所以目标不是消灭副作用,而是把它们集中管理。常见做法是分两层:核心计算写成纯函数,比如计算订单总价、过滤列表;外层负责读数据、调用纯函数、再把结果写回去,比如发请求、setState、写缓存。纯函数那一层用单元测试覆盖,外层只需要测调用顺序和错误处理。React 里的 useEffect 就是这种思路,渲染函数应该是纯的,副作用都放到 effect 里。
回答前,多想一步

容易答错的地方

以为没有赋值语句就是纯函数
console.log、发请求、arr.sort() 都没有显式赋值,但都是副作用。sort、reverse、splice 会直接修改原数组,需要不修改原数组时,用 toSorted、toReversed、toSpliced 或者先复制一份。
以为展开运算符复制就足够了
[...cart] 和 { ...user } 只复制第一层。如果函数里改的是嵌套对象,比如 copy.address.city = 'x',原对象的 address 也会被改,因为两者共享同一个嵌套对象。要改哪一层,就得把从根到那一层的路径都复制一遍。
试着用自己的话回答

面试官还会怎么问?

为什么记忆化(memoization)要求函数是纯的?

记忆化是把参数和结果存起来,下次同样的参数直接返回缓存的结果。它的前提就是「同样的参数一定得到同样的结果」。如果函数依赖外部状态,外部状态变了,缓存返回的就是过期的错误结果。

函数内部修改局部变量算副作用吗?

不算。副作用看的是函数外面能不能观察到。函数里新建一个数组、用 for 循环往里 push,最后返回它,外面看不到这个过程,调用结果仍然只由参数决定,这个函数仍然是纯的。

React 组件为什么要求是纯函数?

React 可能会多次调用组件函数,开发环境的严格模式还会故意调用两次来暴露问题。组件如果在渲染过程中改了外部变量或者发了请求,多调用一次就会出现重复请求、数据错乱。所以渲染阶段只根据 props 和 state 计算 JSX,其它事情放到事件处理函数或 useEffect 里做。

从一道题,走向一组知识

把知识连起来

函数与作用域

函数默认参数里写 x = x,为什么会报错?

默认参数的表达式在每次调用时求值,里面如果调用了有副作用的函数,副作用会在进入函数体之前就发生。

函数与作用域

class 里的方法传出去当回调,this 为什么就丢了?

依赖 this 读写实例状态的方法天然不是纯函数,传出去当回调时 this 还可能丢失。

参考资料

  • JavaScript:函数与作用域参考资料

示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。

本题目录
  1. 先记住这个答案
  2. 最常见的副作用:改了传进来的对象
  3. 读外部状态为什么也不纯
  4. 容易答错的地方
  5. 面试官还会怎么问
  6. 把知识连起来
读懂,再试着讲出来

先看核心答案,再读代码。最后展开追问,检查自己有没有遗漏边界。

试着回答追问
浏览全部面试题理解原理,也关注真实的使用场景。回到顶部 ↑