高阶函数
高阶函数(Higher-Order Function)是指接受函数作为参数,或者返回函数的函数。高阶函数是函数式编程的核心概念,也是 JavaScript 中最常见的编程模式之一。
概念
定义
高阶函数满足以下条件之一:
- 接受一个或多个函数作为参数
- 返回一个函数
javascript
// 类型 1:接受函数作为参数
function forEach(array, callback) {
for (let i = 0; i < array.length; i++) {
callback(array[i], i, array)
}
}
// 类型 2:返回函数
function multiplier(factor) {
return function (number) {
return number * factor
}
}
// 类型 3:既接受函数作为参数,又返回函数
function decorate(func) {
return function (...args) {
console.log(`Calling function with args: ${args}`)
const result = func(...args)
console.log(`Result: ${result}`)
return result
}
}为什么需要高阶函数?
| 优势 | 说明 | 示例 |
|---|---|---|
| 抽象性 | 隐藏实现细节,关注"做什么"而非"怎么做" | map、filter |
| 复用性 | 通过组合和柯里化实现代码复用 | 函数工厂模式 |
| 可测试性 | 小函数易于测试,纯函数无副作用 | 单元测试 |
| 可维护性 | 声明式代码更易理解和维护 | 链式调用 |
常见数组高阶函数
1. map
作用:对数组的每个元素执行回调函数,返回新数组。
特点:
- 不修改原数组(不可变)
- 返回新数组的长度与原数组相同
- 适合转换数据
javascript
const numbers = [1, 2, 3, 4, 5]
// 基础用法
const doubled = numbers.map(n => n * 2)
console.log(doubled) // [2, 4, 6, 8, 10]
// 使用索引参数
const withIndex = numbers.map((n, index) => `${index}: ${n}`)
console.log(withIndex) // ['0: 1', '1: 2', '2: 3', '3: 4', '4: 5']
// 提取对象属性
const users = [
{ name: 'John', age: 30 },
{ name: 'Jane', age: 25 },
{ name: 'Bob', age: 35 }
]
const names = users.map(user => user.name)
console.log(names) // ['John', 'Jane', 'Bob']
// 手动实现 map
function myMap(array, callback) {
const result = []
for (let i = 0; i < array.length; i++) {
result.push(callback(array[i], i, array))
}
return result
}2. filter
作用:过滤数组,返回满足条件的元素组成的新数组。
特点:
- 不修改原数组
- 返回新数组的长度可能小于原数组
- 回调函数返回布尔值
javascript
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
// 基础用法
const evens = numbers.filter(n => n % 2 === 0)
console.log(evens) // [2, 4, 6, 8, 10]
// 过滤对象数组
const users = [
{ name: 'John', age: 30, active: true },
{ name: 'Jane', age: 25, active: false },
{ name: 'Bob', age: 35, active: true }
]
const activeUsers = users.filter(user => user.active)
console.log(activeUsers) // [{ name: 'John', ... }, { name: 'Bob', ... }]
// 多条件过滤
const filtered = users.filter(user => user.age >= 30 && user.active)
console.log(filtered) // [{ name: 'John', ... }]
// 手动实现 filter
function myFilter(array, callback) {
const result = []
for (let i = 0; i < array.length; i++) {
if (callback(array[i], i, array)) {
result.push(array[i])
}
}
return result
}3. reduce
作用:将数组元素累积为单个值。
参数说明:
callback(accumulator, currentValue, index, array):回调函数initialValue:初始值(强烈推荐提供)
javascript
const numbers = [1, 2, 3, 4, 5]
// 求和
const sum = numbers.reduce((total, n) => total + n, 0)
console.log(sum) // 15
// 求最大值
const max = numbers.reduce((max, n) => n > max ? n : max, numbers[0])
console.log(max) // 5
// 数组转对象
const users = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' },
{ id: 3, name: 'Charlie' }
]
const usersById = users.reduce((obj, user) => {
obj[user.id] = user
return obj
}, {})
console.log(usersById)
// { 1: { id: 1, name: 'Alice' }, 2: {...}, 3: {...} }
// 手写 reduce
function myReduce(array, callback, initialValue) {
let accumulator = initialValue
let startIndex = 0
// 如果没有初始值,使用第一个元素
if (initialValue === undefined) {
accumulator = array[0]
startIndex = 1
}
for (let i = startIndex; i < array.length; i++) {
accumulator = callback(accumulator, array[i], i, array)
}
return accumulator
}4. reduceRight
作用:从右到左累积数组元素。
javascript
const numbers = [[1, 2], [3, 4], [5, 6]]
// reduce vs reduceRight
const flattened1 = numbers.reduce((acc, arr) => acc.concat(arr), [])
console.log(flattened1) // [1, 2, 3, 4, 5, 6]
const flattened2 = numbers.reduceRight((acc, arr) => acc.concat(arr), [])
console.log(flattened2) // [5, 6, 3, 4, 1, 2]
// 组合函数(从右到左)
const compose = (...funcs) => input =>
funcs.reduceRight((acc, func) => func(acc), input)
const add1 = x => x + 1
const double = x => x * 2
const subtract10 = x => x - 10
const composed = compose(subtract10, double, add1)
console.log(composed(5)) // 25. forEach
作用:遍历数组元素,无返回值。
特点:
- 无法中断循环(除非抛出异常)
- 不返回新数组
- 适合执行副作用操作
javascript
const numbers = [1, 2, 3, 4, 5]
// 基础用法
numbers.forEach((n, index) => {
console.log(`Index ${index}: ${n}`)
})
// 与 map 的区别
const mapped = numbers.forEach(n => n * 2)
console.log(mapped) // undefined(forEach 不返回值)
// 副作用示例:修改外部变量
let sum = 0
numbers.forEach(n => sum += n)
console.log(sum) // 15
// 手动实现 forEach
function myForEach(array, callback) {
for (let i = 0; i < array.length; i++) {
callback(array[i], i, array)
}
}6. find 和 findIndex
作用:查找满足条件的元素或索引。
特点:
- 找到第一个满足条件的元素后立即返回
- 未找到时,
find返回undefined,findIndex返回-1
javascript
const users = [
{ id: 1, name: 'John', age: 30 },
{ id: 2, name: 'Jane', age: 25 },
{ id: 3, name: 'Bob', age: 35 }
]
// find:返回元素
const user = users.find(u => u.id === 2)
console.log(user) // { id: 2, name: 'Jane', age: 25 }
// findIndex:返回索引
const index = users.findIndex(u => u.name === 'Bob')
console.log(index) // 2
// 未找到的情况
const notFound = users.find(u => u.id === 999)
console.log(notFound) // undefined
const notFoundIndex = users.findIndex(u => u.id === 999)
console.log(notFoundIndex) // -1
// 实际应用:更新数组中的对象
const userId = 2
const updatedUsers = users.map(u =>
u.id === userId ? { ...u, age: 26 } : u
)7. some 和 every
作用:检测数组元素是否满足条件。
特点:
some:至少有一个满足条件返回trueevery:所有元素都满足条件返回true- 都会短路求值(找到结果立即返回)
javascript
const numbers = [1, 2, 3, 4, 5]
// some:检查是否存在
const hasEven = numbers.some(n => n % 2 === 0)
console.log(hasEven) // true
const hasNegative = numbers.some(n => n < 0)
console.log(hasNegative) // false
// every:检查全部
const allPositive = numbers.every(n => n > 0)
console.log(allPositive) // true
// 实际应用:表单校验
const formData = [
{ field: 'username', valid: true },
{ field: 'email', valid: false },
{ field: 'password', valid: true }
]
const formIsValid = formData.every(field => field.valid)
console.log(formIsValid) // false
// 实际应用:权限检查
const userPermissions = ['read', 'write']
const requiredPermissions = ['read', 'delete']
const hasPermission = requiredPermissions.every(p =>
userPermissions.includes(p)
)
console.log(hasPermission) // false8. flatMap
作用:先 map 再 flat,相当于 map().flat(1)。
特点:
- 一边映射一边扁平化
- 只会扁平化一层
javascript
const sentences = ['Hello World', 'Hi JavaScript', 'Good Morning']
// 提取所有单词
const words = sentences.flatMap(sentence => sentence.split(' '))
console.log(words) // ['Hello', 'World', 'Hi', 'JavaScript', 'Good', 'Morning']
// 过滤并映射
const numbers = [1, 2, 3, 4, 5]
const result = numbers.flatMap(n => n % 2 === 0 ? [n, n * 10] : [])
console.log(result) // [2, 20, 4, 40]
// 对比 map + flat
const mapped = sentences.map(s => s.split(' '))
console.log(mapped) // [['Hello', 'World'], ['Hi', 'JavaScript'], ['Good', 'Morning']]
const flattened = mapped.flat()
console.log(flattened) // ['Hello', 'World', 'Hi', 'JavaScript', 'Good', 'Morning']
// 实际应用:展开嵌套结构
const orders = [
{ id: 1, items: ['Apple', 'Banana'] },
{ id: 2, items: ['Orange'] },
{ id: 3, items: ['Grape', 'Mango', 'Pineapple'] }
]
const allItems = orders.flatMap(order => order.items)
console.log(allItems) // ['Apple', 'Banana', 'Orange', 'Grape', 'Mango', 'Pineapple']9. sort
作用:数组排序(注意:会修改原数组)。
特点:
- 默认按字符串 Unicode 码点排序
- 提供比较函数:返回负数表示
a在前,正数表示b在前 - 不稳定排序(直到 ES2019 才稳定)
javascript
const numbers = [3, 1, 4, 1, 5, 9, 2, 6]
// ⚠️ 错误:默认字符串排序
const sortedWrong = [10, 2, 30, 4].sort()
console.log(sortedWrong) // [10, 2, 30, 4](字符串排序)
// ✅ 正确:数值排序
const nums1 = [3, 1, 4, 1, 5, 9, 2, 6].sort((a, b) => a - b) // 升序
console.log(nums1) // [1, 1, 2, 3, 4, 5, 6, 9]
const nums2 = [3, 1, 4, 1, 5, 9, 2, 6].sort((a, b) => b - a) // 降序
console.log(nums2) // [9, 6, 5, 4, 3, 2, 1, 1]
// 对象数组按属性排序
const products = [
{ name: 'iPhone', price: 7999 },
{ name: 'iPad', price: 5999 },
{ name: 'MacBook', price: 12999 }
]
const byPriceAsc = products.sort((a, b) => a.price - b.price)
console.log(byPriceAsc) // iPad(5999), iPhone(7999), MacBook(12999)
// 多字段排序:先按价格,再按名称
const byPriceThenName = products.sort((a, b) =>
a.price - b.price || a.name.localeCompare(b.name)
)
// 不修改原数组的排序
const original = [3, 1, 4, 1, 5]
const sorted = [...original].sort((a, b) => a - b)
console.log(original) // [3, 1, 4, 1, 5](原数组未修改)
console.log(sorted) // [1, 1, 3, 4, 5]函数组合
compose(从右到左)
作用:将多个函数组合成一个函数,从右到左执行。
javascript
function compose(...funcs) {
if (funcs.length === 0) return arg => arg
if (funcs.length === 1) return funcs[0]
return funcs.reduce((a, b) => (...args) => a(b(...args)))
}
const add1 = x => x + 1
const double = x => x * 2
const subtract10 = x => x - 10
const composed = compose(subtract10, double, add1)
console.log(composed(5)) // 2
// 执行顺序:add1(5) -> double(6) -> subtract10(12) -> 2pipe(从左到右)
作用:将多个函数组合成一个函数,从左到右执行(更符合直觉)。
javascript
function pipe(...funcs) {
if (funcs.length === 0) return arg => arg
if (funcs.length === 1) return funcs[0]
return funcs.reduce((a, b) => (...args) => b(a(...args)))
}
const add1 = x => x + 1
const double = x => x * 2
const subtract10 = x => x - 10
const piped = pipe(add1, double, subtract10)
console.log(piped(5)) // 2
// 执行顺序:add1(5) -> double(6) -> subtract10(12) -> 2实际应用:数据处理管道
javascript
// 数据处理管道
const data = [
{ id: 1, name: 'john', age: 30, active: true },
{ id: 2, name: 'jane', age: 25, active: false },
{ id: 3, name: 'bob', age: 35, active: true }
]
const pipeline = pipe(
users => users.filter(u => u.active), // 过滤活跃用户
users => users.sort((a, b) => a.age - b.age), // 按年龄排序
users => users.map(u => ({ ...u, name: u.name.toUpperCase() })), // 大写名称
users => users.map(u => u.name) // 提取名称
)
console.log(pipeline(data)) // ['JOHN', 'BOB']柯里化(Currying)
定义:将接受多个参数的函数转换为一系列接受单个参数的函数。
基础柯里化
javascript
// 手动柯里化实现
function curry(func) {
return function curried(...args) {
if (args.length >= func.length) {
return func.apply(this, args)
} else {
return function (...args2) {
return curried.apply(this, args.concat(args2))
}
}
}
}
function sum(a, b, c) {
return a + b + c
}
const curriedSum = curry(sum)
console.log(curriedSum(1)(2)(3)) // 6
console.log(curriedSum(1, 2)(3)) // 6
console.log(curriedSum(1)(2, 3)) // 6
console.log(curriedSum(1, 2, 3)) // 6实际应用场景
javascript
// 1. 日志系统
function log(level, time, message) {
console.log(`[${level}] ${time}: ${message}`)
}
const curriedLog = curry(log)
// 创建特定级别的日志函数
const errorLog = curriedLog('ERROR')
const errorLogWithTime = errorLog(new Date().toISOString())
errorLogWithTime('Something went wrong')
// 柯里化事件处理:预设元素和事件类型
const handleEvent = (element, eventType, handler) => {
element.addEventListener(eventType, handler)
}
const curriedHandler = curry(handleEvent)
const addClickHandler = curriedHandler(document.getElementById('btn'))('click')
addClickHandler(() => console.log('Clicked!'))偏函数(Partial Application)
定义:固定函数的部分参数,返回一个接受剩余参数的新函数。
javascript
function partial(func, ...presetArgs) {
return function (...laterArgs) {
return func.apply(this, presetArgs.concat(laterArgs))
}
}
function greet(greeting, name, punctuation) {
return `${greeting}, ${name}${punctuation}`
}
const sayHello = partial(greet, 'Hello')
console.log(sayHello('John', '!')) // 'Hello, John!'
const sayHelloToJohn = partial(greet, 'Hello', 'John')
console.log(sayHelloToJohn('!')) // 'Hello, John!'
// 使用 bind 实现偏函数
const sayHi = greet.bind(null, 'Hi')
console.log(sayHi('Jane', '.')) // 'Hi, Jane.'柯里化 vs 偏函数
javascript
// 柯里化:每次只接受一个参数
const curriedAdd = curry((a, b, c) => a + b + c)
curriedAdd(1)(2)(3) // 必须分步调用
// 偏函数:可以固定任意数量的参数
const partialAdd = partial((a, b, c) => a + b + c, 1)
partialAdd(2, 3) // 可以传递剩余所有参数记忆化(Memoization)
作用:缓存函数的计算结果,避免重复计算。
javascript
function memoize(func) {
const cache = new Map()
return function (...args) {
const key = JSON.stringify(args)
if (cache.has(key)) {
console.log(`Cache hit for ${key}`)
return cache.get(key)
}
const result = func.apply(this, args)
cache.set(key, result)
return result
}
}
// 使用:记忆化斐波那契
function fib(n) {
return n <= 1 ? n : fib(n - 1) + fib(n - 2)
}
const memoizedFib = memoize(fib)
console.time('First call')
console.log(memoizedFib(40)) // 计算并缓存
console.timeEnd('First call')
console.time('Second call')
console.log(memoizedFib(40)) // 从缓存读取
console.timeEnd('Second call')高级记忆化
javascript
// 支持自定义键生成器
function memoizeWith(keyGenerator) {
return function (func) {
const cache = new Map()
return function (...args) {
const key = keyGenerator(...args)
if (cache.has(key)) {
return cache.get(key)
}
const result = func.apply(this, args)
cache.set(key, result)
return result
}
}
}
// 示例:缓存 API 请求
const memoizeAPI = memoizeWith((url, params) => `${url}?${JSON.stringify(params)}`)
const fetchUser = memoizeAPI(async (url, params) => {
const response = await fetch(url, params)
return response.json()
})函数节流与防抖
防抖(Debounce)
作用:在事件触发后延迟执行,如果在延迟期间再次触发,则重新计时。
应用场景:搜索框输入、窗口大小调整
javascript
function debounce(func, delay, immediate = false) {
let timer = null
return function (...args) {
const callNow = immediate && !timer
clearTimeout(timer)
timer = setTimeout(() => {
timer = null
if (!immediate) {
func.apply(this, args)
}
}, delay)
// immediate 模式:首次立即执行
if (callNow) {
func.apply(this, args)
}
}
}
// 使用示例:搜索框输入防抖
const searchInput = document.getElementById('search')
const handleSearch = debounce((query) => {
console.log('搜索:', query)
}, 300)
searchInput.addEventListener('input', (e) => handleSearch(e.target.value))
// 使用示例:窗口大小调整
const handleResize = debounce(() => {
console.log('Window resized:', window.innerWidth, window.innerHeight)
}, 250)
window.addEventListener('resize', handleResize)节流(Throttle)
作用:在一定时间间隔内只执行一次,不管触发多少次。
应用场景:滚动事件、鼠标移动、按钮点击
javascript
function throttle(func, limit) {
let inThrottle = false
let lastArgs = null
return function (...args) {
if (!inThrottle) {
func.apply(this, args)
inThrottle = true
lastArgs = null
setTimeout(() => {
inThrottle = false
if (lastArgs) {
func.apply(this, lastArgs)
}
lastArgs = null
}, limit)
} else {
// 记录最后一次参数,供节流结束后执行
lastArgs = args
}
}
}
// 使用示例:滚动事件节流
const handleScroll = throttle(() => {
console.log('Scroll position:', window.scrollY)
}, 200)
window.addEventListener('scroll', handleScroll)
// 使用示例:按钮点击节流(防止重复提交)
const handleClick = throttle((id) => {
console.log('Processing:', id)
// 处理点击
}, 1000)
button.addEventListener('click', () => handleClick(itemId))防抖 vs 节流
code
连续触发事件(每 100ms 触发一次,持续 1000ms)
防抖(delay = 500ms):
触发 ──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┐
│ │ │ │ │ │ │ │ │ │ └──> 最后一次触发后 500ms 执行
└──┴──┴──┴──┴──┴──┴──┴──┴──┴──> 重置计时器
节流(limit = 500ms):
触发 ──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┐
│ │ │ │ │ │ │ │ │ │
执行 └──× │ │ │ └──× │ │ └──×
500ms 500ms 500ms函数工厂
作用:返回函数的函数,用于创建相关但不同的函数。
javascript
// 创建乘法函数
function createMultiplier(multiplier) {
return function (number) {
return number * multiplier
}
}
const double = createMultiplier(2)
const triple = createMultiplier(3)
console.log(double(5)) // 10
console.log(triple(5)) // 15
// 创建事件处理函数工厂
function createEventHandler(config) {
return function (event) {
if (config.preventDefault) {
event.preventDefault()
}
if (config.stopPropagation) {
event.stopPropagation()
}
config.handler(event)
}
}
const handleClick = createEventHandler({
preventDefault: true,
stopPropagation: true,
handler: (e) => console.log('Clicked:', e.target)
})高阶函数的优势
1. 代码复用
javascript
// 使用高阶函数简化重复代码
const operations = {
add: (a, b) => a + b,
subtract: (a, b) => a - b,
multiply: (a, b) => a * b,
divide: (a, b) => a / b
}
function calculate(array, operation) {
return array.reduce(operations[operation])
}
console.log(calculate([1, 2, 3, 4, 5], 'add')) // 15
console.log(calculate([1, 2, 3, 4, 5], 'multiply')) // 120
// 通用过滤器
function createFilter(predicate) {
return function (array) {
return array.filter(predicate)
}
}
const filterEvens = createFilter(n => n % 2 === 0)
const filterPositives = createFilter(n => n > 0)
console.log(filterEvens([1, 2, 3, 4, 5])) // [2, 4]
console.log(filterPositives([-1, 0, 1, 2])) // [1, 2]2. 声明式编程
javascript
// 命令式:告诉计算机"怎么做"
const numbers = [1, 2, 3, 4, 5]
const doubled = []
for (let i = 0; i < numbers.length; i++) {
doubled.push(numbers[i] * 2)
}
// 声明式:告诉计算机"做什么"
const doubled = numbers.map(n => n * 2)
// 复杂示例
const users = [
{ name: 'John', age: 30, active: true },
{ name: 'Jane', age: 25, active: false },
{ name: 'Bob', age: 35, active: true }
]
// 命令式
const result = []
for (let i = 0; i < users.length; i++) {
if (users[i].active && users[i].age >= 30) {
result.push(users[i].name.toUpperCase())
}
}
// 声明式
const result = users
.filter(u => u.active && u.age >= 30)
.map(u => u.name.toUpperCase())3. 关注点分离
javascript
// 业务逻辑与数据处理分离
const users = [
{ name: 'John', age: 30 },
{ name: 'Jane', age: 25 },
{ name: 'Bob', age: 35 }
]
// 定义可复用的转换函数
const getNames = users => users.map(user => user.name)
const filterAdults = users => users.filter(user => user.age >= 18)
const sortByAge = users => [...users].sort((a, b) => a.age - b.age)
// 组合使用
const names = getNames(filterAdults(sortByAge(users)))
console.log(names) // ['Jane', 'John', 'Bob']
// 或使用 pipe
const processUsers = pipe(
sortByAge,
filterAdults,
getNames
)
console.log(processUsers(users)) // ['Jane', 'John', 'Bob']性能考虑
1. 链式调用的性能
javascript
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
// ❌ 多次遍历(性能较差)
const result1 = numbers
.filter(n => n % 2 === 0) // 遍历一次
.map(n => n * 2) // 遍历两次
.filter(n => n > 10) // 遍历三次
// ✅ 单次遍历(性能更好)
const result2 = numbers.reduce((acc, n) => {
if (n % 2 === 0) {
const doubled = n * 2
if (doubled > 10) {
acc.push(doubled)
}
}
return acc
}, [])
// 对于大型数组,考虑使用 transducer
const transduce = (xf, reducer, initial, iterable) => {
const transformedReducer = xf(reducer)
return iterable.reduce(transformedReducer, initial)
}2. 避免在回调中创建函数
javascript
// ❌ 每次渲染都创建新函数(React 中可能导致不必要的重渲染)
class Component {
render() {
return (
<div>
{items.map(item => (
<Item key={item.id} onClick={() => this.handleClick(item.id)} />
))}
</div>
)
}
}
// ✅ 正确:使用类字段箭头函数,避免每次渲染创建新函数
class ComponentOptimized {
handleClick = (id) => {
console.log('Clicked:', id)
}
render() {
return (
<div>
{items.map(item => (
<Item key={item.id} onClick={this.handleClick} />
))}
</div>
)
}
}3. 内存优化
javascript
// ❌ 记忆化可能导致内存泄漏
function badMemoize(func) {
const cache = new Map() // 无限增长的缓存
return function (...args) {
const key = JSON.stringify(args)
if (!cache.has(key)) {
cache.set(key, func(...args))
}
return cache.get(key)
}
}
// ✅ 正确:带容量限制的 LRU 记忆化
function goodMemoize(func, maxSize = 100) {
const cache = new Map()
return function (...args) {
const key = JSON.stringify(args)
if (cache.has(key)) {
// 更新访问顺序(LRU):删除后重新插入
const value = cache.get(key)
cache.delete(key)
cache.set(key, value)
return value
}
const result = func.apply(this, args)
cache.set(key, result)
// 超过容量时删除最旧(LRU)
if (cache.size > maxSize) {
const firstKey = cache.keys().next().value
cache.delete(firstKey) // 删除最旧的
}
return result
}
}常见陷阱与注意事项
1. this 绑定问题
javascript
const obj = {
name: 'John',
friends: ['Jane', 'Bob'],
showFriends() {
// ❌ this 指向错误
this.friends.forEach(function(friend) {
console.log(`${this.name}'s friend: ${friend}`) // this.name 为 undefined
})
// ✅ 使用箭头函数
this.friends.forEach(friend => {
console.log(`${this.name}'s friend: ${friend}`)
})
// ✅ 使用 bind
this.friends.forEach(function(friend) {
console.log(`${this.name}'s friend: ${friend}`)
}.bind(this))
}
}2. 副作用问题
javascript
// ❌ 修改原数组
const numbers = [1, 2, 3, 4, 5]
const doubled = numbers.map(n => {
numbers.push(n * 2) // 副作用!
return n * 2
})
// ✅ 纯函数,无副作用
const numbers = [1, 2, 3, 4, 5]
const doubled = numbers.map(n => n * 2) // 不修改原数组3. 提前终止循环
javascript
const numbers = [1, 2, 3, 4, 5]
// ❌ forEach 无法提前终止
numbers.forEach(n => {
if (n === 3) return // 无效,不会终止循环
console.log(n)
})
// ✅ 使用 some 或 every
numbers.some(n => {
if (n === 3) return true // 终止循环
console.log(n)
return false
})
// ✅ 使用 for...of
for (const n of numbers) {
if (n === 3) break
console.log(n)
}4. 空数组和空值处理
javascript
// 空数组的情况
const empty = []
console.log(empty.map(n => n * 2)) // [](返回空数组)
console.log(empty.reduce((a, b) => a + b, 0)) // 0(使用初始值)
console.log(empty.reduce((a, b) => a + b)) // TypeError!(无初始值)
// find 返回 undefined
const users = [{ id: 1, name: 'John' }]
const user = users.find(u => u.id === 999)
console.log(user.name) // TypeError: Cannot read property 'name' of undefined
// ✅ 安全访问
const user = users.find(u => u.id === 999)
console.log(user?.name) // undefined(可选链)实际应用案例
1. 数据转换管道
javascript
// 处理 API 响应数据
const apiResponse = [
{ id: 1, user_name: 'john', email: 'john@example.com', is_active: 1 },
{ id: 2, user_name: 'jane', email: 'jane@example.com', is_active: 0 },
{ id: 3, user_name: 'bob', email: 'bob@example.com', is_active: 1 }
]
const processUsers = pipe(
// 转换字段名
users => users.map(u => ({
id: u.id,
name: u.user_name,
email: u.email,
active: Boolean(u.is_active)
})),
// 过滤活跃用户
users => users.filter(u => u.active),
// 按名称排序
users => [...users].sort((a, b) => a.name.localeCompare(b.name))
)
console.log(processUsers(apiResponse))
// [
// { id: 3, name: 'bob', email: 'bob@example.com', active: true },
// { id: 1, name: 'john', email: 'john@example.com', active: true }
// ]2. 表单验证
javascript
// 使用高阶函数创建验证器
const validators = {
required: (fieldName) => (value) =>
!value ? `${fieldName} 是必填项` : null,
minLength: (fieldName, min) => (value) =>
value.length < min ? `${fieldName} 至少需要 ${min} 个字符` : null,
maxLength: (fieldName, max) => (value) =>
value.length > max ? `${fieldName} 最多 ${max} 个字符` : null,
email: (fieldName) => (value) =>
!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value) ? `${fieldName} 格式不正确` : null,
};
// 组合校验器:依次执行所有规则
function createValidator(rules) {
return (data) => {
const errors = {}
Object.entries(rules).forEach(([field, validator]) => {
const error = validator(data[field])
if (error) errors[field] = error
})
return errors
}
}
const validateUser = createValidator({
username: formValidators.minLength('用户名', 3),
email: formValidators.email('邮箱'),
password: formValidators.minLength('密码', 8),
});
// 使用示例
const errors = validateUser({ username: 'ab', email: 'bad-email', password: '123' })
// errors: {
// username: '用户名 至少需要 3 个字符',
// email: '邮箱 格式不正确',
// password: '密码 至少需要 8 个字符'
// }
// }3. 状态管理
javascript
// 使用高阶函数实现简单的状态管理
function createStore(reducer, initialState) {
let state = initialState
const listeners = []
return {
getState() {
return state
},
dispatch(action) {
state = reducer(state, action)
// 通知所有订阅者状态变化
listeners.forEach(listener => listener(state))
},
subscribe(listener) {
listeners.push(listener)
// 返回取消订阅函数
return () => {
const index = listeners.indexOf(listener)
if (index !== -1) listeners.splice(index, 1)
}
}
}
}
// 使用
const counterReducer = (state, action) => {
switch (action.type) {
case 'INCREMENT': return { ...state, count: state.count + 1 }
case 'DECREMENT': return { ...state, count: state.count - 1 }
default: return state
}
}
const store = createStore(counterReducer, { count: 0 })
store.subscribe(state => console.log('State:', state))
store.dispatch({ type: 'INCREMENT' }) // State: { count: 1 }
store.dispatch({ type: 'INCREMENT' }) // State: { count: 2 }
store.dispatch({ type: 'DECREMENT' }) // State: { count: 1 }4. 异步流程控制
javascript
// 并行执行
function parallel(tasks, callback) {
const results = []
let completed = 0
tasks.forEach((task, index) => {
task((error, result) => {
if (error) {
callback(error)
return
}
results[index] = result
completed++
// 所有任务完成后回调
if (completed === tasks.length) {
callback(null, results)
}
})
})
}
// 串行执行
function series(tasks, callback) {
const results = []
let index = 0
function next() {
if (index >= tasks.length) {
callback(null, results)
return
}
const current = index++
tasks[current]((error, result) => {
if (error) {
callback(error)
return
}
results.push(result)
next()
})
}
next()
}
// 使用
const tasks = [
(cb) => setTimeout(() => cb(null, 'Task 1'), 100),
(cb) => setTimeout(() => cb(null, 'Task 2'), 50),
(cb) => setTimeout(() => cb(null, 'Task 3'), 150)
]
parallel(tasks, (err, results) => {
console.log('Parallel:', results) // ['Task 1', 'Task 2', 'Task 3']
})
series(tasks, (err, results) => {
console.log('Series:', results) // ['Task 1', 'Task 2', 'Task 3']
})最佳实践
1. 保持函数纯度
javascript
// ✅ 纯函数:相同输入总是返回相同输出,无副作用
const double = x => x * 2
const add = (a, b) => a + b
// ❌ 不纯函数:依赖外部状态或产生副作用
let total = 0
const addToTotal = x => {
total += x
return total
}2. 使用有意义的命名
javascript
// ❌ 不清晰的命名
const x = users.filter(u => u.a)
// ✅ 清晰的命名
const activeUsers = users.filter(user => user.isActive)3. 避免嵌套过深
javascript
// ❌ 嵌套过深
users
.filter(u => u.active)
.map(u => u.orders)
.reduce((acc, orders) => [...acc, ...orders], [])
.filter(o => o.status === 'completed')
.map(o => o.items)
.reduce((acc, items) => [...acc, ...items], [])
// ✅ 使用组合函数
const getActiveUserOrders = pipe(
users => users.filter(u => u.active),
users => users.flatMap(u => u.orders),
orders => orders.filter(o => o.status === 'completed'),
orders => orders.flatMap(o => o.items)
)4. 合理使用性能优化
javascript
// 对于小型数组,优先考虑可读性
const result = users
.filter(u => u.active)
.map(u => u.name)
// 对于大型数组,考虑性能优化
const result = users.reduce((acc, u) => {
if (u.active) {
acc.push(u.name)
}
return acc
}, [])常见问题
Q1: map 和 forEach 的区别?
A: 主要区别在于返回值:
map返回新数组,适合转换数据forEach无返回值,适合执行副作用操作
javascript
const numbers = [1, 2, 3]
// map:返回新数组
const doubled = numbers.map(n => n * 2) // [2, 4, 6]
// forEach:无返回值
numbers.forEach(n => console.log(n)) // undefinedQ2: reduce 的初始值重要吗?
A: 非常重要!不提供初始值可能导致错误。
javascript
// ✅ 提供初始值(推荐)
[].reduce((a, b) => a + b, 0) // 0
// ❌ 不提供初始值(空数组会报错)
[].reduce((a, b) => a + b) // TypeError
// 对象累积必须提供初始值
users.reduce((map, user) => {
map[user.id] = user
return map
}, {}) // 必须提供初始值 {}Q3: 柯里化和偏函数的区别?
A:
- 柯里化:将多参数函数转换为一系列单参数函数,每次只接受一个参数
- 偏函数:固定函数的部分参数,返回接受剩余参数的新函数
javascript
// 柯里化:每次一个参数
curried(1)(2)(3)
// 偏函数:可以固定多个参数
partial(1, 2)(3)Q4: 什么时候使用防抖,什么时候使用节流?
A:
- 防抖:适合只在最后一次操作后执行的场景(搜索框、表单验证)
- 节流:适合需要均匀执行的场景(滚动加载、鼠标跟踪)
Q5: 如何处理高阶函数中的错误?
A: 高阶函数本身不提供错误处理,需要在回调函数中处理。
javascript
// ✅ 在回调中处理错误
users.map(user => {
try {
return processUser(user)
} catch (error) {
console.error('Error processing user:', user.id, error)
return { ...user, error: true }
}
})
// ✅ 使用 flatMap 过滤错误结果
users.flatMap(user => {
try {
const processed = processUser(user)
return [processed]
} catch (error) {
console.error('Error:', error)
return [] // 返回空数组,相当于过滤掉
}
})总结
核心要点
- 高阶函数定义:接受函数作为参数或返回函数的函数
- 常见高阶函数:
map、filter、reduce、forEach、find、some、every、sort、flatMap - 高级技术:柯里化、偏函数、记忆化、函数组合、节流防抖
- 核心优势:代码复用、声明式编程、关注点分离、可测试性
最佳实践清单
✅ 保持函数纯度,避免副作用
✅ 使用有意义的命名
✅ 合理使用链式调用,注意性能
✅ 总是提供 reduce 的初始值
✅ 使用箭头函数避免 this 问题
✅ 对于大型数据集,考虑单次遍历优化
✅ 使用记忆化优化重复计算
✅ 合理使用防抖和节流优化性能
性能优化建议
| 场景 | 建议 |
|---|---|
| 小型数组 | 优先考虑可读性,使用链式调用 |
| 大型数组 | 减少遍历次数,使用 reduce 单次处理 |
| 频繁调用 | 使用记忆化缓存结果 |
| DOM 操作 | 使用节流防抖减少调用频率 |
| React 渲染 | 避免在渲染中创建新函数 |