{T}

纯函数(Pure Functions)

纯函数是函数式编程的核心概念之一,在前端开发中扮演着至关重要的角色。理解并正确使用纯函数,能够显著提升代码质量、可维护性和应用性能

定义

纯函数是指满足以下两个条件的函数:

  1. 相同的输入永远得到相同的输出(确定性)
  2. 不产生任何副作用(无副作用性)

换句话说,纯函数就像一个数学函数:给定相同的参数,总是返回相同的结果,而且不会对程序的外部状态造成任何影响

javascript
// 纯函数示例
function add(a, b) {
  return a + b
}

// 不纯函数示例
let counter = 0
function incrementCounter() {
  counter++ // 修改了外部状态
  return counter
}

纯函数的三大特征

无状态性(Statelessness)

纯函数不依赖或修改函数外部的状态。它们只依赖于传入的参数,这使得函数的行为完全可预测。

javascript
// ❌ 不纯函数 - 依赖外部状态
let taxRate = 0.1
function calculateTax(price) {
  return price * taxRate // 依赖外部变量 taxRate
}

// ✅ 纯函数 - 无状态性
function calculateTax(price, taxRate) {
  return price * taxRate // 只依赖参数
}

无副作用性(No Side Effects)

纯函数不会引起任何可观察的副作用,包括:

  • 修改外部变量或对象
  • 修改函数参数
  • 执行 I/O 操作(如 console.log、网络请求)
  • 操作 DOM
  • 抛出异常(除非是函数参数本身的问题)
javascript
// ❌ 不纯函数 - 有副作用
function addItem(items, newItem) {
  items.push(newItem) // 修改了原数组
  return items
}

// ✅ 纯函数 - 无副作用
function addItem(items, newItem) {
  return [...items, newItem] // 返回新数组,不修改原数组
}

引用透明性(Referential Transparency)

引用透明性意味着函数调用可以被其返回值替代,而不会影响程序的行为。这个特性使得纯函数更容易推理和优化

javascript
// 纯函数具有引用透明性
function multiply(a, b) {
  return a * b
}

// 在程序中,multiply(3, 4) 可以被直接替换为 12
const result1 = multiply(3, 4) + 10 // 等价于 const result1 = 12 + 10;
const result2 = 12 + 10 // 与上面的表达式等价

典型应用

React 中的函数组件

React 的函数组件本质上是纯函数,给定相同的 props,总是渲染相同的 UI

javascript
// ✅ 纯函数组件
function Greeting({ name, age }) {
  return (
    <div>
      <h1>Hello, {name}!</h1>
      <p>You are {age} years old.</p>
    </div>
  )
}

// ❌ 不纯的函数组件
let theme = "light"
function ThemedButton({ label }) {
  // 依赖外部状态,不是纯函数
  return <button className={theme}>{label}</button>
}

Redux 中的 Reducer 函数

Redux 的 reducer 必须是纯函数,这是 Redux 架构的核心原则

javascript
// ✅ 纯函数reducer
function todoReducer(state = [], action) {
  switch (action.type) {
    case "ADD_TODO":
      return [...state, action.payload] // 返回新数组
    case "REMOVE_TODO":
      return state.filter((todo) => todo.id !== action.payload)
    default:
      return state
  }
}

// ❌ 不纯的reducer
function todoReducer(state = [], action) {
  switch (action.type) {
    case "ADD_TODO":
      state.push(action.payload) // ❌ 直接修改原状态
      return state
    default:
      return state
  }
}

Vue 的计算属性

Vue 的计算属性本质上是纯函数,它们基于依赖数据计算返回值

javascript
export default {
  data() {
    return {
      firstName: "John",
      lastName: "Doe"
    }
  },
  computed: {
    // ✅ 纯函数 - 计算属性
    fullName() {
      return `${this.firstName} ${this.lastName}`
    },

    // 纯函数使得计算属性可以缓存
    reversedName() {
      return this.fullName.split("").reverse().join("")
    }
  }
}

纯函数与不纯函数的对比示例

数据处理对比

javascript
// 不纯的数据处理
class DataProcessor {
  constructor() {
    this.cache = {}
  }

