【JS基础-Day3】循环与数组

2 阅读6分钟

【JS基础-Day3】循环与数组

📺 对应视频:P35-P49 | 🎯 核心目标:掌握 for 循环、嵌套循环、数组的增删改查以及排序算法


一、for 循环

1.1 基本语法

// for (初始化; 条件; 迭代表达式) { 循环体 }
for (let i = 0; i < 5; i++) {
  console.log(i)   // 0 1 2 3 4
}

// 执行顺序:
// 1. let i = 0(只执行一次)
// 2. 检查 i < 5
// 3. 执行循环体
// 4. i++
// 5. 回到步骤2,直到条件为 false

1.2 for 循环的灵活写法

// 倒序循环
for (let i = 10; i >= 0; i--) {
  console.log(i)  // 10 9 8 ... 0
}

// 步长不为1
for (let i = 0; i <= 100; i += 5) {
  console.log(i)  // 0 5 10 15 ... 100
}

// 累加求和
let sum = 0
for (let i = 1; i <= 100; i++) {
  sum += i
}
console.log(sum)  // 5050(高斯求和)

// 累乘(阶乘)
let factorial = 1
for (let i = 1; i <= 5; i++) {
  factorial *= i
}
console.log(factorial)  // 120(5! = 120)

1.3 while vs for 的选择

for 循环:已知循环次数时用
while 循环:不知道循环几次、以条件为终止时用

// 例:读取文件直到结束(while 更合适)
while (!eof) { readLine() }

// 例:遍历1到10(for 更合适)
for (let i = 1; i <= 10; i++) { ... }

二、循环嵌套

2.1 基本概念

// 外层循环控制行,内层循环控制列
for (let i = 1; i <= 3; i++) {        // 外层:3次
  for (let j = 1; j <= 3; j++) {      // 内层:每次3次,共9次
    console.log(`i=${i}, j=${j}`)
  }
}

2.2 经典应用:打印图形

// 打印直角三角形
for (let i = 1; i <= 5; i++) {
  let row = ''
  for (let j = 1; j <= i; j++) {
    row += '★'
  }
  console.log(row)
}
// ★
// ★★
// ★★★
// ★★★★
// ★★★★★

// 打印九九乘法表
for (let i = 1; i <= 9; i++) {
  let row = ''
  for (let j = 1; j <= i; j++) {
    row += `${j}×${i}=${i*j}\t`
  }
  console.log(row)
}

💡 嵌套循环时间复杂度:两层嵌套是 O(n²),三层是 O(n³),数据量大时要警惕性能问题。


三、数组基础

3.1 什么是数组?

数组是一种有序的数据集合,可以存储多个值。

// 创建数组
let arr1 = [1, 2, 3, 4, 5]         // 字面量(推荐)
let arr2 = new Array(3)             // [empty × 3],长度为3的空数组
let arr3 = new Array(1, 2, 3)       // [1, 2, 3]

// 混合类型数组(JS 允许,但不推荐)
let mixed = [1, 'hello', true, null, {name: '张三'}, [1, 2]]

3.2 访问元素

let fruits = ['苹果', '香蕉', '橙子']

// 索引从 0 开始
console.log(fruits[0])   // '苹果'
console.log(fruits[1])   // '香蕉'
console.log(fruits[2])   // '橙子'
console.log(fruits[3])   // undefined(越界)

// 最后一个元素
console.log(fruits[fruits.length - 1])  // '橙子'

// 数组长度
console.log(fruits.length)  // 3

3.3 遍历数组

let scores = [85, 92, 78, 95, 88]

// 方式一:for 循环(经典)
for (let i = 0; i < scores.length; i++) {
  console.log(scores[i])
}

// 方式二:for...of(简洁,ES6)
for (let score of scores) {
  console.log(score)
}

// 方式三:forEach(回调函数,进阶)
scores.forEach((score, index) => {
  console.log(`第${index+1}个:${score}`)
})

四、数组的增删改查

4.1 增加元素

let arr = [1, 2, 3]

// 末尾添加(push)- 返回新长度
arr.push(4)        // [1, 2, 3, 4]
arr.push(5, 6)     // [1, 2, 3, 4, 5, 6]

// 头部添加(unshift)- 返回新长度,性能差
arr.unshift(0)     // [0, 1, 2, 3, 4, 5, 6]

// 指定位置插入(splice)
arr.splice(2, 0, 'a', 'b')  // 在索引2处插入'a','b'
// splice(开始索引, 删除数量, ...插入元素)

4.2 删除元素

let arr = [1, 2, 3, 4, 5]

// 删除末尾(pop)- 返回被删除的元素
let last = arr.pop()   // last=5, arr=[1,2,3,4]

// 删除头部(shift)- 返回被删除的元素,性能差
let first = arr.shift()  // first=1, arr=[2,3,4]

// 删除指定位置(splice)
arr.splice(1, 2)    // 从索引1开始删2个 → arr=[2]
arr.splice(1, 1)    // 删除索引1的元素

4.3 修改元素

let arr = ['a', 'b', 'c']

// 直接索引修改
arr[1] = 'B'   // ['a', 'B', 'c']

// splice 替换
arr.splice(0, 1, 'A')  // 删除索引0,插入'A' → ['A', 'B', 'c']

4.4 查找元素

let arr = [10, 20, 30, 20, 40]

// indexOf:找第一个匹配的索引,没找到返回 -1
arr.indexOf(20)      // 1
arr.indexOf(99)      // -1

// lastIndexOf:从末尾开始找
arr.lastIndexOf(20)  // 3

// includes:是否包含(返回布尔值)
arr.includes(30)     // true
arr.includes(99)     // false

// find:找到第一个满足条件的元素(ES6)
arr.find(item => item > 15)  // 20

