← JavaScript回调函数 JavaScript setTimeout() →

JavaScript高阶函数

原创 2026-03-14 JavaScript 已有人查阅

JavaScript高阶函数指南:从基础到实战的深度解析

一、高阶函数到底是什么

说实话,我刚接触这个概念时也觉得有点绕。高阶函数就是满足以下两个条件之一的函数

  1. 接收一个或多个函数作为参数

  2. 返回一个函数作为结果

听起来抽象?看个生活中的例子:你去餐厅吃饭,点餐(调用主函数)时告诉服务员"少放辣"(传入一个函数),后厨做菜时就会根据这个要求调整(回调函数被执行)。这里的点餐过程就像一个高阶函数。

// 一个简单的高阶函数
function orderFood(menuItem, cookingStyle) {
    console.log(`点了一份:${menuItem}`);
    // cookingStyle是一个函数,它决定怎么做这道菜
    let dish = cookingStyle(menuItem);
    return dish;
}

// 传入选定做法
function spicyCook(item) {
    return `特辣版${item},加麻加辣`;
}

function lightCook(item) {
    return `清淡版${item},少油少盐`;
}

console.log(orderFood('水煮鱼', spicyCook));
console.log(orderFood('水煮鱼', lightCook));

二、JavaScript内置的高阶函数

项目开发中,我们天天都在用高阶函数,只是没意识到。数组的map、filter、reduce就是最典型的高阶函数。

1. map函数:数据转换的利器

map接收一个转换函数,对数组每个元素进行处理,返回新数组。

// 学生成绩处理场景
const students = [
    { name: '张明', score: 78, class: '三年一班' },
    { name: '李华', score: 92, class: '三年二班' },
    { name: '王芳', score: 85, class: '三年一班' },
    { name: '赵磊', score: 63, class: '三年二班' }
];

// 生成成绩单卡片
const reportCards = students.map(student => ({
    studentName: student.name,
    grade: student.score >= 60  '及格' : '不及格',
    level: student.score >= 90  '优秀' : 
           student.score >= 75  '良好' : 
           student.score >= 60  '中等' : '待提高',
    classInfo: student.class
}));

console.log('成绩单:', reportCards);

// 提取所有学生姓名
const names = students.map(s => s.name);
console.log('学生名单:', names); // ['张明', '李华', '王芳', '赵磊']

2. filter函数:数据筛选的专家

filter接收一个判断函数,返回满足条件的元素组成的新数组。

const products = [
    { name: '笔记本电脑', price: 5999, stock: 15, category: '电子产品' },
    { name: '机械键盘', price: 399, stock: 3, category: '外设' },
    { name: '鼠标', price: 89, stock: 0, category: '外设' },
    { name: '显示器', price: 1899, stock: 8, category: '电子产品' },
    { name: '耳机', price: 299, stock: 0, category: '音频' }
];

// 筛选有货的商品
const inStock = products.filter(p => p.stock > 0);
console.log('有货商品:', inStock.map(p => p.name));

// 筛选价格低于500的商品
const affordable = products.filter(p => p.price < 500);
console.log('实惠商品:', affordable.map(p => `${p.name} ¥${p.price}`));

// 筛选需要补货的商品(库存小于5且价格高于200)
const needRestock = products.filter(p => p.stock < 5 && p.price > 200);
console.log('需要补货:', needRestock.map(p => p.name));

3. reduce函数:数据聚合的

reduce接收一个累加器函数,将数组归约为单个值。

const salesData = [
    { product: 'T恤', amount: 299, quantity: 5 },
    { product: '牛仔裤', amount: 599, quantity: 3 },
    { product: '外套', amount: 899, quantity: 2 },
    { product: '鞋子', amount: 499, quantity: 4 }
];

// 计算总销售额
const totalRevenue = salesData.reduce((sum, item) => {
    return sum + (item.amount * item.quantity);
}, 0);
console.log('总销售额:', totalRevenue.toFixed(2), '元');

// 统计销售数量和金额
const statistics = salesData.reduce((stats, item) => {
    stats.totalQuantity += item.quantity;
    stats.totalAmount += item.amount * item.quantity;
    stats.averagePrice = stats.totalAmount / stats.totalQuantity;
    return stats;
}, { totalQuantity: 0, totalAmount: 0, averagePrice: 0 });

console.log('销售统计:', statistics);

