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

🔍 十二、reduce() 方法:将数组元素累积计算为单个值

reduce() 方法对数组中的每个元素按序执行一个由您提供的 reducer 函数,每一次运行会将先前元素的计算结果作为参数传入,最后将其结果汇总为单个返回值。

简单来说,它的核心思想是 "累积":将数组"压缩"成一个值(这个值可以是数字、对象、数组,甚至是另一个函数)。

1. 基本语法

javascript 复制代码
arr.reduce(callback(accumulator, currentValue[, index[, array]])[, initialValue])

参数解析:

  1. callback (必须):执行数组中每个元素的函数。
    • accumulator (acc):累积器 。它是上一次回调函数的返回值,或者是初始值 initialValue。这是 reduce 的核心。
    • currentValue (cur):当前正在处理的元素。
    • index (可选):当前元素的索引。
    • array (可选):原数组。
  2. initialValue (强烈建议):作为第一次调用 callback 时的 accumulator 的初始值。

**返回值:**累积处理后的最终结果。

2. 基础示例

(1) 数值累加(最经典示例)

计算数组中所有数字的总和。

javascript 复制代码
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) 求数组最大值

javascript 复制代码
const arr = [10, 5, 100, 2];

const max = arr.reduce((acc, cur) => {
  return Math.max(acc, cur);
}, arr[0]); // 初始值设为第一个元素

console.log(max); // 输出: 100

3. 企业开发中的实际应用场景

reduce 的强大在于它的返回值可以是任意类型,这使得它能处理非常复杂的业务逻辑。

场景 1:数组转对象

在后端接口对接中,有时需要将数组转换为以 ID 为 Key 的对象,以便快速查找。

javascript 复制代码
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 实现能体现逻辑)。

javascript 复制代码
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:数据统计与分组

将数组中的元素按类别分组,例如按角色分组用户。

javascript 复制代码
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:函数式编程管道

在复杂的数据处理流程中,组合多个函数。

javascript 复制代码
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

错误示范:

javascript 复制代码
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。如果 mapfilterforEach 能更直观地解决问题,就不要强行使用 reduce。过度复杂的 reduce 会让代码变得晦涩难懂,维护成本增加。

🔍 十三、toFixed() 方法:保留小数位

toFixed() 是 Number 对象的一个原型方法。它使用定点表示法来格式化一个数值,把数字转换为字符串,并保留指定位数的小数。

核心作用:

  1. 格式化数字的小数位数。
  2. 自动进行四舍五入(注意:并非标准的四舍五入,详见后文)。
  3. 返回字符串

1. 基本语法

javascript 复制代码
numObj.toFixed(digits)

参数解析:

  • digits (可选):小数点后显示的位数。取值范围是 0 到 20(包括 0 和 20)。
    • 如果省略,默认为 0。
    • 如果超出范围,部分环境可能支持更大范围,也可能抛出 RangeError

返回值:

  • 返回一个字符串,代表该数字保留指定小数位后的形式。

2. 基础示例

(1) 基础保留小数

javascript 复制代码
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) 数据类型陷阱

这是新手最容易踩的坑:结果是字符串,不是数字。

javascript 复制代码
const num = 10.5;
const result = num.toFixed(2);

console.log(result);      // 输出: "10.50"
console.log(typeof result); // 输出: "string"

// 如果想变回数字进行计算,需要转换
console.log(Number(result)); // 输出: 10.5

3. 企业开发中的实际应用场景

场景 1:电商价格展示(补零)

后端返回的价格可能是整数 100 或两位小数 99.9,前端展示时通常要求统一格式 100.0099.90

javascript 复制代码
const price = 99.9;
const displayPrice = price.toFixed(2); 

// 模板中直接使用
console.log(`¥${displayPrice}`); // 输出: ¥99.90

场景 2:数据可视化图表标签

在使用 ECharts 等图表库时,tooltip 或 y 轴标签通常要求保留两位小数,避免过长的小数挤占空间。

javascript 复制代码
const data = [12.345, 67.891, 45.001];

const formattedData = data.map(item => item.toFixed(2));
// 输出: ["12.35", "67.89", "45.00"]

场景 3:输入框金额格式化

在"金额输入框"失去焦点时,自动格式化为标准金额格式。

javascript 复制代码
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 前一位:奇数进位,偶数舍去

诡异示例:

javascript 复制代码
(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() 配合运算:

javascript 复制代码
// 封装一个严格的四舍五入函数
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.jslodash 来处理精度问题。

(2) 返回值是字符串

再次强调,toFixed() 返回字符串。如果你在计算总价时直接相加,会发生字符串拼接。

javascript 复制代码
let total = 0;
const price = 10.5;

total += price.toFixed(2); 
console.log(total); // 输出: "010.50" (字符串拼接!)

(3) 浮点数精度问题

由于 JavaScript 采用 IEEE 754 标准,浮点数运算本身就不精确。toFixed 有时能掩盖问题,有时会暴露问题。

javascript 复制代码
(0.335).toFixed(2); // 输出: "0.33" (因为 0.335 在内存中可能比 0.335 小一点点)
相关推荐
北寻北爱2 小时前
面试题-js篇
前端·javascript
两万五千个小时2 小时前
学习 Pi Coding Agent:系统提示词与工具设计深度解析
javascript·人工智能·架构
harrain2 小时前
vue3多个watch监听统一等待触发再执行后续逻辑的处理方案
前端·javascript·vue.js
像污秽一样2 小时前
算法设计与分析-习题9.2
数据结构·算法·排序算法·dfs
Book思议-2 小时前
【数据结构实战】:基于C语言单链表实现红旗渠景区年卡信息管理系统
c语言·开发语言·数据结构
wuhen_n2 小时前
Vite 核心原理:ESM 带来的开发时“瞬移”体验
前端·javascript·vue.js
nibabaoo2 小时前
前端开发攻略---vue3长列表性能优化终极指南:虚拟滚动、分页加载、时间分片等6种方案详解与代码实现
前端·javascript·vue.js·虚拟滚动·分页加载·长列表·时间分片
不甜情歌2 小时前
拆解JS原型核心:显式原型(prototype)+ 隐式原型(__proto__)+原型链,解锁JS继承的关键密码
前端·javascript
香草泡芙2 小时前
解锁AI Agent潜能:基于Langchain组件库的落地指南(2)
前端·javascript·人工智能