  processData(data) {
    // 修改外部状态(缓存)—— 副作用
    this.cache[JSON.stringify(data)] = data.map((item) => item * 2)
    return this.cache[JSON.stringify(data)]
  }
}

// ✅ 正确的记忆化:缓存封装在内部,不污染外部
const DataProcessor = (() => {
  const cache = new Map()

  return {
    // 纯函数 + 内部缓存
    processData(data) {
      const key = JSON.stringify(data)
      if (cache.has(key)) {
        return cache.get(key)
      }
      const result = data.map((item) => item * 2)
      cache.set(key, result)
      return result
    }
  }
})()

状态管理对比

javascript
// 不纯的状态管理
let appState = {
  user: null,
  posts: []
}

function loginUser(username) {
  appState.user = { name: username } // 直接修改全局状态
  return appState
}

// 纯函数的状态管理
function loginUser(currentState, username) {
  return {
    ...currentState,
    user: { name: username }
  }
}

// 使用纯函数的状态更新
let appState = {
  user: null,
  posts: []
}

appState = loginUser(appState, "John") // 返回新状态

纯函数在前端性能优化中的作用

便于记忆化(Memoization)

纯函数的确定性使其非常适合记忆化优化

javascript
// 记忆化工具函数
function memoize(fn) {
  const cache = new Map()

  return function (...args) {
    const key = JSON.stringify(args)

    if (cache.has(key)) {
      console.log("从缓存获取结果")
      return cache.get(key)
    }

    const result = fn.apply(this, args)
    cache.set(key, result)
    return result
  }
}

// 使用示例
const expensiveCalculation = memoize(function (n) {
  console.log(`计算 ${n} 的斐波那契数`)
  if (n <= 1) return n
  return expensiveCalculation(n - 1) + expensiveCalculation(n - 2)
})

// 第一次计算
console.log(expensiveCalculation(40)) // 实际计算
console.log(expensiveCalculation(40)) // 从缓存获取

更可靠的单元测试

纯函数使得单元测试变得简单直接,不需要复杂的设置和清理

javascript
// 纯函数的测试
import { calculateDiscount, formatPrice } from "./priceUtils"

describe("价格工具函数", () => {
  test("calculateDiscount 应该正确计算折扣", () => {
    expect(calculateDiscount(100, 0.2)).toBe(80)
    expect(calculateDiscount(50, 0.5)).toBe(25)
    expect(calculateDiscount(0, 0.1)).toBe(0)
  })

  test("formatPrice 应该正确格式化价格", () => {
    expect(formatPrice(99.99)).toBe("¥99.99")
    expect(formatPrice(1000)).toBe("¥1,000.00")
    expect(formatPrice(0)).toBe("¥0.00")
  })

  test("不纯的格式化函数", () => {
    // 测试变得复杂:需要模拟全局状态、重置副作用
    setLocale("zh-CN")
    expect(formatPriceImpure(99.99)).toBe("¥99.99")
    setLocale("en-US") // 清理:需要还原状态
    expect(formatPriceImpure(99.99)).toBe("$99.99")
  })
})

更好的可维护性

纯函数提高了代码的可读性和可维护性