// 按价格区间分组
const groupedByPrice = salesData.reduce((groups, item) => {
    let priceRange;
    if (item.amount < 300) priceRange = '平价';
    else if (item.amount < 600) priceRange = '中等';
    else priceRange = '高端';
    
    if (!groups[priceRange]) {
        groups[priceRange] = [];
    }
    groups[priceRange].push(item.product);
    return groups;
}, {});

console.log('价格分组:', groupedByPrice);

三、高阶函数的进阶用法

1. 函数组合(Composition)

函数组合让我能把多个小功能拼装成更复杂的功能,就像搭积木一样。

// 基础函数
const addTax = price => price * 1.1; // 加10%税
const discount = price => price * 0.8; // 打8折
const formatPrice = price => `¥${price.toFixed(2)}`;

// 组合函数
function compose(...functions) {
    return function(initialValue) {
        return functions.reduceRight((value, fn) => fn(value), initialValue);
    };
}

// 创建一个计算最终价格的函数
const calculateFinalPrice = compose(formatPrice, addTax, discount);

console.log(calculateFinalPrice(100)); // 原价100,打8折后80,加税后88,输出:¥88.00

// 用更现在的方式写组合
const pipe = (...fns) => x => fns.reduce((v, f) => f(v), x);
const calculatePricePipe = pipe(discount, addTax, formatPrice);

console.log(calculatePricePipe(100)); // 同样输出:¥88.00

2. 柯里化(Currying)

柯里化是我特别喜欢的技巧,它能把多参数函数变成一系列单参数函数,让函数复用变得特别灵活。

// 普通的加法函数
function regularAdd(a, b, c) {
    return a + b + c;
}

// 柯里化版本
const curriedAdd = a => b => c => a + b + c;

console.log(curriedAdd(5)(3)(2)); // 10

// 实际应用:创建折扣计算器
const createDiscount = (discountRate) => (price) => price * (1 - discountRate);

const studentDiscount = createDiscount(0.1); // 学生价9折
const vipDiscount = createDiscount(0.2);     // VIP8折
const employeeDiscount = createDiscount(0.3); // 员工7折

console.log('学生价:', studentDiscount(200)); // 180
console.log('VIP价:', vipDiscount(200));      // 160
console.log('员工价:', employeeDiscount(200)); // 140

// 更复杂的柯里化:运费计算器
const calculateShipping = (baseRate) => (weight) => (distance) => {
    return baseRate * weight * distance;
};

const standardShipping = calculateShipping(0.5);
const expressShipping = calculateShipping(1.2);

// 计算5公斤货物运输100公里的运费
console.log('标准运费:', standardShipping(5)(100)); // 250
console.log('快递运费:', expressShipping(5)(100));  // 600

四、项目开发中的应用场景

1. 事件处理中的高阶函数

class SearchComponent {
    constructor(inputId) {
        this.input = document.getElementById(inputId);
        this.searchHistory = [];
        
        // 防抖函数:减少频繁触发
        this.debounce = (fn, delay) => {
            let timer = null;
            return (...args) => {
                clearTimeout(timer);
                timer = setTimeout(() => fn.apply(this, args), delay);
            };
        };
        
        // 节流函数:控制执行频率
        this.throttle = (fn, interval) => {
            let last = 0;
            return (...args) => {
                const now = Date.now();
                if (now - last >= interval) {
                    last = now;
                    fn.apply(this, args);
                }
            };
        };
        
        // 使用防抖处理输入
        this.input.addEventListener('input', this.debounce((e) => {
            this.performSearch(e.target.value);
        }, 300));
        
        // 使用节流处理滚动
        window.addEventListener('scroll', this.throttle(() => {
            this.checkScrollPosition();
        }, 200));
    }
    
    performSearch(keyword) {
        if (keyword.length > 1) {
            this.searchHistory.push({ keyword, time: new Date().toLocaleString() });
            console.log('搜索:', keyword);
            console.log('搜索历史:', this.searchHistory);
        }
    }
    
    checkScrollPosition() {
        const scrollY = window.scrollY;
        console.log('当前滚动位置:', scrollY);
    }
}

// 使用示例(假设有id为"searchBox"的输入框)
// const searchComp = new SearchComponent('searchBox');

2. 异步编程中的高阶函数

