Skip to content

数组 (Array)

1. 核心概念与特性

特性描述
动态大小JS 数组无需预先声明长度,它会随着元素的增加自动扩容。
异构性同一个数组可以容纳不同类型的数据(如 [1, "hello", {a: 1}, null])。
索引机制0 开始。通过 arr[index] 访问元素,时间复杂度为 $O(1)$。
本质数组其实是特殊的对象。typeof [] 返回 "object"。判断数组的正确方式是 Array.isArray(arr)

2. 核心操作 API (高频必会)

为了便于记忆,我将数组方法按**“是否会改变原数组”**分为两类,这是面试和开发中最容易踩坑的地方。

2.1 会改变原数组的方法 (Mutating Methods)

执行这些方法后,原数组的数据或长度会被直接修改。

方法描述示例时间复杂度
push()在末尾添加一个或多个元素,返回新长度。arr.push(4)$O(1)$
pop()移除并返回末尾的最后一个元素。arr.pop()$O(1)$
unshift()在头部添加一个或多个元素,返回新长度。arr.unshift(0)$O(n)$ (需移动后续所有元素)
shift()移除并返回头部的第一个元素。arr.shift()$O(n)$
splice()万能方法。用于添加、删除、替换元素。arr.splice(1, 2, 'a') (从索引1删2个,插入'a')$O(n)$
sort()对数组进行排序(默认按字符串的 Unicode 码点排)。arr.sort((a,b) => a - b) (按数字升序)$O(n \log n)$
reverse()颠倒数组中元素的顺序。arr.reverse()$O(n)$
fill()用静态值填充/覆盖数组的部分或全部。arr.fill(0)$O(n)$

2.2 不改变原数组的方法 (Non-Mutating Methods)

执行这些方法会返回一个新数组或新值,原数组保持不变。(在 React/Redux 等强调不可变数据的框架中非常重要)。

方法描述示例时间复杂度
concat()合并两个或多个数组,返回新数组。arr.concat([4,5])$O(n)$
slice()截取数组的一部分,返回新数组(浅拷贝)。arr.slice(1, 3) (取索引 1 到 2)$O(n)$
join()将所有元素连接成一个字符串。arr.join('-')$O(n)$
map()遍历并对每个元素执行回调,返回映射后的新数组arr.map(x => x * 2)$O(n)$
filter()遍历并过滤出满足条件的元素,返回新数组arr.filter(x => x > 5)$O(n)$
reduce()累加器,将数组所有元素计算为一个单一值arr.reduce((acc, cur) => acc + cur, 0)$O(n)$
forEach()仅遍历执行操作,没有返回值(返回 undefined)。arr.forEach(x => console.log(x))$O(n)$
some()是否有至少一个元素满足条件?返回布尔值。arr.some(x => x > 10)最优 $O(1)$, 最差 $O(n)$
every()是否所有元素都满足条件?返回布尔值。arr.every(x => x > 0)最优 $O(1)$, 最差 $O(n)$
find()查找并返回第一个满足条件的元素arr.find(x => x === 3)$O(n)$
findIndex()查找并返回第一个满足条件的索引arr.findIndex(x => x === 3)$O(n)$
includes()判断数组是否包含某个特定值(能识别 NaN)。arr.includes(NaN)$O(n)$
indexOf()查找元素第一次出现的索引(找不到返回 -1)。arr.indexOf(3)$O(n)$

3. 高阶应用技巧

3.1 数组去重

js
const arr = [1, 2, 2, 3, 3];
// 方案一:ES6 Set (最简洁,推荐)
const unique = [...new Set(arr)]; 

// 方案二:filter + indexOf
const unique2 = arr.filter((item, index) => arr.indexOf(item) === index);

3.2 拍平多维数组 (Flatten)

js
const arr = [1, [2, [3, 4]]];
// 方案一:ES2019 flat (参数为深度,Infinity 表示无限深)
const flatArr = arr.flat(Infinity); // [1, 2, 3, 4]

// 方案二:reduce 递归
const flatten = (ary) => ary.reduce((acc, val) => 
  acc.concat(Array.isArray(val) ? flatten(val) : val), []
);

3.3 创建指定长度的初始化数组

js
// 创建一个长度为 5,全是 0 的数组
const arr1 = new Array(5).fill(0); // [0, 0, 0, 0, 0]

// 创建一个 1 到 5 的数组
const arr2 = Array.from({ length: 5 }, (_, i) => i + 1); // [1, 2, 3, 4, 5]

4. 常见问题 (FAQ)

4.1 为什么 [1, 2, 10].sort() 的结果是 [1, 10, 2]

  • :JS 数组的 sort() 默认将元素转换为字符串,然后比较它们的 UTF-16 码点顺序。字符串 "10" 的第一位 "1""2" 小,所以排在前面。
  • 解决:必须传入比较函数:arr.sort((a, b) => a - b);

4.2 for...infor...of 遍历数组有什么区别?

  • for...in:遍历的是索引(键),且会遍历出原型链上被添加的自定义属性。极不推荐用于遍历数组。
  • for...of:ES6 新增,专门用于遍历实现了 Iterator 接口的数据结构(如数组)。它遍历的是,安全且高效。

4.3 forEach 循环中能使用 breakreturn 跳出循环吗?

  • 答:不能。 forEach 无法通过常规的 break 中断。return 仅仅是跳出当前这一个回调函数(类似于 continue)。
  • 解决:如果需要提前中断循环,请使用普通的 for 循环,或者 some()every()

4.4 V8 引擎底层是如何实现 JS 数组的?

  • :JS 数组在底层并不是纯粹的一片连续内存。V8 引擎有两种数组模式:
    1. 快数组 (Fast Elements):如果你的数组类型一致(如全是数字),且索引紧凑没有大跳跃,V8 会分配一块连续的线性内存,像 C 语言数组一样高效。
    2. 慢数组 (Dictionary Elements):如果你的数组充满空洞(如 arr[0]=1; arr[10000]=2;),或者混杂了各种对象,V8 会把它转为**哈希表(字典)**存储。这样节省了内存,但查找和插入的时间复杂度退化。

4.5 Array.prototype.slice.call(arguments) 是什么意思?

  • :在 ES6 箭头函数和 Rest 参数 ... 出现之前,这是一种将类数组对象(如函数内部的 arguments、DOM 节点集合 NodeList)转换为真正数组的黑魔法。现在更推荐使用 Array.from(arguments)[...arguments]