返回笔记列表
01 · NOTES / WebGIS 开发

第 13 节 · 数组高阶方法

2026 年 9 月 1 日

第 13 节 · 数组高阶方法

📌 版本信息:基于 ECMAScript 2025(map/filter/reduce 等为基础语义,多年稳定;2026-08-29 核对) 📚 来源:现代 JS 教程 · 数组方法MDN Array | 本地教材 resources/01-web-basics/modern-js-tutorial/1-js/

一、这一节的目标

  1. 精通三巨头:map / filter / reduce,理解"回调函数当参数"的用法
  2. 掌握 find / some / every / includes 的适用场景
  3. 链式调用:像流水线一样处理一批数据
  4. 建立 GIS 核心工作流直觉:拿到一个数组(比如地震列表)→ 筛 → 变形 → 汇总

二、为什么需要高阶方法

第 12 节处理数据时你写的是裸 for 循环——能用,但"要什么"淹没在"怎么循环"里。高阶方法把意图直接写出来:

// 需求:从震级列表里拿到所有 ≥5 的
const mags = [3.2, 5.1, 4.8, 6.0, 2.9];

// 裸循环版:程序在说"怎么循环、怎么判断、怎么塞"
const result1 = [];
for (const m of mags) {
  if (m >= 5) result1.push(m);
}

// filter 版:一句话说出"要什么"
const result2 = mags.filter((m) => m >= 5);   // [5.1, 6.0]

本质:这些方法接收一个回调函数(第 11 节:函数是值,可以当参数传),数组自己负责循环,你只负责"判断/变形规则"。


三、三巨头详解

① map:一一变形(数量不变,样子变)

const quakes = [
  { place: '武汉', mag: 5.1 },
  { place: '西安', mag: 3.2 },
];

// 把对象数组变成"描述字符串"数组
const labels = quakes.map((q) => `${q.place} ${q.mag} 级`);
// ['武汉 5.1 级', '西安 3.2 级']

// 给每个对象加字段(返回新对象,不改原数据)
const labeled = quakes.map((q) => ({ ...q, level: q.mag >= 5 ? '大震' : '小震' }));

口诀:map 是"加工流水线",进去 N 个,出来 N 个,每个都被加工过。

② filter:筛选(样子不变,数量变少)

const strong = quakes.filter((q) => q.mag >= 5);     // 只留大震
const notWuhan = quakes.filter((q) => q.place !== '武汉');

口诀:filter 是"安检门",回调返回 true 的放行,false 的拦下。

③ reduce:汇总(N 个进,1 个出)

const mags = [3.2, 5.1, 4.8, 6.0];

// 求平均震级
const total = mags.reduce((sum, m) => sum + m, 0);   // 19.1(0 是初始值)
const avg = total / mags.length;

// 进阶:一次遍历同时统计(GIS 统计面板的雏形)
const stats = mags.reduce(
  (acc, m) => {
    acc.count += 1;
    acc.total += m;
    acc.max = Math.max(acc.max, m);
    return acc;                    // ⚠️ 别忘了把 acc 返回给下一轮
  },
  { count: 0, total: 0, max: 0 }   // 初始值(汇总结果的形状)
);
// { count: 4, total: 19.1, max: 6 }

reduce 是三巨头里最抽象的——初始值 + 逐轮累积。读别人的 reduce 时先找这两个东西。


四、其余高频方法速查

const list = [{ id: 1, name: '武汉' }, { id: 2, name: '西安' }];

list.find((x) => x.id === 2);      // 找第一个满足的元素(返回元素本身或 undefined)
list.findIndex((x) => x.id === 2); // 它的下标
list.some((x) => x.id > 10);       // 有一个满足吗?false
list.every((x) => x.id > 0);       // 全部满足吗?true
list.includes('x');                // 原始值精确查找(数组内是对象时用 find)

[1, 2, 3].at(-1);                  // 3(at(-1) 取最后一个,现代写法)
[[1, 2], [3, 4]].flat();           // [1,2,3,4](拍平一层)

五、链式调用:GIS 数据处理的流水线

三个方法都返回新数组,所以能一路点下去:

const quakes = [
  { place: '武汉', mag: 5.1, depth: 10 },
  { place: '西安', mag: 3.2, depth: 18 },
  { place: '成都', mag: 6.0, depth: 12 },
  { place: '拉萨', mag: 4.4, depth: 8 },
];

// 需求:3 级以上的 → 按震级降序 → 拿前两名 → 变成描述串
const report = quakes
  .filter((q) => q.mag >= 3)                     // 筛
  .sort((a, b) => b.mag - a.mag)                 // 排(⚠️ sort 会改原数组,链头是 filter 的新数组所以安全)
  .slice(0, 2)                                   // 取前 2
  .map((q) => `${q.place}${q.mag}级,深 ${q.depth}km)`);  // 变形
// ['成都(6级,深 12km)', '武汉(5.1级,深 10km)']

读链式代码的技巧:从上往下一行行读,每行的结果就是下一行的输入——和车间流水线一模一样。

⚠️ 两个规范:①链式里优先 filter 在前(先筛后算,性能好);②map 里必须 return(箭头函数省略花括号才有隐式 return,写了 {} 就必须显式 return)——漏 return 拿到一串 undefined 是新手高频 bug。


六、动手跟练:13 · 数据表格渲染

配套文件:02-web-basics/examples/13-数据表格渲染.html

步骤:

  1. Live Server 打开,F12 对照输出读代码
  2. 数据是一份迷你"USGS 地震 GeoJSON"(真数据的形状!)
  3. 完成 6 个 TODO:filter 筛强震、map 生成表格行、reduce 统计面板、链式组合、按震级排序渲染、把表格真的渲染到页面
  4. 这一节是 C-02b(地震数据面板)的直接前奏——这里的 TODO 6 就是那个综合项目的核心代码

通关标准:

  • 6 个 TODO 全部完成且表格正确渲染
  • 不查资料写出"筛 → 排 → 取前三 → 拼字符串"的链
  • 能解释 map 回调里 {}() 包住对象的区别(=> ({...})=> {...}

七、自测题

  1. map 和 filter 的输入输出数量关系分别是什么?
  2. reduce 的两个关键组成是什么?漏写初始值会怎样?
  3. xs.filter(...).sort(...) 为什么不用担心 sort 改坏原数组?
  4. xs.map((x) => { name: x.name }) 有什么 bug?怎么修?
  5. 链式处理中为什么建议 filter 放最前面?

参考答案

  1. map:N 进 N 出(逐个变形);filter:N 进 ≤N 出(筛掉一部分)。
  2. 初始值 + 累积回调(每轮返回累积结果)。漏初始值时第一轮把前两个元素当初始值和首项,遇空数组直接报错,遇对象数组几乎必错。
  3. filter 返回的是新数组,sort 改的是这个新数组,原件无恙。
  4. {} 被箭头函数当成"函数体"而非"对象字面量",里面的 name: 被当标签语句,返回 undefined。要写 => ({ name: x.name }) 用圆括号包住对象。
  5. 先筛先减量,后续 sort/map/reduce 处理的元素更少,性能更好,结果也一致。

八、下一步

数据会处理了,但结果还只在 Console 里 → 第 14 节:DOM 操作,把数据真正画到页面上。

TAGSweb