对象属性遍历顺序非随意:v8将整数索引键(如1、"42")存入elements区并按数值升序优先遍历,其余键存入properties区按插入顺序遍历;symbol键排最后且保序。

对象属性的遍历顺序不是随意的,V8 引擎对整数索引键(array-indexed properties)做了明确区分和特殊处理——它把这类键单独归类、单独存储、优先遍历。
整数索引键会被识别并存入 elements 区域
V8 会将形如 1、99、"123"(能被转为有效整数的字符串)这类 key 视为“数组索引属性”,不看声明顺序,只看是否符合整数索引定义(即 ToString(ToInt32(k)) === k 且在 [0, 2^32-2] 范围内)。满足条件的键会被存进对象的 elements 数组,而非常规的 properties 表。
-
this[5] = "a"→ 进入 elements -
this["42"] = "b"→ 进入 elements(字符串但可转为整数) -
this["4.5"] = "c"→ 进入 properties(非整数字符串) -
this["foo"] = "d"→ 进入 properties
遍历时先排 elements,再排 properties
执行 for...in 或 Object.keys() 时,V8 严格按以下两阶段输出:
- 第一阶段:遍历 elements 中所有存在的整数索引键,按数值升序排列(1 → 5 → 42 → 100)
- 第二阶段:遍历 properties 中的其余键,按插入顺序排列("A" → "B" → "C")
这就是为什么 {1: 'a', 100: 'z', B: 'y', A: 'x'} 的 Object.keys() 总是返回 ['1', '100', 'B', 'A'] —— 数字部分排序,字符串部分保序。
快属性机制进一步强化了这种分离逻辑
当命名属性(properties)数量 ≤ 10 时,V8 直接把它们作为“对象内属性”(in-object properties)存到对象头里,查找更快;一旦超限,就移入独立的 properties 哈希表。但这个优化只影响命名属性,不影响 elements 的整数索引键。
- 整数索引键永远走 elements 路径,与快/慢属性无关
- 哪怕你只设了一个
this[999999] = 'x',它也会触发 elements 分配,哪怕其他 9 个都是字符串 key - 这种分离让 V8 能对密集数组使用连续内存 + 线性访问,对稀疏对象用哈希查找,兼顾性能与灵活性
注意边界情况:浮点数、负数、超大数都不算整数索引
只有满足 ECMAScript 规范中 IsArrayIndex 判定的 key 才进 elements:
-
this[-1] = 'a'→ properties(负数不行) -
this[2**32] = 'b'→ properties(超出2^32 - 2) -
this["01"] = 'c'→ properties(ToString(ToInt32("01")) === "1" ≠ "01") -
this[1.0] = 'd'→ elements(1.0是合法整数)
这些判定细节决定了属性最终落点,也解释了为什么看似相似的 key 在遍历时位置差异巨大。










