一、为什么函数式编程能解决咱们的开发痛点
不知道你有没有遇到过这种情况:改了一个不起眼的工具函数,结果整个列表页面的数据全乱了,查了半天才发现那个函数偷偷改了原来的数组;或者业务逻辑写得越来越乱,重复代码堆得像小山,改的时候怕牵一发而动全身?今天咱们就聊聊JavaScript里能解决这些糟心事的两个核心——纯函数和高阶函数,用它们优化代码,让维护变得更省心。
二、纯函数:做一个“只看输入给输出”的靠谱函数
2.1 什么是纯函数?
用生活化的例子来说,纯函数就像你去固定门店点奶茶:只要你选了大杯加珍珠,不管谁去点,出来的永远是一模一样的大杯加珍珠,门店不会偷偷换配料,也不会给你多放糖。换成代码里的定义,就是同一个输入永远得到同一个输出,不会修改输入之外的任何东西(没有副作用)。
2.2 纯函数的对比示例
先写一个“不靠谱的不纯函数”,它会偷偷修改外部状态和输入参数:
// 不纯的函数:依赖外部变量,修改输入数组,结果不稳定
let tempStorage = []; // 外部状态,函数偷偷用它
function countNumbersImpure(arr) {
// 偷偷把元素存到外部数组,污染全局状态
arr.forEach(item => tempStorage.push(item));
const result = {};
arr.forEach(item => result[item] = (result[item] || 0) + 1);
// 偷偷修改传入的原数组,破坏原始数据
arr.length = 0;
return result;
}
// 测试:第一次调用正常,原数组被改空,第二次就崩溃了
const testArr = [1,2,2,3];
console.log(countNumbersImpure(testArr)); // {1:1,2:2,3:1}
console.log(testArr); // [] 原数组被篡改
console.log(countNumbersImpure(testArr)); // {} 因为原数组已空
再写“靠谱的纯函数”,完全不碰外部和输入,只靠参数输出:
// 纯函数:仅依赖输入参数,不修改外部/输入,结果稳定可复用
function countNumbersPure(arr) {
const result = {};
// 只遍历输入,绝不修改原数组
arr.forEach(item => result[item] = (result[item] || 0) + 1);
return result;
}
// 测试:原数组完好,多次调用结果完全一致
const testArr2 = [1,2,2,3];
console.log(countNumbersPure(testArr2)); // {1:1,2:2,3:1}
console.log(testArr2); // [1,2,2,3] 原数据未被篡改
console.log(countNumbersPure(testArr2)); // {1:1,2:2,3:1} 结果稳定
2.3 纯函数的核心好处
纯函数最大的优势是“可预测性”:你不用关心函数内部做了什么小动作,只要给对参数,输出一定正确。测试的时候只需要传输入、核对输出,不用搭复杂的测试环境;代码复用的时候也不用担心“这个函数会不会改我的数据”,因为它只动自己的局部变量。
三、高阶函数:给函数装个“灵活开关”,减少重复代码
3.1 什么是高阶函数?
简单说,能把函数当参数传递,或者返回一个新函数的函数。咱们日常用的数组map、filter、reduce都是高阶函数,它们把“具体操作逻辑”抽成参数,让你不用重复写循环代码。比如你要给数组元素加1、乘以2、转成价格,不用写三个独立的循环,只需要把不同的操作逻辑传给同一个高阶函数就行。
3.2 自定义高阶函数示例
写一个通用的数组处理高阶函数,把循环逻辑和具体操作分开:
// 高阶函数:接收数组和处理函数,返回新数组(不修改原数组)
function processArray(arr, handler) {
const result = [];
arr.forEach(item => {
// 只负责遍历,把处理逻辑交给传入的handler函数
result.push(handler(item));
});
return result;
}
// 用这个高阶函数做不同的业务操作,无需重复写循环
const numbers = [1,2,3,4];
// 操作1:每个元素加5
const addFiveList = processArray(numbers, num => num +5);
console.log(addFiveList); // [6,7,8,9]
// 操作2:每个元素乘以3
const multiplyList = processArray(numbers, num => num *3);
console.log(multiplyList); // [3,6,9,12]
// 操作3:转成价格格式
const priceList = processArray(numbers, num => `${num}元`);
console.log(priceList); // ['1元','2元','3元','4元']
3.3 高阶函数和纯函数的完美配合
刚才传给高阶函数的num => num+5就是纯函数,它只输入输出,没有副作用。把纯函数当参数传给高阶函数,能组合出更复杂的业务逻辑,而且整个流程没有“暗地操作”,代码可维护性直接拉满。
四、实际场景:用户列表处理的优化对比
咱们用日常开发最常见的用户列表处理场景对比,看看函数式写法的优势。先给模拟的用户数据:
// 模拟用户数据:姓名、积分、活跃天数
const users = [
{ name: '张三', score: 100, activeDays: 15 },
{ name: '李四', score: 80, activeDays: 3 },
{ name: '王五', score: 120, activeDays: 20 },
{ name: '赵六', score: 90, activeDays: 6 }
];
普通写法的痛点
普通写法容易写乱,重复代码多,还可能有副作用:
// 普通写法:三个独立循环,重复度高,逻辑分散
function processUsersNormal(userList) {
const activeUsers = [];
// 过滤活跃用户(7天以上)
for(let i=0; i<userList.length; i++){
if(userList[i].activeDays >=7) activeUsers.push(userList[i]);
}
// 提取姓名
const names = [];
for(let i=0; i<activeUsers.length; i++) names.push(activeUsers[i].name);
// 计算总积分
let totalScore =0;
for(let i=0; i<activeUsers.length; i++) totalScore += activeUsers[i].score;
return { names, totalScore };
}
console.log(processUsersNormal(users)); // {names: ['张三','王五'], totalScore:220}
函数式写法的优化
用纯函数拆分小逻辑,高阶函数(filter、map、reduce)组合逻辑,代码简洁且可维护:
// 纯函数:过滤活跃用户、提取姓名、计算积分的小逻辑
const isActive = user => user.activeDays >=7;
const getUserName = user => user.name;
const sumScore = (total, user) => total + user.score;
// 高阶函数组合:逻辑清晰,修改时只改小函数
function processUsersFunctional(userList) {
return {
names: userList.filter(isActive).map(getUserName),
totalScore: userList.filter(isActive).reduce(sumScore, 0)
};
}
console.log(processUsersFunctional(users)); // {names: ['张三','王五'], totalScore:220}
对比下来,函数式写法把每个小功能都拆成纯函数,单独测试isActive的时候,只需要传一个用户参数就能验证对错;如果要改过滤条件(比如把7天改成10天),只需要改isActive里的数字,不用碰其他代码,完全不会影响其他逻辑。
五、技术优缺点和注意事项
5.1 优点
纯函数:1. 可预测性拉满,排错时不用怀疑函数内部状态,不会出现“改了A影响B”的隐性bug;2. 易测试,单元测试只需要输入输出,不用 mock 外部环境;3. 并行安全,多个纯函数可以同时运行,不会互相干扰,适合前端批量数据处理或Node多进程场景。 高阶函数:1. 减少重复代码,把公共循环逻辑抽成高阶函数,业务逻辑只需要写小函数;2. 逻辑拆分清晰,每个小函数只做一件事,组合起来完成复杂功能;3. 易维护,改公共逻辑只改一个地方,不会牵一发而动全身。
5.2 缺点
纯函数:1. 复杂场景下需要传很多参数,看起来可能有点啰嗦;2. 有副作用的场景(比如接口请求、DOM操作)没法硬套,需要和副作用代码分开处理。
高阶函数:1. 过度嵌套会导致可读性下降,新手可能看不懂多层filter+map的组合;2. 简单场景硬套会导致抽象过度,比如只有一行的循环,用高阶函数反而不如普通循环直观。
5.3 注意事项
- 不要为了用函数式而用,简单逻辑不用硬套,适合的才是最好的;2. 纯函数绝对不能修改外部状态,哪怕看起来方便也不能碰全局变量或输入参数;3. 高阶函数不要滥用,公共逻辑抽出来是为了减少重复,不是为了堆抽象;4. 副作用代码(如接口请求)和纯函数分开,纯函数只处理数据转换,副作用放在外层,这样数据逻辑稳定,副作用可控。
六、总结
其实函数式编程不是什么高大上的概念,不用背太多术语,只要抓住两个核心:纯函数只管输入输出,不搞小动作;高阶函数把公共逻辑当工具,灵活组合。平时写代码的时候,尽量写纯函数,把每个小功能做成可复用、可测试的工具;遇到重复的循环逻辑,就看看能不能抽成高阶函数。这样写出来的代码,改的时候不容易崩,维护的时候省时间,哪怕过了几个月再看,也能一眼看懂逻辑,不用再翻来覆去排查bug。
评论
围绕“JavaScript函数式编程应用:利用纯函数与高阶函数优化代码可维护性”参与讨论