for...in 不该遍历数组,因其遍历所有可枚举属性(含继承和非数字键),不保证顺序、不跳过稀疏空位,且易受原型污染或手动添加属性影响。

JavaScript 中的数组不是“类数组对象”,也不是“内存连续的固定大小结构”——它本质是带数字键的特殊对象,length 属性可读写,索引自动触发增删逻辑。
为什么 for...in 不该用来遍历数组
for...in 遍历的是对象的所有可枚举属性(包括继承的、手动添加的非数字键),不保证顺序,也不跳过稀疏空位。数组上一旦执行过 arr.foo = 'bar' 或原型被污染,for...in 就会出错。
- 用
for (let i = 0; i 最安全,尤其在性能敏感或需修改数组时 -
for...of只遍历元素值,跳过空槽(如[1, , 3]中间那个undefined会被跳过) -
forEach()不能break或return跳出,错误处理需靠try/catch包裹回调
push/pop 和 unshift/shift 的性能差异在哪
所有这四个方法都会修改原数组并返回新长度(pop/shift 返回被删元素)。但底层行为不同:
-
push和pop操作末尾:时间复杂度 O(1),几乎无性能顾虑 -
unshift和shift操作开头:需要重排所有索引,O(n) 时间,数组越大越慢 - 若频繁从头增删,考虑用
Deque类库或改用push+reverse等间接策略
哪些操作会改变原数组,哪些不会
区分“变异(mutating)”和“非变异(non-mutating)”是避免隐性 bug 的关键:
立即学习“Java免费学习笔记(深入)”;
- 会改原数组:
push、pop、shift、unshift、splice、sort、reverse、fill、copyWithin - 不改原数组:
slice、concat、map、filter、flatMap、toSorted(ES2024)、with(ES2023) -
splice是唯一既能删又能插还能返删项的多面手,但容易误写成arr.splice(1)(从索引 1 删到末尾)而非arr.splice(1, 1)(只删 1 个)
稀疏数组、负索引、length 手动赋值这些行为看似边缘,但在处理分页数据、WebSocket 流式更新或 legacy API 响应时,很容易踩中。别默认数组“总是致密”的。