javascript
// 纯函数的业务逻辑
const ShoppingCart = {
  // 添加商品(纯函数,返回新数组)
  addItem: (cart, item) => {
    const existingItem = cart.find((i) => i.id === item.id)

    if (existingItem) {
      return cart.map((i) =>
        i.id === item.id ? { ...i, quantity: i.quantity + 1 } : i
      )
    }

    return [...cart, { ...item, quantity: 1 }]
  },

  // 计算总价(纯函数)
  calculateTotal: (cart, taxRate) =>
    cart.reduce((sum, i) => sum + i.price * i.quantity, 0) * (1 + taxRate),

  // 应用折扣(纯函数)
  applyDiscount: (total, code) =>
    code === "SAVE10" ? total * 0.9 : total

let cart = []
cart = ShoppingCart.addItem(cart, { id: 1, name: "T-shirt", price: 29.99 })
cart = ShoppingCart.addItem(cart, { id: 2, name: "Jeans", price: 89.99 })

const total = ShoppingCart.calculateTotal(cart, 0.08)
const finalPrice = ShoppingCart.applyDiscount(total, "SAVE10")

注意事项和最佳实践

识别和重构不纯函数

javascript
// ❌ 不纯函数 - 需要重构
class UserService {
  constructor() {
    this.users = []
  }

  addUser(user) {
    this.users.push(user) // 修改内部状态
    this.logActivity(user) // 副作用
    return this.users
  }

  logActivity(user) {
    console.log("用户活动:", user.name) // 副作用
  }
}

// ✅ 重构:纯函数 + 副作用分离
// 纯函数 - 校验用户
function validateUser(user) {
  return (
    user.name &&
    user.email &&
    user.age > 0 &&
    user.email.includes("@")
  )
}

// 纯函数 - 添加用户(返回新数组)
function addUser(users, user) {
  return [...users, user]
}

// 副作用 - 记录日志(独立函数)
function logActivity(user) {
  console.log("用户活动:", user.name)
}

// 使用:副作用明确地在调用处执行
const users = []
const newUser = { name: "John", email: "john@example.com", age: 25 }

if (validateUser(newUser)) {
  const updatedUsers = addUser(users, newUser) // 纯函数
  logActivity(newUser) // 明确的副作用
}

处理异步操作

javascript
// 纯函数处理异步数据
// 不纯的方式:在函数中直接发起网络请求
async function fetchUserData(userId) {
  const response = await fetch(`/api/users/${userId}`) // 副作用
  return response.json()
}

// 更纯的方式:分离副作用
// 纯函数 - 数据处理
function processUserData(rawData) {
  return {
    id: rawData.id,
    name: rawData.name,
    email: rawData.email,
    avatar: rawData.avatar_url
  }
}

// 副作用分离
async function fetchAndProcessUser(userId) {
  const response = await fetch(`/api/users/${userId}`)
  const rawData = await response.json()
  return processUserData(rawData) // 使用纯函数处理数据
}

最佳实践清单

  1. 优先使用纯函数:对于数据转换、计算等操作,优先使用纯函数
  2. 明确分离副作用:将 I/O 操作、DOM 操作等副作用集中到专门的函数中
  3. 避免修改原数据:使用展开运算符、Object.assign 等创建新对象
  4. 使用类型系统:TypeScript 可以帮助识别潜在的副作用
  5. 编写测试:纯函数更容易测试,确保为重要的纯函数编写单元测试
  6. 利用框架优化:React 的 React.memo、Vue 的计算属性等都依赖纯函数特性
javascript
// 综合示例:遵循最佳实践的纯函数代码
import { memo } from "react"

// 纯函数工具库
const Utils = {
  // 纯函数 - 数组操作
  sortBy: (array, key) => {
    return [...array].sort((a, b) => a[key] - b[key])
  },

  // 纯函数 - 对象操作
  pick: (obj, keys) => {
    return keys.reduce((result, key) => {
      if (obj.hasOwnProperty(key)) {
        result[key] = obj[key]
      }
      return result
    }, {})
  },

  // 纯函数 - 按 id 查找
  findById: (array, id) => array.find((item) => item.id === id) || null
}

// 使用 memo 优化的 React 组件
const UserList = memo(function UserList({ users, onSelect }) {
  const sortedUsers = Utils.sortBy(users, "id")

  return (
    <ul>
      {sortedUsers.map((user) => (
        <li key={user.id} onClick={() => onSelect(Utils.pick(user, ["id", "name"]))}>
          {user.name}
        </li>
      ))}
    </ul>
  )
})

export default UserList

总结

纯函数是前端开发中的重要概念,它提供了以下优势:

  • 可预测性:相同输入总是得到相同输出
  • 可测试性:无需复杂的测试设置
  • 可缓存性:便于实现记忆化优化
  • 可并行性:纯函数可以安全地并行执行
  • 可维护性:代码逻辑清晰,易于理解和修改

在实际开发中,应该尽可能使用纯函数来处理核心业务逻辑,将副作用明确分离。这种编程方式不仅能提高代码质量,还能充分利用现代前端框架的优化特性,构建更加可靠和高效的应用程序