流程控制、数组与集合

这一篇讲 JS 的流程控制(if / switch / 异常)、循环(for / while / for…in / for…of)、数组Set 与 Map,以及各类遍历方式

其中 for...infor...of、数组的高阶方法(map/filter/reduce)是 JS 与 Java 差异最大的部分;ifforswitch 等基础语法与 Java 几乎一致,只做对照。

阅读方式:与 Java 相同的语法简单带过,重点看「差异」和「⚠️ 坑点」。

一、流程控制

1. if / else if / else(与 Java 相同)

1
2
3
4
5
6
7
if (a > 0) {
// ...
} else if (a === 0) {
// ...
} else {
// ...
}

唯一差异:条件表达式不强制是 boolean,JS 会用 truthy/falsy 自动判断(0""nullundefinedNaN 都走 else,见第 1 篇)。

2. switch(基本与 Java 相同)

1
2
3
4
5
6
7
8
9
10
11
switch (x) {
case 1:
// ...
break;
case 2:
case 3: // 多个 case 合并
// ...
break;
default:
// ...
}
  • JS 的 switch=== 严格相等比较(不隐式转换)。
  • 与 Java 一样有 fall-through:漏写 break 会继续执行下一个 case。
  • case 后面可以是表达式,不限于常量。

3. 三元运算符(同 Java)

1
let y = x > 0 ? "正" : "非正";

4. break / continue(同 Java)

break 跳出循环、continue 跳过本次,for...in/for...of 里同样适用。

5. 异常处理 try / catch / finally

1
2
3
4
5
6
7
try {
// 可能出错的代码
} catch (err) {
console.log(err.message); // err 是被抛出的值
} finally {
// 无论如何都执行(与 Java 一致)
}

与 Java 的差异:

  • catch 不声明异常类型(Java 要写 catch (ExceptionType e))。
  • throw 可以抛任意值(字符串、数字、对象都行),但推荐抛 Error
1
throw new Error("出错了");

二、循环

1. for / while / do-while(与 Java 相同,带过)

1
2
3
for (let i = 0; i < arr.length; i++) { /* ... */ }
while (cond) { /* ... */ }
do { /* ... */ } while (cond);

注意:循环计数器用 let,不要用 varvar 无块级作用域,闭包场景会出经典 bug)。

2. for…of(遍历”值”,重点)

对应 Java 的增强 for-each(for (int x : arr)),遍历可迭代对象的值

1
2
3
4
for (const item of [1, 2, 3]) { /* 1, 2, 3 */ }
for (const ch of "hello") { /* h, e, l, l, o */ }
for (const v of set) { /* Set 的每个值 */ }
for (const [k, v] of map) { /* Map 的 [键, 值] */ }

小细节:循环变量可以写 const item,每次迭代都是独立作用域,放心用 const

3. for…in(遍历”键/索引”,重点)

遍历对象的可枚举属性名(字符串):

1
2
3
4
5
6
7
8
9
const obj = { a: 1, b: 2 };
for (const key in obj) {
console.log(key); // "a", "b"(属性名)
console.log(obj[key]); // 1, 2(用 obj[key] 取值)
}

for (const index in [10, 20]) {
console.log(index); // "0", "1"(数组索引是字符串)
}

⚠️ 坑点

  • for...in 遍历数组得到的是字符串索引,还会带上原型上的可枚举属性,所以数组不要用 for…in,用 for...offorEach
  • 遍历对象时,为避免原型链上的继承属性,常配合 hasOwnProperty 判断(或直接用 Object.keys)。

4. for…of 与 for…in 对比(核心)

for...of for...in
遍历内容 键名 / 索引
适用对象 数组、字符串、Set、Map 等可迭代对象 普通对象、数组
数组 遍历元素值 遍历索引(字符串)
普通对象 ❌ 不能直接用(对象不可迭代,会报错) ✅ 遍历属性名
1
2
3
4
let arr = ["a", "b", "c"];

for (let v of arr) console.log(v); // a, b, c(值)
for (let k in arr) console.log(k); // 0, 1, 2(索引)

记忆口诀:**of 取”值”,in 取”键”**。

5. 遍历普通对象(补充)

1
2
3
4
5
Object.keys(obj);     // ["a", "b"] 键数组
Object.values(obj); // [1, 2] 值数组
Object.entries(obj); // [["a",1], ["b",2]]

for (const [key, value] of Object.entries(obj)) { /* 遍历键值对 */ }

三、数组 Array

JS 数组是动态长度、可存混合类型,兼具 Java 的”数组”和 ArrayList 的特点。

1. 创建与访问

1
2
3
4
let arr = [1, 2, 3];        // 字面量(常用)
let arr2 = new Array(3); // 长度为 3 的空数组
arr[0]; // 下标访问
arr.length; // 长度,可赋值修改(arr.length = 0 清空)

2. 增删元素

1
2
3
4
5
arr.push(4);              // 尾部加,返回新长度
arr.pop(); // 尾部删,返回删除的元素
arr.unshift(0); // 头部加
arr.shift(); // 头部删
arr.splice(1, 2, 'a'); // 从索引1删2个,再插入 'a'(增删改一体)
方法 位置 作用
push / pop 尾部 加 / 删(类比栈)
unshift / shift 头部 加 / 删
splice(start, n, ...) 任意 删除 + 插入

3. 查找

1
2
3
4
arr.indexOf(2);          // 返回索引,不存在 -1
arr.includes(2); // 是否存在,返回 true/false
arr.find(x => x > 2); // 第一个满足条件的元素(无则 undefined)
arr.findIndex(x => x > 2); // 索引(无则 -1)

