JavaScript 稀疏数组和数组空位是什么?遍历时有什么差异?
简化版
稀疏数组是存在“空位”的数组。空位不是显式的 undefined,而是这个索引根本没有属性。
不同遍历方法对空位处理不同:map、forEach 会跳过空位,for...of 会把空位读成 undefined,Object.keys 只返回真实存在的索引。面试中要区分“值为 undefined”和“索引不存在”。
详细版
数组本质上也是对象,索引是特殊的属性名。下面两个数组看起来相似,但内部不同。
const a = [undefined];
const b = [];
b.length = 1;
console.log(0 in a); // true
console.log(0 in b); // false
a[0] 这个属性存在,值是 undefined;b[0] 这个属性不存在,只是 length 为 1。
遍历差异:
const arr = [1, , 3];
arr.forEach(x => console.log(x)); // 1, 3
console.log(arr.map(x => x * 2)); // [2, empty, 6]
console.log([...arr]); // [1, undefined, 3]
console.log(Object.keys(arr)); // ["0", "2"]
实际开发中,应避免制造稀疏数组。需要固定长度时可以用 Array.from({ length: n }, ...) 创建真实元素。
完整版教学
一、空位和 undefined 的区别
空位表示某个索引没有对应属性,undefined 表示属性存在但值是 undefined。
const withUndefined = [undefined];
const withHole = new Array(1);
console.log(withUndefined[0]); // undefined
console.log(withHole[0]); // undefined
console.log(0 in withUndefined); // true
console.log(0 in withHole); // false
两者读取结果一样,但存在性不同。
面试里最关键的判断不是
arr[i] === undefined,而是i in arr或Object.hasOwn(arr, i)。
二、稀疏数组怎么产生
常见来源有三类。
const a = [1, , 3];
const b = [];
b[3] = "x";
const c = new Array(5);
b 的长度是 4,但只有索引 3 存在。c 的长度是 5,但 0 到 4 都是空位。
数字例子:
| 表达式 | length | 实际存在的索引 |
|---|---|---|
[1, , 3] | 3 | 0、2 |
new Array(3) | 3 | 无 |
[undefined, undefined] | 2 | 0、1 |
三、不同遍历 API 的处理差异
forEach、map、filter、some、every 通常跳过空位,因为它们只访问存在的元素。
const arr = [10, , 30];
arr.forEach((v, i) => console.log(i, v));
// 0 10
// 2 30
for...of 通过数组迭代器读取每个位置,空位会表现为 undefined。
for (const item of arr) {
console.log(item);
}
// 10
// undefined
// 30
四、map 会保留空位
map 跳过空位,但结果数组会保留对应空位。
const arr = [1, , 3];
const next = arr.map(x => x * 2);
console.log(next); // [2, empty, 6]
console.log(1 in next); // false
这会导致后续链式处理出现不直观结果。比如你以为数组长度为 3 就处理了 3 个元素,实际回调只执行了 2 次。
五、数组方法和对象属性视角
数组索引本质上是对象属性。
const arr = [1, , 3];
console.log(Object.keys(arr)); // ["0", "2"]
console.log(arr.length); // 3
length 表示最大索引加一,不表示真实元素数量。统计真实元素数量要看 keys 或显式过滤。
const realCount = Object.keys(arr).length; // 2
六、如何创建没有空位的数组
需要初始化固定长度数组时,更推荐 Array.from。
const list = Array.from({ length: 3 }, (_, i) => i + 1);
console.log(list); // [1, 2, 3]
如果使用 new Array(3).map(...),回调不会执行。
console.log(new Array(3).map(() => 1)); // [empty × 3]
七、常见误区与追问
- 误区:空位就是 undefined。 空位是索引不存在,
undefined是索引存在但值为undefined。 - 误区:数组 length 等于真实元素个数。
length主要受最大索引影响。 - 误区:
new Array(3).map会执行三次。 空位会被map跳过。 - 误区:所有遍历方式处理空位都一样。
forEach和for...of的表现不同。 - 追问:如何判断某个索引是否真实存在? 可以用
index in arr或Object.hasOwn(arr, index)。 - 追问:业务里为什么要避免稀疏数组? 它会让遍历、序列化、统计和展示出现不一致。
八、加强记忆
把数组想成带 length 的对象:
[1, , 3]
├─ property "0" = 1
├─ property "1" missing
├─ property "2" = 3
└─ length = 3
答题顺序可以是:先定义空位,再用 0 in arr 区分 undefined,接着讲遍历差异,最后强调初始化数组用 Array.from,减少稀疏数组带来的隐性问题。