map / filter / reduce:数据管道的三个主角

三个方法都以回调函数为参数,都不改原数组。下面用一份商品数据逐个点按钮观察。

名称价格在售库存

黄色行是 onSale: true 的在售商品——后面 filter 用它筛选。

[迷你控制台]

一、map:逐个变换,等长输入等长输出

products.map(p => p.name)                       // ['键盘', '鼠标', …] 每个元素换成另一个值
products.map(p => `${p.name} ¥${p.price}`)      // 元素变换成字符串

// 回调的完整签名:map((元素, 下标, 原数组) => 新值)
products.map((p, i) => (i + 1) + '. ' + p.name)

二、filter:按条件保留,结果只会更短

products.filter(p => p.onSale)          // 回调返回真,就保留
products.filter(p => p.price < 300)

// 返回的永远是数组:没筛到就是 [],不是 null
// 判断「有没有结果」用 filtered.length > 0

三、reduce:把一串值「汇总」成一个值

const total = products.reduce((acc, p) => acc + p.price, 0)

// 形态:reduce((累计值, 当前元素) => 新的累计值, 初始值)
// 初始值决定结果类型:0 → 数字;[] → 数组;{} → 对象

const grouped = products.reduce((acc, p) => {
  const key = p.onSale ? 'on' : 'off'
  acc[key] = (acc[key] ?? 0) + 1
  return acc
}, {})   // { on: 4, off: 1 }

reduce 是最容易写晕的一个:把「累计值」想成「到目前为止的结果」,每个元素负责把它更新一步。始终传初始值——空数组不传初始值会直接报错,是真实事故来源。

四、some / every / find:判断与查找

products.some(p => p.price > 1000)    // true —— 有没有「至少一个」满足
products.every(p => p.stock > 0)      // false —— 是不是「全部」满足(空数组恒为 true!)
products.find(p => p.price < 300)     // 第一个满足的「元素」,没有则 undefined

五、链式管道:先筛、再排、后变

const names = products
  .filter(p => p.onSale)                     // ① 先缩小范围
  .sort((a, b) => a.price - b.price)         // ② 再排序(先拷贝再排,见正文)
  .map(p => `${p.name}(¥${p.price})`)       // ③ 最后变换成展示文本

// 每步返回新数组,可以继续接下一个方法 —— 「数据管道」