Object.getOwnPropertyNames 返回的属性名数组顺序为:先按数值升序排列整数索引属性,再按定义顺序排列非整数索引自有属性;但该顺序不可靠用于精确还原插入时序,因规范仅鼓励而非保证后者顺序。

Object.getOwnPropertyNames 返回的属性名数组,其顺序并非完全随机,而是遵循 JavaScript 引擎的内部规范:**先按属性是否为整数索引(即“数组索引”)分组,再按创建顺序排列**。这意味着,它能在一定条件下反映属性的添加时序,但有明确限制——不能直接、可靠地用于精确还原所有属性的插入顺序。
整数索引属性优先,且按添加顺序排列
如果对象中存在形如 "0"、"1"、"10" 这类可被转换为非负整数且不超出 253−1 的字符串键(即“数组索引”),它们会被提取并**按数值大小升序排列**,而非创建顺序。例如:
obj[1] = 'a'; obj[0] = 'b'; obj['2'] = 'c'; → Object.getOwnPropertyNames(obj) 返回 ["0", "1", "2"]
这个排序由规范强制规定(ECMAScript §9.4.1),与创建先后无关。因此,一旦混入整数索引属性,原始时序就已被覆盖。
非整数索引属性按实际定义顺序保留
对于普通字符串键(如 "name"、"_id"、"2abc" 等不符合数组索引规则的键),现代主流引擎(V8、SpiderMonkey、JavaScriptCore)在大多数情况下会保持其**定义(赋值或 Object.defineProperty)的先后顺序**。
- 直接赋值:
obj.a = 1; obj.b = 2; obj.c = 3;→["a","b","c"] - Object.defineProperty(不可枚举):
Object.defineProperty(obj, "x", {value:1});→ 若未被遮蔽,仍计入顺序 - 注意:使用
for...in或Object.keys()无法探测不可枚举属性;而getOwnPropertyNames可以,这是它在此场景中的关键优势
探测时序的实用边界与陷阱
想靠它“反推创建时间线”,需严格满足以下条件:
- 对象不含任何数组索引属性(避免数值排序干扰)
- 所有目标属性均为自有属性(非原型链继承)
- 未通过
Object.defineProperties批量定义(部分引擎可能打乱顺序) - 避免使用
delete后重写同名属性(V8 中删除后再赋值可能插入到末尾) - 不依赖跨引擎一致性:虽然主流引擎目前行为趋同,但规范未保证非索引属性的顺序,仅“鼓励实现保持插入顺序”(ECMAScript §7.3.21)
替代方案更可靠
若业务逻辑真正依赖属性创建顺序(如序列化、审计、DSL 解析),不应依赖 Object.getOwnPropertyNames 的隐式行为:
- 显式记录时间戳或序号:
obj._meta = { order: ["a","b","c"], timestamp: {...} } - 用
Map替代普通对象——Map的迭代顺序严格等于插入顺序,且规范明确定义(§24.1.5) - 借助 Proxy 拦截 set 操作,动态维护一个有序键列表

















