Veloris.
返回索引
概念基础 2026-03-08

数组与高阶函数

3 分钟
936 words

数组与高阶函数

前言

上一篇我们学了对象和原型链。现在来看 JS 中最常用的数据结构——数组。

前端开发中几乎每个页面都涉及列表渲染,而列表数据就是数组。在 C 里你用 for 循环遍历数组,手动管理索引和边界。JS 的数组提供了一套强大的高阶函数(map/filter/reduce 等),让你用“声明式”的方式处理数据——告诉程序“做什么”而不是“怎么做”。

本文的重点是 reduce——数组方法中的“万能工具”,以及 不可变更新模式——React 开发的必备技能。

正文

1. 数组基础

const arr = [1, 2, 3, 4, 5];
const mixed = [1, "hello", true, null, { name: "Alice" }];
const filled = new Array(5).fill(0); // [0, 0, 0, 0, 0]

arr[0];              // 1
arr[arr.length - 1]; // 5(最后一个元素)
Array.isArray(arr);  // true

2. 常用增删方法

const fruits = ["apple", "banana"];

// 尾部操作
fruits.push("cherry");    // ["apple", "banana", "cherry"]
fruits.pop();             // 返回 "cherry"

// 头部操作
fruits.unshift("mango");  // ["mango", "apple", "banana"]
fruits.shift();           // 返回 "mango"

// splice:万能增删改(修改原数组)
const nums = [1, 2, 3, 4, 5];
nums.splice(2, 1);           // 从索引2删1个 → [1, 2, 4, 5]
nums.splice(1, 0, 10, 20);   // 从索引1插入 → [1, 10, 20, 2, 4, 5]

// slice:截取(不修改原数组)
[1, 2, 3, 4, 5].slice(1, 3); // [2, 3]
[1, 2, 3, 4, 5].slice(-2);   // [4, 5]

// 其他常用
[1, 2].concat([3, 4]);          // [1, 2, 3, 4]
[1, 2, 3].includes(2);          // true
[1, 2, 3].indexOf(2);           // 1
[3, 1, 4].sort((a, b) => a - b); // [1, 3, 4]
[1, 2, 3].reverse();            // [3, 2, 1]
["a", "b"].join("-");            // "a-b"
[1, [2, [3]]].flat(Infinity);   // [1, 2, 3]

3. 高阶函数

3.1 forEach — 遍历

["apple", "banana", "cherry"].forEach((fruit, index) => {
  console.log(`${index}: ${fruit}`);
});

3.2 map — 映射转换

对每个元素操作,返回新数组(不修改原数组):

const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map(n => n * 2); // [2, 4, 6, 8, 10]

const users = [
  { name: "Alice", age: 25 },
  { name: "Bob", age: 30 },
];
const names = users.map(u => u.name); // ["Alice", "Bob"]

3.3 filter — 筛选

返回满足条件的元素组成的新数组

const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const evens = numbers.filter(n => n % 2 === 0); // [2, 4, 6, 8, 10]

// 删除假值
[0, 1, "", "hello", null, 42].filter(Boolean); // [1, "hello", 42]

3.4 ★ reduce — 归约(万能工具)

reduce 是数组方法中最强大也最难理解的一个。它的核心思想是:带着一个“累加器”遍历数组,每步更新累加器,最后返回累加器的值:

const numbers = [1, 2, 3, 4, 5];

// 求和
const sum = numbers.reduce((acc, cur) => acc + cur, 0); // 15

// 求最大值
const max = numbers.reduce((m, n) => n > m ? n : m, -Infinity); // 5

// 统计词频
const words = ["hello", "world", "hello", "js", "hello"];
const freq = words.reduce((acc, w) => {
  acc[w] = (acc[w] || 0) + 1;
  return acc;
}, {});
// { hello: 3, world: 1, js: 1 }

// 事实上,map 和 filter 都可以用 reduce 实现——它是数组方法的“底层 API”

