A32. 数组 流程控制
2.1 🌟 数组的创建
Python 里用
numbers = [1,2,3]
创建列表,这和 JavaScript 的方式也十分相似。
JavaScript 中的数组主要有三种创建方式:
2.1.1. 🌟 数组字面量
数组字面量以 []
包裹,元素之间用英文逗号分隔。
// 类似 Python 的列表字面量
const numbers = [1, 2, 3]; // 基本用法
const arr1 = [1, 'hello', true]; // 支持多类型混用,但不推荐
const arr2 = [[1,2], [3,4]]; // 数组也可以在内部嵌套数组
注意空位陷阱
若无意间连续打了两个逗号,[1, , 3]
会创建一个长度为 3 的数组,中间位置为空。应避免使用逗号分隔符时的空位,因为它可能导致意料之外的行为:在遍历时该位置被跳过。
为什么用 const
定义数组?
const
表示变量不能被重新赋值(即不能将整个数组替换为新数组),但数组内容可以修改。
而我们也常常不希望数组被无意间完全替换,所以通常用 const
定义数组。
例如:arr1.push(4)
是允许的,但 arr1 = [1,2,3]
会报错。
2.1.2. ⭐ Array 构造函数
构造函数 Array
可以用来创建数组,注意所有构造函数需要通过 new
关键字来调用。
// 无参数:创建空数组
const arr3 = new Array();
// 传入数字作为长度
const arr4 = new Array(3); // [空 × 3]
arr4.fill(0); // [0, 0, 0] | 推荐立即用 .fill() 初始化
// 传入多个参数
const arr5 = new Array(1, 2, 3); // [1, 2, 3]
潜在风险
这种传入参数的做法事实上并不被推荐。例如 new Array(x)
,
- 若
x
是数字,则创建长度为x
的空数组; - 若
x
不是数字,则创建包含x
作为唯一元素的数组。
所以,为了避免这种潜在的错误,最好确保 x
为数字,或直接使用数组字面量 [x]
,或使用 .push()
一步步添加元素。(见下)
2.1.3. Array.of 方法
Array.of
方法用于创建一系列包含给定值的数组,无论这些值的类型如何。很少用。
const arr6 = Array.of(1, 2); // [1, 2]
const arr7 = Array.of(3); // [3]
2.1.4. 连接数组
可以使用 concat
方法将多个数组连接成一个新数组,而不改变原来的数组。
const a = [1, 2];
const b = [3, 4];
const c = a.concat(b); // [1, 2, 3, 4]
总结:
方法 | 常用度 | 特点 |
---|---|---|
数组字面量 | ⭐⭐⭐⭐ | 简洁直观,推荐使用 |
Array 构造函数 | ⭐⭐ | 需谨慎处理参数类型 |
Array.of | ⭐ | 极少使用 |
2.2. 🌟 数组的长度
数组最关键的要素是长度。在 Python 中通过
len(arr)
来获取数组长度,而在 JavaScript 中则是arr.length
。
const arr8 = [1, 2, 3];
console.log(arr8.length); // 3
// ❌ 最好不要像下面这样修改长度,容易出错
arr8.length = 5; // 自动填充“空”到第5位
console.log(arr8); // [1, 2, 3, 空 × 2]
TIP
长度与索引关系: arr.length
总是比最大索引大 1(索引从 0 开始)。
2.3. 🌟 数组元素的增删改查
和 Python 类似,数组元素的增删改查都有对应的方法。不过,JavaScript 有一个最厉害的函数:
splice
。
2.3.1. 🌟 在首尾添加元素
const arr9 = [1, 2];
arr9.push(3); // 返回新长度 3 → [1, 2, 3]
arr9.unshift(0); // 返回新长度 4 → [0, 1, 2, 3]
2.3.2. 🌟 删除首尾元素
const arr10 = [0, 1, 2, 3];
arr10.pop(); // 返回被删除的元素 3
arr10.shift(); // 返回被删除的元素 0
2.3.3. 🌟 修改单个元素
注意索引从 0 开始。
const arr11 = [10, 20, 30];
arr11[1] = 25; // 修改了第 2 个元素 → [10, 25, 30]
2.3.4. 🌟 查询元素
const arr12 = [10, 20, 30, 20];
// 返回子数组(索引左开右闭)
arr12.slice(1, 3); // 第 [1,3) 个索引,[20, 30]
// 查找第一个索引(-1 表示不存在)
arr12.indexOf(20); // 1
arr12.indexOf(40); // -1
2.3.5. ⭐ 任意位置的插入和删除
const arr13 = [1, 2, 3, 4];
// 删除 2 个元素,插入新元素
arr13.splice(1, 2, 'a', 'b'); // 返回 [2, 3]
// 新数组 → [1, 'a', 'b', 4]
闭包传参
在 splice
中插入多个元素时,应通过 逗号分隔的多个参数 传递(如 'a', 'b'
),而不是直接传入一个数组。
若直接传入数组(如 [1,2]
),splice
会将其视为 单个元素 插入,而非展开多个值。 示例:
const arr = [1, 2];
const values = [3, 4];
arr.splice(2, 0, values); // ❌ 插入整个数组作为单个元素 → [1, 2, [3,4]]
arr.splice(2, 0, 3, 4); // ✅ 插入多个参数 → [1, 2, 3, 4]
arr.splice(2, 0, ...values); // ✅ 使用 ... 展开数组,插入展开的元素 → [1, 2, 3, 4]
2.3.6. ⭐ splice 方法的常见技巧
1. 删除元素
arr13.splice(index, count); // 删除 index 开始的 count 个元素
2. 插入元素
arr13.splice(index, 0, ...items); // 删除 0 个元素,即忽略删除步骤;只从 index 插入 items
3. 替换元素
arr13.splice(index, count, ...items); // 删除 count 个,替换为 items
2.3.7 数组方法总结
方法名 | 作用 | 参数 | 返回值 | 示例 |
---|---|---|---|---|
push | 尾部添加 | ...items | 新长度 | [1].push(2) → 2 |
pop | 删除末尾元素 | 无 | 被删元素 | [1,2].pop() → 2 |
unshift | 头部添加 | ...items | 新长度 | [2].unshift(1) → 2 |
shift | 删除首元素 | 无 | 被删元素 | [1,2].shift() → 1 |
slice | 截取子数组 | start, end | 新数组 | [1,2,3].slice(1) → [2,3] |
splice | 删除/插入元素 | index, count, ...items | 被删元素数组 | [1,2].splice(1, 0, 3) → [] |
indexOf | 查找索引 | searchElement | 索引或 -1 | [1,2].indexOf(2) → 1 |
2.4 🌟 if 关键字及其分支结构
2.4.1. 🌟 基础 if 结构
if (confirm("确认执行?")) {
alert("已确认!");
}
真假值
在 if
中,表达式会被自动转换为布尔值,转换为 true
才运行内部语句。
JavaScript 中 0
、""
、null
、undefined
、NaN
都为假值。
所以,if (0)
不会运行,但 if ("1")
会运行。
2.4.2. 🌟 if...else 结构
if (age >= 18) {
console.log("成年人");
} else {
console.log("未成年人");
}
与 Python 的区别
- 条件必须用括号包裹,后面必须跟大括号
{}
- 单行省略大括号:js但这种方式容易引发错误(比如加代码后,本来要加入内部的代码行被判定为外部)。
if (a === 1) console.log("Hello");
2.4.3. 🌟 多重条件判断
WARNING
在 Python 中使用 elif
,但在 JavaScript 中使用 else if
。
if (score >= 90) {
console.log("A");
} else if (score >= 80) {
console.log("B");
} else {
console.log("C");
}
TIP
else if 与多个 if 的区别:
else if
是互斥的,一旦满足某个条件,后续条件不再判断- 多个独立
if
可以同时满足多个条件jsif (true) { ... } if (true) { ... } // 继续执行
2.5. ⭐ while 关键字及其循环结构
最常见的循环需要循环条件和循环体:什么时候继续循环?循环的时候做什么?while 循环就是最基础的循环结构。
形式为 while (condition) { code }
,其中 condition
是一个布尔表达式,满足时一直执行,code
是循环体,需包含对相应变量的更新。
同样地,JavaScript 中 while
循环也需要用大括号 {}
包裹,条件也需要用小括号 ()
包裹。
let i = 0;
while (i < 3) {
console.log(i);
i++; // 忘记更新会导致死循环!
}
死循环问题
若循环条件变量忘记更新(如 i++
),程序将无限运行直至崩溃。
2.6. 🌟 for 关键字及其循环结构
while 循环虽然具有通用的特性,但常常需要在循环开始前初始化循环变量,以及在循环体中更新循环变量,这些都很容易忘记,循环体外的变量也容易造成混淆。为此,我们有 for 循环,提炼常见循环的流程,简化开发。
2.6.1. 🌟 基础 for 循环
// 初始化;条件;更新
for (let i = 0; i < 3; i++) {
console.log(i);
}
注意语法:for ( ; ; ) {}
,两个分号将括号内分为三部分结构:
部分 | 说明 |
---|---|
初始化 | let i = 0 |
条件 | i < 3 |
更新 | i++ |
WARNING
常见陷阱:
- ❌ 索引越界:js
const arr15 = [1, 2]; for (let i = 0; i <= arr15.length; i++) { // 不应为 <=,而是 < console.log(arr15[i]); // 最后一次访问 arr15[2] 为 undefined }
- ❌ 在循环体内修改
arr.length
:jsconst arr16 = [1, 2, 3]; for (let i = 0; i < arr16.length; i++) { arr16.length = 1; // 循环提前终止 console.log(arr16[i]); }
- (不推荐)用
var
声明i
甚至不使用let
、var
、const
的任意一种:jsvar i = 0; for (var i = 0; i < 3; i++) { // ... } console.log(i); // 3(在外部仍然能使用该循环变量,但此时这个变量的意义已经不大)
2.6.2. 🌟 for ... of 循环
为了解决数不清楚索引的问题或代码重复性太高的问题,对于数组(或其他可迭代对象),我们可以使用 for ... of
循环。
const arr14 = [10, 20, 30];
for (const item of arr14) {
console.log(item); // 直接获取元素值
}
2.6.3. ⭐ for ... in 循环
for ... of
中损失了索引信息,若需要索引信息,我们可以使用 for... in
循环。
const arr15 = [10, 20, 30];
for (const index in obj) {
console.log(index); // "0", "1", "2"
console.log(index + 1); // ❌ "01", "11", "21"
console.log(Number(index) + 1); // ✅ 1, 2, 3
}
索引类型
在 for... in
循环中,key
是字符串类型,而不是数字类型。
这是因为 for... in
循环遍历的是对象的属性名,而许多对象的属性名无法用数字表示,所以为了兼容统一,全部返回字符串。
2.7. 🌟 break 和 continue 关键字
基础的循环结构比较死板,但有时循环到某种程度就可以“到此为止”了。比如,用枚举法求最小符合条件的整数,一旦有一个符合条件,后续就无需再枚举了,这时就需要灵活地跳出循环。
2.7.1. 🌟 break 关键字
for (let i = 0; i < 10; i++) {
if (i === 5) break; // 到 5 直接打断,跳出循环
console.log(i); // 全过程只输出了 0~4
}
2.7.2. ⭐ continue 关键字
for (let i = 0; i < 5; i++) {
if (i % 2 === 0) continue; // 到偶数时终止本次迭代,继续进行下一次迭代。
console.log(i); // 全过程输出了 1, 3
}
知识回顾
数组创建与操作
- 数组创建方式:
- 字面量:
const arr = [1,2,3]
(推荐) - 构造函数:
new Array()
(需注意参数类型) - Array.of:
Array.of(1, 2)
(少用)
- 字面量:
- 数组操作方法:
- 首尾增删:
push
/pop
、unshift
/shift
- 修改元素:
arr[index] = value
- 查询元素:
slice(start, end)
、indexOf(searchElement)
- 任意位置操作:
splice(index, count, ...items)
(核心方法,先删除再插入)
- 首尾增删:
- 数组长度特性:
arr.length
动态可变,但不等于实际元素数(可能包含undefined
)- 索引从 0 开始,最大索引为
length - 1
- 数组创建方式:
流程控制
- 分支结构:
if (条件) { ... }
:条件必须加括号if...else
:互斥分支else if
:多级互斥条件- 与 Python 的区别:不依赖缩进,必须用
{}
包裹代码块
- 循环结构:
while
:先判断条件,再执行循环体for
:三部分结构(初始化、条件、更新)for...of
:遍历元素值for...in
:遍历索引(对象属性名)- 常见陷阱:
- 死循环(忘记更新循环变量)
- 索引越界(
i <= arr.length
) - 修改
arr.length
导致循环异常 - 作用域污染(用
var
声明循环变量) for...in
索引类型为字符串
- break:立即退出当前循环
- continue:跳过当前迭代,进入下一次循环
- 分支结构:
课后练习
(单选)以下代码执行后,
arr17
的值是什么?jsconst arr17 = [1, 2, 3]; arr17.splice(1, 0, 4);
- A.
[1, 4, 2, 3]
- B.
[4, 1, 2, 3]
- C.
[1, 2, 3, 4]
- D.
[1, 2, 4, 3]
- A.
(编程)使用三种
for
循环的方式打印列表中元素的 2 倍:jsconst arr18 = [1, 2, 3]; for (_____________) { console.log(_________________); }
(纠错)运行这段代码后,浏览器卡死,可能是什么原因?
jsfor (var i = 1; i <= 10; i--) { console.log(i); }
(编程)用
splice
方法将[1, 2, 3, 4, 5]
改为[1, 2, 'X', 4, 5]
。jsconst arr19 = [1, 2, 3, 4, 5]; arr19.________(___________);
参考答案
A
解析:在索引为1
的元素(2)前插入了4
,结果为[1, 4, 2, 3]
。- javascript
// 方法 1:传统 for for (let i = 0; i < arr18.length; i++) { console.log(arr18[i] * 2); } // 方法 2:for...of for (const item of arr18) { console.log(item * 2); } // 方法 3:for...in for (const key in arr18) { console.log(arr18[key] * 2); }
i--
导致i
越来越小,循环条件i <= 10
永远成立,形成死循环。arr19.splice(2, 1, 'X')
解析:- 从索引
2
开始删除1
个元素(即3
) - 在索引
2
插入'X'
- 从索引