{T}

高阶函数

高阶函数(Higher-Order Function)是指接受函数作为参数,或者返回函数的函数。高阶函数是函数式编程的核心概念,也是 JavaScript 中最常见的编程模式之一。

概念

定义

高阶函数满足以下条件之一:

  1. 接受一个或多个函数作为参数
  2. 返回一个函数
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
  }
}

为什么需要高阶函数?

优势说明示例
抽象性隐藏实现细节,关注"做什么"而非"怎么做"mapfilter
复用性通过组合和柯里化实现代码复用函数工厂模式
可测试性小函数易于测试,纯函数无副作用单元测试
可维护性声明式代码更易理解和维护链式调用

常见数组高阶函数

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))  // 2

5. 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 返回 undefinedfindIndex 返回 -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:至少有一个满足条件返回 true
  • every:所有元素都满足条件返回 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)  // false

8. 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) -> 2

pipe(从左到右)

作用:将多个函数组合成一个函数,从左到右执行(更符合直觉)。

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))  // undefined

Q2: 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 []  // 返回空数组,相当于过滤掉
  }
})

总结

核心要点

  • 高阶函数定义:接受函数作为参数或返回函数的函数
  • 常见高阶函数mapfilterreduceforEachfindsomeeverysortflatMap
  • 高级技术:柯里化、偏函数、记忆化、函数组合、节流防抖
  • 核心优势:代码复用、声明式编程、关注点分离、可测试性

最佳实践清单

✅ 保持函数纯度,避免副作用
✅ 使用有意义的命名
✅ 合理使用链式调用,注意性能
✅ 总是提供 reduce 的初始值
✅ 使用箭头函数避免 this 问题
✅ 对于大型数据集,考虑单次遍历优化
✅ 使用记忆化优化重复计算
✅ 合理使用防抖和节流优化性能

性能优化建议

场景建议
小型数组优先考虑可读性,使用链式调用
大型数组减少遍历次数,使用 reduce 单次处理
频繁调用使用记忆化缓存结果
DOM 操作使用节流防抖减少调用频率
React 渲染避免在渲染中创建新函数

参考资料