JavaScript: JavaScript 数组进阶
上一课学了数组的基础操作,这课来学数组的"大招"——高阶方法。这些方法能让你用更少的代码做更多的事,是现代 JavaScript 开发的核心技能。
1. map — 映射
map 对数组每个元素执行函数,返回一个新数组,原数组不变。就像一条流水线:每个元素进去,加工后出来,组成新产品。
HTML
<script>
let prices = [10, 20, 30];
let doubled = prices.map(p => p * 2); // [20, 40, 60]
</script>
map 的回调函数接收三个参数:(元素, 索引, 原数组),最常用的是第一个。
▶ 示例
JAVASCRIPT
// 本节代码示例
const message = "你好,世界!";
console.log(message);
这是最简单的示例,后续章节会详细解释。## 2. filter — 过滤
filter 筛选满足条件的元素,返回新数组,原数组不变。像安检:符合条件的放行,不符合的拦下。
HTML
<script>
let scores = [45, 80, 92, 55, 78, 98];
let passed = scores.filter(s => s >= 60); // [80, 92, 78, 98]
</script>
回调函数返回 true 的元素保留,返回 false 的丢弃。
3. reduce — 归并
reduce 把数组"压缩"成一个值。最经典的用法是求和:
HTML
<script>
let nums = [1, 2, 3, 4, 5];
let sum = nums.reduce((acc, cur) => acc + cur, 0); // 15
</script>
两个参数的含义:
acc(accumulator):累计值,上一次的返回结果cur(current):当前元素- 第二个参数
0是累计值的初始值
💡 提示:
reduce 一开始最难理解,但它的本质就一句话:把数组一步步"压缩"成一个值。求和、求最大值、统计次数都能用它。
▶ 示例:map、filter、reduce 组合
HTML
<!DOCTYPE html>
<html>
<body>
<h2>map / filter / reduce</h2>
<div id="output"></div>
<script>
let products = [
{ name: '笔记本', price: 4999, category: '电子' },
{ name: 'T恤', price: 89, category: '服装' },
{ name: '鼠标', price: 199, category: '电子' },
{ name: '牛仔裤', price: 299, category: '服装' },
{ name: '键盘', price: 399, category: '电子' }
];
let html = '<strong>所有商品:</strong><br>';
products.forEach(p => html += `${p.name} - ¥${p.price} (${p.category})<br>`);
let electronics = products.filter(p => p.category === '电子');
html += '<br><strong>电子类商品(filter):</strong><br>';
electronics.forEach(p => html += `${p.name} - ¥${p.price}<br>`);
let names = products.map(p => p.name);
html += '<br><strong>商品名列表(map):</strong>' + names.join(', ') + '<br>';
let total = products.reduce((sum, p) => sum + p.price, 0);
html += '<br><strong>总价(reduce):</strong>¥' + total;
document.getElementById('output').innerHTML = html;
</script>
</body>
</html>
4. find 和 findIndex
find— 返回第一个满足条件的元素findIndex— 返回第一个满足条件的索引
找不到时,find 返回 undefined,findIndex 返回 -1。
HTML
<script>
let users = [
{ name: '小明', age: 20 },
{ name: '小红', age: 17 },
{ name: '小刚', age: 25 }
];
users.find(u => u.age > 22); // { name: '小刚', age: 25 }
users.findIndex(u => u.age < 18); // 1(小红的索引)
</script>
5. sort — 排序
sort 对数组原地排序(会改变原数组!)。默认按字符串排序,这坑了不少人:
HTML
<script>
let nums = [10, 1, 21, 2];
nums.sort(); // [1, 10, 2, 21] ← 字符串排序!"10" < "2"
// 正确:数字排序需要传比较函数
nums.sort((a, b) => a - b); // [1, 2, 10, 21] ← 从小到大
nums.sort((a, b) => b - a); // [21, 10, 2, 1] ← 从大到小
</script>
⚠️ 注意:
sort 会修改原数组!不想改原数组,先拷贝一份再排序:[...arr].sort(...)。
▶ 示例:学生成绩排序
HTML
<!DOCTYPE html>
<html>
<body>
<h2>学生成绩排序</h2>
<div id="output"></div>
<script>
let students = [
{ name: '小明', score: 72 },
{ name: '小红', score: 95 },
{ name: '小刚', score: 88 },
{ name: '小丽', score: 61 },
{ name: '小华', score: 95 }
];
let html = '<strong>原始顺序:</strong><br>';
students.forEach(s => html += `${s.name} - ${s.score}分<br>`);
let byScore = [...students].sort((a, b) => b.score - a.score);
html += '<br><strong>按分数降序:</strong><br>';
byScore.forEach((s, i) => {
let medal = i === 0 ? '🥇' : i === 1 ? '🥈' : i === 2 ? '🥉' : '';
html += `${medal} ${s.name} - ${s.score}分<br>`;
});
let topStudents = students.filter(s => s.score >= 90);
html += '<br><strong>90分以上(filter):</strong><br>';
topStudents.forEach(s => html += `${s.name} - ${s.score}分<br>`);
let found = students.find(s => s.name === '小刚');
html += `<br><strong>查找"小刚"(find):</strong>${found ? found.score + '分' : '未找到'}`;
document.getElementById('output').innerHTML = html;
</script>
</body>
</html>
6. some 和 every
some— 是否至少有一个元素满足条件(返回布尔值)every— 是否所有元素都满足条件(返回布尔值)
HTML
<script>
let scores = [60, 75, 82, 90];
scores.some(s => s >= 90); // true 有人90分以上
scores.some(s => s === 100); // false 没人满分
scores.every(s => s >= 60); // true 全部及格
</script>
💡 提示:
some 像在问"有没有人...",every 像在问"是不是所有人都..."。一问存在,一问全部。
7. 展开运算符(...)
展开运算符 ... 可以"拆开"数组,常用于复制和合并:
HTML
<script>
let a = [1, 2, 3];
let b = [...a]; // 复制:[1, 2, 3](新数组,不是引用)
let c = [...a, 4, 5]; // 合并:[1, 2, 3, 4, 5]
let d = [0, ...a, 4]; // 插入:[0, 1, 2, 3, 4]
let x = [1, 2], y = [3, 4];
let z = [...x, ...y]; // [1, 2, 3, 4]
</script>
8. 解构赋值
从数组中提取值,按位置赋给变量:
HTML
<script>
let [a, b, c] = [1, 2, 3];
// a = 1, b = 2, c = 3
let [first, ...rest] = [1, 2, 3, 4, 5];
// first = 1, rest = [2, 3, 4, 5]
let [name, score] = ['小明', 95];
// name = '小明', score = 95
</script>
💡 提示: 解构赋值让代码更简洁。函数返回多个值时,用数组包装再解构是常见模式。
9. 链式调用
高阶方法返回新数组,所以可以链式调用——一个接一个:
HTML
<script>
let result = students
.filter(s => s.score >= 60) // 先过滤及格的
.map(s => s.name) // 再取名字
.sort(); // 最后排序
</script>
▶ 示例:链式调用处理数据
HTML
<!DOCTYPE html>
<html>
<body>
<h2>链式调用</h2>
<div id="output"></div>
<script>
let employees = [
{ name: '张三', dept: '技术部', salary: 15000 },
{ name: '李四', dept: '销售部', salary: 8000 },
{ name: '王五', dept: '技术部', salary: 20000 },
{ name: '赵六', dept: '销售部', salary: 12000 },
{ name: '孙七', dept: '技术部', salary: 18000 },
{ name: '周八', dept: '市场部', salary: 10000 }
];
let html = '<strong>所有员工:</strong><br>';
employees.forEach(e => html += `${e.name} | ${e.dept} | ¥${e.salary}<br>`);
let techHighSalary = employees
.filter(e => e.dept === '技术部')
.filter(e => e.salary >= 16000)
.map(e => `${e.name}(¥${e.salary})`);
html += '<br><strong>技术部高薪(链式filter+map):</strong><br>' + techHighSalary.join(', ');
let avgSalary = employees
.filter(e => e.dept === '技术部')
.map(e => e.salary)
.reduce((sum, s) => sum + s, 0);
let count = employees.filter(e => e.dept === '技术部').length;
html += `<br><br><strong>技术部平均薪资:</strong>¥${(avgSalary / count).toFixed(0)}`;
let allAbove5k = employees.every(e => e.salary > 5000);
let has20k = employees.some(e => e.salary >= 20000);
html += `<br><br><strong>全员薪资>5000?</strong>${allAbove5k ? '是' : '否'}`;
html += `<br><strong>有人薪资≥20000?</strong>${has20k ? '是' : '否'}`;
document.getElementById('output').innerHTML = html;
</script>
</body>
</html>
▶ 示例:展开运算符与解构
HTML
<!DOCTYPE html>
<html>
<body>
<h2>展开运算符与解构</h2>
<div id="output"></div>
<script>
let a = [1, 2, 3];
let b = [...a];
b.push(4);
let html = `<strong>展开运算符复制:</strong><br>`;
html += `原数组 a: [${a}]<br>`;
html += `副本 b: [${b}]<br>`;
html += `修改 b 不影响 a<br><br>`;
let x = [1, 2], y = [3, 4];
let merged = [...x, ...y, 5];
html += `<strong>合并数组:</strong>[${merged}]<br><br>`;
let [first, second, ...rest] = [10, 20, 30, 40, 50];
html += `<strong>解构赋值:</strong><br>`;
html += `first = ${first}<br>`;
html += `second = ${second}<br>`;
html += `rest = [${rest}]<br><br>`;
let scores = [85, 92, 78, 95, 60];
let [highest, ...others] = [...scores].sort((a, b) => b - a);
html += `<strong>解构+排序:</strong><br>`;
html += `最高分 = ${highest}<br>`;
html += `其余 = [${others}]`;
document.getElementById('output').innerHTML = html;
</script>
</body>
</html>
❓ 常见问题
Q
map 和 forEach 有什么区别?A
map 返回新数组,forEach 返回 undefined。需要用结果就选 map,只是执行操作就选 forEach。别在 forEach 里手动 push 到外部数组——那该用 map。Q
sort 为什么会改变原数组?A 这是历史设计。
sort 采用原地排序(in-place),修改原数组而不是返回新数组。不想改原数组就先拷贝:[...arr].sort(...)。Q
reduce 的初始值可以省略吗?A 可以,省略时以数组第一个元素作为初始值,从第二个元素开始遍历。但空数组省略初始值会报错,所以建议始终写上初始值,更安全。
📖 小节
map映射生成新数组,filter过滤出满足条件的元素,reduce归并为单个值find找第一个匹配元素,findIndex找第一个匹配索引sort默认按字符串排序,数字排序必须传比较函数(a, b) => a - bsome判断有没有满足条件的,every判断是否全部满足- 展开运算符
...复制和合并数组,解构赋值提取数组元素 - 高阶方法返回新数组,可以链式调用,代码又短又好读
📝 作业
- 给定数组
[3, 8, 12, 5, 9, 17, 4],用filter筛选出大于 7 的数,再用map将它们翻倍,输出结果 - 用
reduce统计一个字符串数组中每个单词出现的次数,如['apple', 'banana', 'apple', 'cherry', 'banana', 'apple']→{ apple: 3, banana: 2, cherry: 1 } - 给定学生数组(含 name、score),用链式调用实现:过滤及格的学生 → 按分数降序排序 → 取出名字生成新数组,将结果显示在页面上