// findIndex:找到第一个满足条件的索引(ES6)
arr.findIndex(item => item > 25)  // 2(值30在索引2)

五、数组常用方法

5.1 截取与合并

let arr = [1, 2, 3, 4, 5]

// slice:截取(不修改原数组)
arr.slice(1, 3)    // [2, 3](包含start,不含end)
arr.slice(2)       // [3, 4, 5]
arr.slice(-2)      // [4, 5](负数从末尾数)

// concat:合并数组(不修改原数组)
let a = [1, 2]
let b = [3, 4]
let c = a.concat(b)   // [1, 2, 3, 4]
let d = [...a, ...b]  // [1, 2, 3, 4](展开运算符,更常用)

5.2 转换方法

// join:数组转字符串
[1, 2, 3].join()      // '1,2,3'(默认逗号)
[1, 2, 3].join('-')   // '1-2-3'
[1, 2, 3].join('')    // '123'

// split:字符串转数组(与 join 相反)
'a,b,c'.split(',')    // ['a', 'b', 'c']
'hello'.split('')     // ['h', 'e', 'l', 'l', 'o']

5.3 排序方法

let arr = [3, 1, 4, 1, 5, 9, 2, 6]

// reverse:反转数组(修改原数组)
arr.reverse()  // [6, 2, 9, 5, 1, 4, 1, 3]

// sort:排序(修改原数组)
// ⚠️ 默认按字符串Unicode排序,数字排序需要传比较函数!
[10, 9, 2].sort()                   // [10, 2, 9](字符串排序,错误!)
[10, 9, 2].sort((a, b) => a - b)    // [2, 9, 10](升序)
[10, 9, 2].sort((a, b) => b - a)    // [10, 9, 2](降序)

六、冒泡排序

6.1 算法思路

通过相邻元素比较和交换,每轮把最大值"冒泡"到末尾。

1轮:[5,3,1,4,2] → 比较并交换 → [3,1,4,2,5]5冒到最后)
第2轮:[3,1,4,2,5] → 比较并交换 → [1,3,2,4,5]4冒到倒数第2)
...

6.2 代码实现

function bubbleSort(arr) {
  let len = arr.length
  for (let i = 0; i < len - 1; i++) {        // 外层:控制轮数
    for (let j = 0; j < len - 1 - i; j++) {  // 内层:控制比较次数
      if (arr[j] > arr[j + 1]) {
        // 交换相邻元素
        let temp = arr[j]
        arr[j] = arr[j + 1]
        arr[j + 1] = temp
        // ES6 解构交换写法:[arr[j], arr[j+1]] = [arr[j+1], arr[j]]
      }
    }
  }
  return arr
}

console.log(bubbleSort([5, 3, 1, 4, 2]))  // [1, 2, 3, 4, 5]

时间复杂度分析:

  • 最坏情况(逆序):O(n²)
  • 最好情况(已排序):O(n)(加了优化后)
  • 空间复杂度:O(1)

6.3 优化版(提前终止)

function bubbleSortOptimized(arr) {
  let len = arr.length
  for (let i = 0; i < len - 1; i++) {
    let swapped = false  // 本轮是否发生了交换
    for (let j = 0; j < len - 1 - i; j++) {
      if (arr[j] > arr[j + 1]) {
        ;[arr[j], arr[j + 1]] = [arr[j + 1], arr[j]]
        swapped = true
      }
    }
    if (!swapped) break  // 没有发生交换,说明已经有序
  }
  return arr
}

七、综合练习

练习1:求数组最大值

function getMax(arr) {
  let max = arr[0]
  for (let i = 1; i < arr.length; i++) {
    if (arr[i] > max) max = arr[i]
  }
  return max
}
// 或者使用内置方法:
Math.max(...[3, 1, 4, 1, 5])  // 5

练习2:数组去重

// 方法一:Set(ES6,最简洁)
let unique = [...new Set([1, 2, 2, 3, 3, 4])]  // [1, 2, 3, 4]

// 方法二:filter + indexOf
let arr = [1, 2, 2, 3, 3, 4]
let unique2 = arr.filter((item, index) => arr.indexOf(item) === index)

练习3:找出所有偶数

let numbers = [1, 2, 3, 4, 5, 6, 7, 8]
let evens = numbers.filter(n => n % 2 === 0)  // [2, 4, 6, 8]

八、知识图谱

循环与数组
├── for 循环
│   ├── 基本语法(初始化、条件、迭代)
│   ├── 各种变体(倒序、步长)
│   └── 嵌套循环(打印图形、九九表)
├── 数组
│   ├── 创建:字面量 [] / new Array()
│   ├── 访问:arr[index],长度 .length
│   ├── 遍历:for / for...of / forEach
│   ├── 增:push / unshift / splice
│   ├── 删:pop / shift / splice
│   ├── 改:直接赋值 / splice
│   ├── 查:indexOf / includes / find
│   └── 常用方法:slice / join / sort / reverse
└── 排序算法
    └── 冒泡排序(相邻比较交换,O(n²))

九、高频面试题

Q1: splice slice 的区别?

splice 会修改原数组,可以增删改;slice 不修改原数组,只是截取返回新数组。

Q2:如何判断一个值是数组?

Array.isArray([])    // true,推荐
[] instanceof Array  // true,也可以

Q3:数组的 sort 为什么需要传比较函数?

默认的 sort 把元素转成字符串再比较 Unicode,导致 [10, 9, 2].sort() 得到 [10, 2, 9](字符串 "10" < "2")。传入 (a, b) => a - b 才能正确数值排序。


⬅️ 上一篇Day2 - 运算符与分支 ➡️ 下一篇Day4 - 函数与作用域