// 模拟API请求的高阶函数:添加重试机制
function withRetry(apiCall, maxRetries = 3) {
    return async function(...args) {
        let lastError;
        for (let i = 0; i < maxRetries; i++) {
            try {
                console.log(`尝试第${i + 1}次请求...`);
                const result = await apiCall(...args);
                console.log(`第${i + 1}次请求成功`);
                return result;
            } catch (error) {
                console.log(`第${i + 1}次请求失败:`, error.message);
                lastError = error;
                if (i < maxRetries - 1) {
                    // 等待后重试
                    await new Promise(r => setTimeout(r, 1000 * (i + 1)));
                }
            }
        }
        throw lastError;
    };
}

// 模拟一个有时会失败的API
async function fetchUserData(userId) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            if (Math.random() > 0.6) { // 40%概率失败
                resolve({ id: userId, name: '张三', age: 28 });
            } else {
                reject(new Error('网络不稳定'));
            }
        }, 500);
    });
}

// 使用重试机制
const fetchWithRetry = withRetry(fetchUserData, 3);

// 调用
fetchWithRetry(1001)
    .then(user => console.log('最终获取的用户:', user))
    .catch(err => console.log('所有重试都失败:', err.message));

3. 数据验证和处理管道

// 创建数据处理管道
function createDataPipeline(...processors) {
    return function(data) {
        return processors.reduce((currentData, processor) => {
            try {
                return processor(currentData);
            } catch (error) {
                console.error('处理步骤出错:', error.message);
                return null;
            }
        }, data);
    };
}

// 各个处理步骤
function validateInput(data) {
    if (!data || typeof data !== 'object') {
        throw new Error('输入数据必须是对象');
    }
    if (!data.name || !data.age) {
        throw new Error('缺少必要字段');
    }
    return data;
}

function formatName(data) {
    return {
        ...data,
        name: data.name.trim().toUpperCase(),
        displayName: `${data.name}(${data.age}岁)`
    };
}

function validateAge(data) {
    if (data.age < 0 || data.age > 150) {
        throw new Error('年龄范围无效');
    }
    data.ageGroup = data.age < 18  '未成年' : 
                    data.age < 60  '成年' : '老年';
    return data;
}

function addTimestamp(data) {
    return {
        ...data,
        processedAt: new Date().toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai' })
    };
}

// 创建完整的处理管道
const processUserData = createDataPipeline(
    validateInput,
    formatName,
    validateAge,
    addTimestamp
);

// 测试数据
const userInput = { name: ' 李四 ', age: 25 };
const processed = processUserData(userInput);
console.log('处理结果:', processed);

五、高阶函数的优势与挑战

优势:为什么我偏爱高阶函数

// 1. 代码复用:一次定义,多处使用
const formatCurrency = amount => `¥${amount.toFixed(2)}`;
const formatPercentage = value => `${(value * 100).toFixed(1)}%`;

const prices = [1999, 2999, 3999];
const rates = [0.1, 0.2, 0.3];

console.log(prices.map(formatCurrency)); // ['¥1999.00', '¥2999.00', '¥3999.00']
console.log(rates.map(formatPercentage)); // ['10.0%', '20.0%', '30.0%']

// 2. 逻辑抽象:隐藏实现细节
function withTiming(fn) {
    return function(...args) {
        const start = performance.now();
        const result = fn.apply(this, args);
        const end = performance.now();
        console.log(`${fn.name}执行时间:${(end - start).toFixed(2)}ms`);
        return result;
    };
}

const slowFunction = (n) => {
    let sum = 0;
    for (let i = 0; i < n * 1000000; i++) {
        sum += i;
    }
    return sum;
};

const timedSlowFunc = withTiming(slowFunction);
console.log('计算结果:', timedSlowFunc(10));

挑战:使用高阶函数时要注意什么

// 1. 避免过度抽象
// 不好的例子:不必要的抽象
const processArray = (arr, fn) => arr.map(fn);
const numbers = [1, 2, 3, 4];
const doubled = processArray(numbers, x => x * 2); // 何必呢?直接用numbers.map多好

// 好的做法:有价值的抽象
const applyDiscount = (price, discountType) => {
    const discounts = {
        student: p => p * 0.9,
        member: p => p * 0.85,
        vip: p => p * 0.8,
        none: p => p
    };
    return discounts[discountType].(price)  price;
};

