函数式编程核心概念
纯函数(Pure Function)
定义: 相同输入始终返回相同输出,且不产生副作用。
javascript
// ❌ 非纯函数(依赖外部状态)
let counter = 0
function increment() {
counter++
return counter
}
// ✅ 纯函数(无外部依赖,无副作用)
function add(a, b) {
return a + b
}
// ❌ 非纯函数(有副作用)
function saveUser(user) {
user.updatedAt = new Date() // 修改传入对象
db.save(user) // 副作用:数据库操作
}
// ✅ 纯函数
function createUpdatedUser(user) {
return {
...user,
updatedAt: new Date()
}
}纯函数的优势:
- 可缓存(memoize)
- 可测试
- 可并行执行
- 结果可预测
透明引用(Referential Transparency)
定义: 表达式可以用其值替换而不改变程序行为。
javascript
// 透明引用示例
const add = (a, b) => a + b
const result = add(1, 2) // 可以替换为 3
// 非透明引用
let x = 10
const addToX = y => x + y
// addToX(5) 不能替换为 15,因为 x 可能变化副作用(Side Effects)
定义: 除了返回值之外,对外部环境产生的任何影响。
javascript
// 常见副作用
function withSideEffects() {
// 1. 修改外部变量
globalVar = 'changed'
// 2. 修改传入参数
obj.x = 100
// 3. I/O 操作
console.log('log') // 输出
fetch(url) // 网络请求
readFile(path) // 读文件
// 4. 抛出异常
throw new Error('error')
// 5. 定时器
setTimeout(() => {}, 1000)
return 'result'
}处理副作用的策略:
javascript
// 将副作用延迟到边界处理
const pureAddUser = (user) => ({
type: 'ADD_USER',
payload: user
})
const addUser = (user) => {
// 副作用在这里(可测试的边界)
return db.save(user)
.then(saved => ({ type: 'ADD_USER_SUCCESS', payload: saved }))
.catch(error => ({ type: 'ADD_USER_ERROR', payload: error }))
}柯里化(Currying)
定义: 将多参数函数转换为一系列单参数函数。
javascript
// 普通函数
const add = (a, b, c) => a + b + c
add(1, 2, 3) // 6
// 柯里化版本
const curryAdd = a => b => c => a + b + c
curryAdd(1)(2)(3) // 6
// curry 工具函数
const curry = fn => (...args) => {
if (args.length >= fn.length) return fn(...args)
return (...more) => fn(...args, ...more)
}
const add = (a, b, c) => a + b + c
const curryAdd = curry(add)
curryAdd(1)(2)(3) // 6
curryAdd(1, 2)(3) // 6
curryAdd(1)(2, 3) // 6
curryAdd(1, 2, 3) // 6
// 实际应用
const fetchWithAuth = curry((token, url, options) => {
return fetch(url, { ...options, headers: { ...options.headers, token } })
})
const fetchWithToken = fetchWithAuth('my-token')
fetchWithToken('/api/users', { method: 'GET' })
fetchWithToken('/api/posts', { method: 'POST' })函数组合(Function Composition)
定义: 将多个函数串联起来,一个函数的输出作为下一个函数的输入。
javascript
// compose - 从右到左执行
const compose = (...fns) => x => fns.reduceRight((v, f) => f(v), x)
// pipe - 从左到右执行
const pipe = (...fns) => x => fns.reduce((v, f) => f(v), x)
// 示例
const toUpper = str => str.toUpperCase()
const split = sep => str => str.split(sep)
const join = sep => arr => arr.join(sep)
const first = arr => arr[0]
// compose 执行顺序:first → join → split → toUpper
const getFirstUpper = compose(
first,
join(', '),
split(' '),
toUpper
)
getFirstUpper('hello world from fp') // 'HELLO'
// pipe 执行顺序更直观
const getFirstUpperPipe = pipe(
str => str.toUpperCase(),
str => str.split(' '),
arr => arr[0]
)
getFirstUpperPipe('hello world from fp') // 'HELLO'高阶函数(Higher-Order Functions)
定义: 接收函数作为参数,或返回函数的函数。
数组方法
javascript
const users = [
{ name: 'Alice', age: 25, active: true },
{ name: 'Bob', age: 30, active: false },
{ name: 'Charlie', age: 35, active: true }
]
// map - 转换
const names = users.map(u => u.name) // ['Alice', 'Bob', 'Charlie']
// filter - 过滤
const activeUsers = users.filter(u => u.active)
// reduce - 聚合
const totalAge = users.reduce((sum, u) => sum + u.age, 0) // 90
// find - 查找
const bob = users.find(u => u.name === 'Bob')
// some - 是否存在
const hasUnder20 = users.some(u => u.age < 20)
// every - 是否全部
const allActive = users.every(u => u.active)自定义高阶函数
javascript
// once - 只执行一次
const once = fn => {
let called = false
let result
return (...args) => {
if (called) return result
called = true
result = fn(...args)
return result
}
}
const init = once(() => console.log('initialized'))
init() // 'initialized'
init() // 无输出
// memoize - 记忆化
const memoize = fn => {
const cache = new Map()
return (...args) => {
const key = JSON.stringify(args)
if (cache.has(key)) return cache.get(key)
const result = fn(...args)
cache.set(key, result)
return result
}
}
const fib = memoize(n => {
if (n <= 1) return n
return fib(n - 1) + fib(n - 2)
})
// partial - 部分应用
const partial = (fn, ...args) => (...moreArgs) => fn(...args, ...moreArgs)
const add = (a, b, c) => a + b + c
const add5 = partial(add, 5)
add5(3, 2) // 10不可变性(Immutability)
定义: 不修改原始数据,创建新的数据副本。
javascript
// ❌ 可变操作
const arr = [1, 2, 3]
arr.push(4) // 修改原数组
arr[0] = 'changed' // 修改原数组
// ✅ 不可变操作
const newArr = [...arr, 4] // [1, 2, 3, 4]
const updatedArr = arr.map(x => x === 2 ? 'changed' : x)
// 对象
const obj = { a: 1, b: 2 }
const newObj = { ...obj, b: 3 } // { a: 1, b: 3 }
const updated = { ...obj, c: 3 } // { a: 1, b: 2, c: 3 }
// 深层不可变
const nested = { a: { b: { c: 1 } } }
const updatedNested = {
...nested,
a: {
...nested.a,
b: {
...nested.a.b,
c: 2
}
}
}不可变库:
javascript
// Immer - 更简洁的不可变操作
import { produce } from 'immer'
const state = { count: 0, todos: ['learn fp'] }
const newState = produce(state, draft => {
draft.count++
draft.todos.push('use immer')
})面试高频题
Q1: 什么是纯函数?有什么好处?
答案: 纯函数是相同输入始终返回相同输出,且不产生副作用的函数。
好处:
- 可缓存(性能优化)
- 可测试
- 可并行执行
- 结果可预测,易于推理
Q2: 柯里化的作用是什么?
答案:
- 将多参数函数转为单参数函数链
- 提高函数的复用性和组合性
- 延迟执行,灵活传参
Q3: 函数组合相比嵌套函数调用的优势?
答案:
- 更清晰的可读性
- 更易于调试和测试
- 符合管道式编程思想
javascript
// ❌ 嵌套
toUpper(first(join(',')(split(' ')(str))))
// ✅ 组合
pipe(str => str.split(' '), join(', '), toUpper, first)Q4: map、filter、reduce 的区别?
答案:
map:转换,每个元素映射为新值,长度不变filter:过滤,保留符合条件的元素reduce:聚合,将数组归约为单个值