4. 遍历与高阶方法(重点,对应 Java Stream)

1
2
3
4
5
6
7
arr.forEach((item, index) => { /* 遍历,无返回值 */ });

arr.map(x => x * 2); // 每个元素变换,返回新数组
arr.filter(x => x > 1); // 过滤,返回新数组
arr.reduce((acc, x) => acc + x, 0); // 累加归并(第二个参数是初始值)
arr.some(x => x > 1); // 是否有满足条件的(true/false)
arr.every(x => x > 0); // 是否都满足(true/false)
JS 方法 作用 Java 对应
map 映射变换 Stream.map
filter 过滤 Stream.filter
reduce 归并累加 Stream.reduce
forEach 遍历 forEach
some / every 存在性 / 全量判断 anyMatch / allMatch
find / findIndex 查找 findFirst

⚠️ 这些方法不修改原数组map/filter 返回新数组,forEach 返回 undefined)。reduce 回调是 (累计值, 当前值),和 Java 的 reduce(初始值, 累加器) 参数顺序相反,注意区分。

5. 排序与转换

1
2
3
4
5
6
arr.sort((a, b) => a - b);  // 数字升序(必须传比较函数)
arr.reverse(); // 反转
arr.join('-'); // 用分隔符拼接成字符串
arr.slice(1, 3); // 截取子数组(不修改原数组,前闭后开)
arr.concat(arr2); // 合并
arr.flat(); // 拍平一层 [1,[2]] -> [1,2]

⚠️ sort 的坑:默认按字符串排序,数字会排错:

1
2
[3, 1, 10, 2].sort();             // [1, 10, 2, 3](按字符串字典序)
[3, 1, 10, 2].sort((a, b) => a - b); // [1, 2, 3, 10](正确)

6. 解构与展开

1
2
let [a, b] = [1, 2];       // 解构赋值
let arr3 = [...arr, 4]; // 展开拼接

四、Set 与 Map

1. Set(集合,对应 Java HashSet)

元素唯一、自动去重

1
2
3
4
5
6
7
8
let s = new Set([1, 2, 2, 3]);  // Set {1, 2, 3}
s.add(4); // 添加
s.delete(2); // 删除
s.has(3); // 是否存在 true
s.size; // 大小(注意是属性,不是方法)
s.clear(); // 清空

for (const v of s) { /* 遍历 */ }

去重是 Set 最常用场景:

1
let unique = [...new Set(arr)];  // 数组去重

2. Map(键值对,对应 Java HashMap)

1
2
3
4
5
6
7
8
9
10
11
let m = new Map();
m.set("name", "Tom"); // 设置键值
m.set(1, "one"); // 键可以是任意类型(数字、对象、函数)
m.get("name"); // 取值 "Tom"
m.has("name"); // 是否有键 true
m.delete("name"); // 删除
m.size; // 大小
m.clear(); // 清空

for (const [k, v] of m) { /* 遍历 [键, 值] */ }
m.forEach((v, k) => { /* 注意参数是 (值, 键) */ });

⚠️ 与 Java 的差异:

  • Map 的键可以是任意类型(含原始类型、对象),不像 Java 键必须是引用类型。
  • m.size属性,Java 的 map.size() 是方法。
  • forEach 回调参数顺序是 (值, 键),和直觉相反,容易搞混。

3. 普通对象 vs Map(怎么选)

普通对象 {} Map
键类型 只能是字符串 / Symbol 任意类型
大小 size,要 Object.keys().length size
遍历 for...in / Object.keys 直接 for...of
插入顺序 不保证(数字键会重排) 保证插入顺序

记忆点:需要”字符串键 + 字面量简洁”用普通对象;需要”任意类型键、频繁增删、保持顺序、有 size”用 Map

五、速查表

需求 写法
条件分支 if / else if / else
多分支 switch (x) { case ... }
异常捕获 try {} catch (err) {} finally {}
抛出异常 throw new Error("msg")
计数循环 for (let i = 0; i < n; i++)
遍历值 for (const v of arr)
遍历键 for (const k in obj)
遍历对象键值 for (const [k, v] of Object.entries(obj))
尾部加/删 push / pop
头部加/删 unshift / shift
任意删改 splice(start, n, ...)
是否存在 includes
查找索引 indexOf / findIndex
映射 map(x => ...)
过滤 filter(x => ...)
归并 reduce((acc, x) => ..., 0)
数字升序 sort((a, b) => a - b)
数组去重 [...new Set(arr)]
Set 增删查 add / delete / has / size
Map 增删查 set / get / delete / has / size

六、复习要点

  1. if/for/while/switch 与 Java 基本相同;差异在 switch===catch 不声明类型、throw 可抛任意值。
  2. **for…of 取”值”(数组/字符串/Set/Map),for…in 取”键”**(对象属性名、数组索引)。
  3. 数组是动态的,混合类型;splice 是增删改一体的万能方法。
  4. 高阶方法 map/filter/reduce/some/every/find 对应 Java Stream,不修改原数组
  5. reduce 回调是 (累计值, 当前值),与 Java 的 (初始值, 累加器) 顺序相反。
  6. sort 默认按字符串排序,数字排序必须传 (a, b) => a - b
  7. Set 用于去重([...new Set(arr)]),size 是属性不是方法。
  8. Map 键可以是任意类型,forEach 参数顺序是 (值, 键)
  9. 需要”任意键 / 有 size / 保序”用 Map,否则普通对象即可。
  10. 忘了某条,回本文对应小节看示例即可。

下一篇预告:函数——函数是一等公民、箭头函数、闭包、this 绑定、回调与高阶函数(JS 与 Java 差异最大的部分)。