先记住这个答案
数组本质上是一个以索引为键的对象。空位的意思是这个索引上根本没有属性,比如 new Array(3) 只把 length 设成了 3,索引 0、1、2 都不存在。显式 undefined 则是属性存在,值是 undefined。两者读出来都是 undefined,但数组方法对待它们不同:map、forEach、filter 会跳过空位,只处理真实存在的元素。所以 new Array(3).map(() => 0) 一次回调都不会执行,结果还是三个空位。初始化数组要用 fill 或 Array.from。
- 空位是索引上没有属性,不是存了
undefined map、forEach、filter都会跳过空位- 初始化用
fill或Array.from,别直接map fill({})让所有位置共享同一个对象
同样读出 undefined,结构不同
用 Object.hasOwn 检查索引上到底有没有属性,再看 map 和展开的表现。
undefined 和几种转换JavaScriptconst sparse = new Array(2) // 两个空位
const explicit = [undefined, undefined] // 两个真实的 undefined
console.log(Object.hasOwn(sparse, 0), Object.hasOwn(explicit, 0))
let calls = 0
const mapped = sparse.map(() => { calls++; return 0 })
console.log(calls, Object.hasOwn(mapped, 0)) // 回调没执行,结果还是空位
const copied = [...sparse] // 展开把空位读成 undefined
console.log(Object.hasOwn(copied, 0), copied[0] === undefined)
console.log(JSON.stringify(copied))
console.log(JSON.stringify(Array.from({ length: 2 }, (_, index) => index)))查看输出与解释
false true
0 false
true true
[null,null]
[0,1]第一行说明空位上没有属性,显式 undefined 有。第二行 map 的回调调用了 0 次,返回的 mapped 依然是空位。第三行展开运算符通过迭代器按索引逐个读取,空位读到 undefined 后写进新数组,于是变成了真实的元素。最后 Array.from 带映射函数,给每个位置都生成了实际的值,这是初始化数组最稳的写法。
初始化数组的正确写法
想生成 [0, 0, 0],写 new Array(3).fill(0)。fill 不管索引存不存在,会逐个写入,写完就没有空位了,之后再 map 就能正常工作。想生成 [0, 1, 2] 这样和索引相关的值,用 Array.from({ length: 3 }, (_, i) => i)。
fill对象时的坑- 要生成一个二维数组,写成
new Array(3).fill([])看起来没问题,但三个位置放的是同一个数组。往第一行push一个元素,三行都变了。原因是fill只计算一次参数,把同一个引用写到每个位置。每行都要独立的对象或数组时,用Array.from({ length: 3 }, () => []),工厂函数每次调用都会创建新的。
容易答错的地方
- 用
arr[i] === undefined判断空位 - 显式存了
undefined的位置也会返回true,分不出来。要判断索引上有没有属性,用Object.hasOwn(arr, i)。i in arr也行,但它还会查原型链。 - 说所有数组方法都跳过空位
forEach、map、filter、reduce、some、every会跳过空位。但for...of、展开运算符、Array.from、find、findIndex、includes会把空位当成undefined处理。join会把空位转成空字符串。回答时要说清是哪个方法。
面试官还会怎么问?
delete arr[1] 之后数组长度会变吗?
不会。delete 只删掉索引 1 上的属性,留下一个空位,length 不变。要真正移除元素并让后面的元素前移,用 splice(1, 1)。
稀疏数组是怎么产生的?
除了 new Array(n),还有几种常见情况:字面量里写连续逗号 [1, , 3];给超过当前长度的索引赋值 arr[10] = 1;直接把 length 调大;对元素用 delete。JSON 本身不支持空位,接口返回的数据不会是稀疏的。
为什么不建议使用稀疏数组?
除了方法行为不一致,引擎对稀疏数组通常也无法做连续存储的优化,访问会变慢。业务里需要表示「这个位置没有数据」时,用 null 这样明确的值更清楚。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。