JavaScript高阶函数
1. 背景介绍
高阶函数是函数式编程的核心概念之一,在JavaScript中得到了广泛应用。高阶函数是指可以接收函数作为参数和/或返回函数作为结果的函数。它们为代码重用、抽象和组合提供了强大的工具。
JavaScript作为一种多范式语言,天生支持函数式编程风格。随着ES6及后续版本的发布,JavaScript的函数式编程能力得到了进一步增强,使得高阶函数的使用更加便捷和普遍。
2. 高阶函数的功能
高阶函数主要有以下几个功能:
- 抽象和封装通用的操作逻辑
- 实现函数组合
- 创建特定的函数工厂
- 实现延迟计算和惰性求值
- 增强函数的功能(如缓存、节流等)
3. 使用场景
高阶函数在很多场景下都有应用,主要包括:
- 数组操作(如map, filter, reduce等)
- 事件处理(如防抖、节流)
- 异步编程(如Promise链式调用)
- 函数式编程(如柯里化、组合)
- 中间件模式(如Express中间件)
- 装饰器模式
- 依赖注入
4. 详细示例
4.1 基础数组操作
const numbers = [1, 2, 3, 4, 5];
// 使用map进行数组转换
const doubled = numbers.map(num => num * 2);
console.log(doubled); // [2, 4, 6, 8, 10]
// 使用filter进行数组过滤
const evenNumbers = numbers.filter(num => num % 2 === 0);
console.log(evenNumbers); // [2, 4]
// 使用reduce进行累加
const sum = numbers.reduce((acc, cur) => acc + cur, 0);
console.log(sum); // 15
4.2 高级数组操作
const products = [
{ name: 'Laptop', price: 1000, category: 'Electronics' },
{ name: 'Book', price: 20, category: 'Books' },
{ name: 'Phone', price: 500, category: 'Electronics' },
{ name: 'Desk', price: 300, category: 'Furniture' },
];
// 找出所有电子产品的平均价格
const avgElectronicsPrice = products
.filter(product => product.category === 'Electronics')
.map(product => product.price)
.reduce((sum, price, _, array) => sum + price / array.length, 0);
console.log(avgElectronicsPrice); // 750
4.3 函数组合
const compose = (...fns) => x => fns.reduceRight((v, f) => f(v), x);
const addOne = x => x + 1;
const double = x => x * 2;
const square = x => x * x;
const compute = compose(square, double, addOne);
console.log(compute(3)); // 64
4.4 柯里化
const curry = (fn) => {
return function curried(...args) {
if (args.length >= fn.length) {
return fn.apply(this, args);
} else {
return function(...args2) {
return curried.apply(this, args.concat(args2));
}
}
};
}
const add = (a, b, c) => a + b + c;
const curriedAdd = curry(add);
console.log(curriedAdd(1)(2)(3)); // 6
console.log(curriedAdd(1, 2)(3)); // 6
console.log(curriedAdd(1, 2, 3)); // 6
4.5 函数记忆化 (Memoization)
function memoize(fn) {
const cache = new Map();
return function (...args) {
const key = JSON.stringify(args);
if (cache.has(key)) {
return cache.get(key);
}
const result = fn.apply(this, args);
cache.set(key, result);
return result;
};
}
// 使用记忆化优化斐波那契数列计算
function fibonacci(n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
const memoizedFibonacci = memoize(fibonacci);
console.time('Without memoization');
console.log(fibonacci(40));
console.timeEnd('Without memoization');
console.time('With memoization');
console.log(memoizedFibonacci(40));
console.timeEnd('With memoization');
4.6 函数组合与管道
// 函数组合 (从右到左)
const compose = (...fns) => x => fns.reduceRight((v, f) => f(v), x);
// 管道 (从左到右)
const pipe = (...fns) => x => fns.reduce((v, f) => f(v), x);
// 示例函数
const addTax = (rate) => (price) => price * (1 + rate);
const formatPrice = (price) => `$${price.toFixed(2)}`;
const discount = (rate) => (price) => price * (1 - rate);
// 使用组合
const calculateTotalCompose = compose(
formatPrice,
addTax(0.1),
discount(0.2)
);
// 使用管道
const calculateTotalPipe = pipe(
discount(0.2),
addTax(0.1),
formatPrice
);
console.log(calculateTotalCompose(100)); // "$88.00"
console.log(calculateTotalPipe(100)); // "$88.00"
4.7 部分应用 (Partial Application)
function partial(fn, ...args) {
return function(...moreArgs) {
return fn(...args, ...moreArgs);
}
}
function greet(greeting, name) {
return `${greeting}, ${name}!`;
}
const sayHelloTo = partial(greet, "Hello");
console.log(sayHelloTo("John")); // "Hello, John!"
const sayGoodMorningTo = partial(greet, "Good morning");
console.log(sayGoodMorningTo("Alice")); // "Good morning, Alice!"
4.8 函数节流 (Throttle)
function throttle(fn, limit) {
let inThrottle;
return function(...args) {
const context = this;
if (!inThrottle) {
fn.apply(context, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
}
}
// 使用示例
const expensiveOperation = () => console.log('Expensive operation');
const throttledOperation = throttle(expensiveOperation, 1000);
// 模拟频繁调用
setInterval(throttledOperation, 100);
// 防抖
function debounce(func, delay) {
let timer;
return function (...args) {
clearTimeout(timer);
timer = setTimeout(() => func.apply(this, args), delay);
};
}
// 示例:用户停止输入 500ms 后触发搜索
const searchInput = document.getElementById('search');
searchInput.addEventListener('input', debounce(() => {
console.log('Search triggered');
}, 500));
4.9 异步操作的组合
// 串行执行Promise
function serialPromises(promises) {
return promises.reduce((chain, promise) =>
chain.then(results =>
promise().then(result => [...results, result])
),
Promise.resolve([])
);
}
// 模拟异步操作
const asyncOperation = (id) => () =>
new Promise(resolve => setTimeout(() => resolve(`Operation ${id} complete`), 1000));
const operations = [
asyncOperation(1),
asyncOperation(2),
asyncOperation(3)
];
serialPromises(operations).then(results => console.log(results));
// 大约3秒后输出:
// ["Operation 1 complete", "Operation 2 complete", "Operation 3 complete"]
防抖和节流
防抖是在事件结束后多久之后执行一次,有可能永远都不会触发,
防抖是指在事件被触发 n 秒后再执行回调,如果在这 n 秒内事件又被触发,则重新计时。
- 搜索框输入,只在用户停止输入一段时间后才发送请求
- 窗口调整,只在用户停止调整一段时间后才重新计算布局
function debounce (func,wait) {
let timer = null
return function (...args) {
clearTimeout(timer)
timer = setTimeout(()=> {
func(this,args)
},wait)
}
}
节流是在事件一直执行中多久去执行一次,节流是指在一定时间内,只允许函数执行一次。
- 当触发事件时,设置一个标志
- 在一定时间内,如果再次触发事件,则忽略
- 到达设定的时间后,执行函数并重置标志
- 适用场景:
- 滚动事件监听
- 频繁点击按钮
- 游戏中的移动事件