// 2. 注意this指向
const handler = {
    name: '事件处理器',
    count: 0,
    
    // 错误:this会丢失
    badSetup: function() {
        document.addEventListener('click', function() {
            this.count++; // this指向document,不是handler
            console.log(this.name);
        });
    },
    
    // 正确:使用箭头函数或bind
    goodSetup: function() {
        document.addEventListener('click', (e) => {
            this.count++;
            console.log(`${this.name}被点击了${this.count}次`);
        });
    }
};

六、性能优化和实践

1. 缓存计算结果

// 记忆化高阶函数
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);
        }
        
        console.log('计算新结果');
        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 memoizedFib = memoize(fibonacci);

console.log(memoizedFib(40)); // 第一次计算,较慢
console.log(memoizedFib(40)); // 第二次,直接返回缓存结果

2. 避免不必要的函数创建

// 不好的做法:每次渲染都创建新函数
function BadComponent({ items }) {
    return (
        <ul>
            {items.map(item => (
                <li key={item.id} onClick={() => handleClick(item)}>
                    {item.name}
                </li>
            ))}
        </ul>
    );
}

// 好的做法:提取函数引用
function GoodComponent({ items }) {
    const handleItemClick = (item) => {
        console.log('点击了:', item.name);
    };
    
    return (
        <ul>
            {items.map(item => (
                <li key={item.id} onClick={() => handleItemClick(item)}>
                    {item.name}
                </li>
            ))}
        </ul>
    );
}

3. 链式调用的性能考虑

const largeArray = Array.from({ length: 1000000 }, (_, i) => i + 1);

// 链式调用会产生多个中间数组
console.time('chain');
const result1 = largeArray
    .filter(n => n % 2 === 0)
    .map(n => n * 2)
    .reduce((sum, n) => sum + n, 0);
console.timeEnd('chain');

// 单次遍历性能更好
console.time('single');
let result2 = 0;
for (let i = 0; i < largeArray.length; i++) {
    if (largeArray[i] % 2 === 0) {
        result2 += largeArray[i] * 2;
    }
}
console.timeEnd('single');

// 用reduce实现单次遍历
console.time('reduce');
const result3 = largeArray.reduce((sum, n) => {
    if (n % 2 === 0) {
        return sum + n * 2;
    }
    return sum;
}, 0);
console.timeEnd('reduce');

七、面试常见问题

// 1. 实现一个compose函数
function compose(...fns) {
    return function(x) {
        return fns.reduceRight((acc, fn) => fn(acc), x);
    };
}

// 2. 实现一个pipe函数
function pipe(...fns) {
    return function(x) {
        return fns.reduce((acc, fn) => fn(acc), x);
    };
}

// 3. 实现一个once函数(函数只执行一次)
function once(fn) {
    let called = false;
    let result;
    
    return function(...args) {
        if (!called) {
            called = true;
            result = fn.apply(this, args);
        }
        return result;
    };
}

const initialize = once(() => {
    console.log('初始化只执行一次');
    return { status: 'ready' };
});

console.log(initialize());
console.log(initialize()); // 第二次调用不执行函数体

八、给代码号学习编程的建议

学习高阶函数可以按照这个路径循序渐进:

  1. 先会用内置的高阶函数:map、filter、reduce要熟练

  2. 理解函数是一等公民:函数可以赋值给变量,可以传来传去

  3. 尝试写简单的高阶函数:比如写个debounce、throttle

  4. 掌握组合和柯里化:这两个概念能让你的代码水平上一个台阶

  5. 在项目中刻意练习:遇到循环先想想能不能用map/filter/reduce替代

// 练习:把下面的命令式代码改写成函数式
// 原始代码
let names = [];
for (let i = 0; i < students.length; i++) {
    if (students[i].score >= 60) {
        names.push(students[i].name);
    }
}

// 函数式改写
const passedNames = students
    .filter(s => s.score >= 60)
    .map(s => s.name);

高阶函数不是炫技的工具,而是让代码更清晰、更可维护的实用技术。用好它,你的代码会更有表现力,也更容易测试和调试。

记住:抽象的目的是简化,不是复杂化。如果用了高阶函数反而让代码更难懂,那就说明用得不对。在实践中慢慢体会,找到最适合自己项目的平衡点。

← JavaScript回调函数 JavaScript setTimeout() →
分享笔记 (共有 篇笔记)
验证码:
微信公众号