前端开发中的常用工具函数(五)

前端开发中的常用工具函数(五) 十二、reduce() 方法将数组元素累积计算为单个值reduce()方法对数组中的每个元素按序执行一个由您提供的reducer函数每一次运行会将先前元素的计算结果作为参数传入最后将其结果汇总为单个返回值。简单来说它的核心思想是“累积”将数组“压缩”成一个值这个值可以是数字、对象、数组甚至是另一个函数。1. 基本语法arr.reduce(callback(accumulator, currentValue[, index[, array]])[, initialValue])参数解析callback必须执行数组中每个元素的函数。accumulator(acc)累积器。它是上一次回调函数的返回值或者是初始值initialValue。这是reduce的核心。currentValue(cur)当前正在处理的元素。index(可选)当前元素的索引。array(可选)原数组。initialValue强烈建议作为第一次调用callback时的accumulator的初始值。返回值累积处理后的最终结果。2. 基础示例(1) 数值累加最经典示例计算数组中所有数字的总和。const numbers [1, 2, 3, 4]; const sum numbers.reduce((acc, cur) { return acc cur; }, 0); // 初始值设为 0 console.log(sum); // 输出: 10执行过程解析第1次acc 0 (初始值),cur 1 - 返回 1。第2次acc 1,cur 2 - 返回 3。第3次acc 3,cur 3 - 返回 6。第4次acc 6,cur 4 - 返回 10。(2) 求数组最大值const arr [10, 5, 100, 2]; const max arr.reduce((acc, cur) { return Math.max(acc, cur); }, arr[0]); // 初始值设为第一个元素 console.log(max); // 输出: 1003. 企业开发中的实际应用场景reduce的强大在于它的返回值可以是任意类型这使得它能处理非常复杂的业务逻辑。场景 1数组转对象在后端接口对接中有时需要将数组转换为以 ID 为 Key 的对象以便快速查找。const users [ { id: 101, name: Alice }, { id: 102, name: Bob } ]; // 目标: { 101: { id: 101, name: Alice }, 102: { ... } } const userMap users.reduce((acc, user) { acc[user.id] user; // 以 id 为键对象为值 return acc; // 必须返回 acc }, {}); console.log(userMap[101].name); // Alice (查找速度 O(1))场景 2数组扁平化将多维数组转化为一维数组虽然现在有flat()但reduce实现能体现逻辑。const nestedArr [[1, 2], [3, 4], [5]]; const flatArr nestedArr.reduce((acc, cur) { return acc.concat(cur); }, []); console.log(flatArr); // 输出: [1, 2, 3, 4, 5]场景 3数据统计与分组将数组中的元素按类别分组例如按角色分组用户。const people [ { name: Alice, role: admin }, { name: Bob, role: user }, { name: Charlie, role: admin } ]; const groupByRole people.reduce((acc, person) { const key person.role; if (!acc[key]) { acc[key] []; } acc[key].push(person); return acc; }, {}); console.log(groupByRole); // 输出: // { // admin: [{ name: Alice,... }, { name: Charlie,... }], // user: [{ name: Bob,... }] // }场景 4函数式编程管道在复杂的数据处理流程中组合多个函数。const process (input) input .split() .reverse() .join(); // 或者使用 reduce 组合函数 const compose (...fns) x fns.reduceRight((v, f) f(v), x);4. 注意事项与常见陷阱reduce虽然强大但也是最容易出错的数组方法之一。(1) 千万不要忘记return这是新手最容易犯的错误。reducer 函数必须返回新的累积值accumulator。如果你忘记写return下一次迭代的accumulator就会变成undefined。错误示范const sum arr.reduce((acc, cur) { acc cur; // 忘记 return结果将是 NaN }, 0);(2) 初始值initialValue的重要性虽然reduce允许不写初始值此时第一次迭代的acc是数组第一个元素cur是第二个元素但这极易引发 Bug。空数组报错如果数组为空且没有提供初始值reduce会抛出TypeError。逻辑混乱如果不提供初始值索引index会从 1 开始容易造成困惑。最佳实践始终为reduce提供第二个参数初始值哪怕它是0、{}或[]。(3) 纯函数与引用陷阱如果你累积器是对象或数组请注意不要直接修改累积器的引用除非你很清楚自己在做什么这可能会带来副作用。但在性能优化场景下原地修改累积器对象如acc[key] val是可以接受的因为它比深拷贝更高效。(4) 可读性权衡不要为了用reduce而用reduce。如果map、filter或forEach能更直观地解决问题就不要强行使用reduce。过度复杂的reduce会让代码变得晦涩难懂维护成本增加。 十三、toFixed() 方法保留小数位toFixed()是 Number 对象的一个原型方法。它使用定点表示法来格式化一个数值把数字转换为字符串并保留指定位数的小数。核心作用格式化数字的小数位数。自动进行四舍五入注意并非标准的四舍五入详见后文。返回字符串。1. 基本语法numObj.toFixed(digits)参数解析digits(可选)小数点后显示的位数。取值范围是 0 到 20包括 0 和 20。如果省略默认为 0。如果超出范围部分环境可能支持更大范围也可能抛出RangeError。返回值返回一个字符串代表该数字保留指定小数位后的形式。2. 基础示例(1) 基础保留小数const num 3.14159; console.log(num.toFixed(2)); // 输出: 3.14 console.log(num.toFixed(0)); // 输出: 3 console.log(num.toFixed(4)); // 输出: 3.1416 (末尾补 0)(2) 数据类型陷阱这是新手最容易踩的坑结果是字符串不是数字。const num 10.5; const result num.toFixed(2); console.log(result); // 输出: 10.50 console.log(typeof result); // 输出: string // 如果想变回数字进行计算需要转换 console.log(Number(result)); // 输出: 10.53. 企业开发中的实际应用场景场景 1电商价格展示补零后端返回的价格可能是整数100或两位小数99.9前端展示时通常要求统一格式100.00或99.90。const price 99.9; const displayPrice price.toFixed(2); // 模板中直接使用 console.log(¥${displayPrice}); // 输出: ¥99.90场景 2数据可视化图表标签在使用 ECharts 等图表库时tooltip 或 y 轴标签通常要求保留两位小数避免过长的小数挤占空间。const data [12.345, 67.891, 45.001]; const formattedData data.map(item item.toFixed(2)); // 输出: [12.35, 67.89, 45.00]场景 3输入框金额格式化在“金额输入框”失去焦点时自动格式化为标准金额格式。const handleBlur (e) { const value parseFloat(e.target.value); if (!isNaN(value)) { form.price value.toFixed(2); } };4. 注意事项与“银行家舍入”陷阱这是关于toFixed()最重要的部分也是产生“莫名其妙” Bug 的根源。(1) “四舍六入五成双”问题toFixed()使用的并不是我们小学数学里的“四舍五入”而是银行家舍入法。简单规则如下 5舍去。 5进位。 5分两种情况。如果 5 后面还有非 0 数字进位。如果 5 后面没有数字或全是 0则看 5 前一位奇数进位偶数舍去。诡异示例(1.35).toFixed(1); // 输出: 1.4 (5前面是3奇数进位) (1.45).toFixed(1); // 输出: 1.4 (5前面是4偶数舍去预期可能是1.5) (2.55).toFixed(1); // 输出: 2.5 (5前面是5奇数理应进位但浮点数精度导致结果为2.5) (2.555).toFixed(2); // 输出: 2.55 (有时候浮点数精度会干扰这个判断)解决方案如果你必须严格遵循“四舍五入”不要直接依赖toFixed。可以使用Math.round()配合运算// 封装一个严格的四舍五入函数 function roundFixed(num, decimals) { const factor Math.pow(10, decimals); const temp num * factor * 10; // *10是为了处理 .5 的情况 // 修正浮点数计算误差 const rounded Math.round(temp) / 10 / factor; return rounded.toFixed(decimals); } console.log(roundFixed(1.45, 1)); // 输出: 1.5或者使用第三方库如decimal.js、lodash来处理精度问题。(2) 返回值是字符串再次强调toFixed()返回字符串。如果你在计算总价时直接相加会发生字符串拼接。let total 0; const price 10.5; total price.toFixed(2); console.log(total); // 输出: 010.50 (字符串拼接)(3) 浮点数精度问题由于 JavaScript 采用 IEEE 754 标准浮点数运算本身就不精确。toFixed有时能掩盖问题有时会暴露问题。(0.335).toFixed(2); // 输出: 0.33 (因为 0.335 在内存中可能比 0.335 小一点点)