💡 工程师手记reduce 是我学 JS 时觉得最“神奇”的方法。刚开始看不懂,后来发现它的本质就是一个带着“状态”遍历的 for 循环——acc 就是那个状态。用嵌入式的话说,就像一个状态机,每次处理一个输入就更新一次状态。

(建议替换为你自己理解 reduce 的顿悟时刻)

3.5 find / findIndex

const users = [
  { id: 1, name: "Alice" },
  { id: 2, name: "Bob" },
];
users.find(u => u.id === 2);      // { id: 2, name: "Bob" }
users.findIndex(u => u.id === 2); // 1

3.6 some / every

[1, 3, 5, 7, 8].some(n => n % 2 === 0);  // true(8是偶数)
[1, 3, 5, 7].every(n => n % 2 === 1);     // true(全是奇数)

4. 链式调用

const products = [
  { name: "Phone", price: 999, inStock: true },
  { name: "Laptop", price: 1999, inStock: true },
  { name: "Tablet", price: 599, inStock: false },
  { name: "Watch", price: 399, inStock: true },
];

const result = products
  .filter(p => p.inStock)
  .filter(p => p.price >= 500)
  .sort((a, b) => a.price - b.price)
  .map(p => `${p.name} ¥${p.price}`);
// ["Phone ¥999", "Laptop ¥1999"]

5. 展开运算符与解构

// 展开
const merged = [...[1, 2], ...[3, 4]]; // [1, 2, 3, 4]
Math.max(...[3, 1, 4]); // 4

// 解构
const [first, second, ...rest] = [1, 2, 3, 4, 5];
// first=1, second=2, rest=[3,4,5]

// 交换变量
let a = 1, b = 2;
[a, b] = [b, a]; // a=2, b=1

6. 不可变更新模式(React 必备)

在 React 中,状态更新必须是“不可变”的——不能直接修改原数组,而是创建一个新数组。这是因为 React 通过对比“新旧状态是否是同一个引用”来决定是否重新渲染,如果你直接修改原数组,引用没变,React 会认为“没变化”而跳过渲染。

const todos = [
  { id: 1, text: "学HTML", done: true },
  { id: 2, text: "学CSS", done: true },
  { id: 3, text: "学JS", done: false },
];

// 添加(不用 push)
const added = [...todos, { id: 4, text: "学React", done: false }];

// 删除(不用 splice)
const removed = todos.filter(t => t.id !== 2);

// 更新某一项(不直接修改)
const updated = todos.map(t =>
  t.id === 3 ? { ...t, done: true } : t
);

总结

知识点核心要点
map转换每个元素,返回新数组
filter筛选满足条件的元素
reduce归约为单个值(求和、统计、分组)
find查找第一个满足条件的元素
some/every条件检测
链式调用filter → sort → map 数据管道
展开/解构[...arr] 浅拷贝、[a, b] = arr 提取
不可变更新React 中用展开/filter/map 代替 push/splice

常见问题

💬 你可能会问:map/filter 和 for 循环有什么区别?性能差异大吗?

功能上等价,但 map/filter 更简洁、更不容易出错(不用管理索引)。性能差异微乎其微,前端场景下完全可以忽略。优先用高阶函数,代码可读性更重要。

💬 你可能会问:reduce 太难理解了,可以不用吗?

日常开发中,求和、统计、分组等场景确实需要 reduce。但如果你觉得 reduce 读起来很吃力,用 for 循环实现同样的逻辑也完全没问题——可读性永远优先于“炒作”。

💬 你可能会问:为什么 React 要求不可变更新?直接 push 不行吗?

React 通过对比“新旧状态是否是同一个引用”来决定是否重新渲染。直接 push 修改原数组,引用没变,React 会认为“没变化”而跳过更新。后续学 React 时你会深切体会这一点。

下一步行动:在 Console 中练习链式调用——给一个产品数组依次应用 filter、sort、map,试试用 reduce 实现求和和词频统计。

参考资料


📖 系列导航:本文是「FPGA 工程师的前端学习笔记」系列的第 9 篇 上一篇:对象与原型链 下一篇:DOM 操作与事件机制

End of file.