背景:
今天因为遇到json diff有多余diff问题,所以就来研究了一下js对象的key值排序,排序之后即可减少多余diff
一、JavaScript 对象属性的排序规则(ES2015+ 规范)
在 ES2015(ES6)之前,规范未定义对象属性的遍历顺序,完全由浏览器引擎实现决定(表现混乱)。ES2015 及后续版本明确了对象属性的排序逻辑,核心分为三类属性,按优先级依次排序:
1. 第一优先级:数值型属性(Numeric Properties)
-
定义:属性名可被强制转换为 32 位无符号整数的属性(如
123、0xFF,但'123.5'不属于此类)。 -
排序规则:严格按照数值大小升序排列(与赋值顺序无关)。
-
示例:
javascript
const obj = { 10: 'a', 2: 'b', 100: 'c' }; console.log(Object.keys(obj)); // 输出 ["2", "10", "100"](按数值升序)
-
2. 第二优先级:字符串型属性(String Properties,非数值)
-
定义:属性名是字符串且无法转换为 32 位无符号整数的属性(如
'name'、'age18'、'123.5')。 -
排序规则:严格按照首次赋值的顺序排列(插入顺序)。
-
示例:
const obj = { name: 'Alice', age: 20, city: 'Beijing' }; console.log(Object.keys(obj)); // 输出 ["name", "age", "city"](与赋值顺序一致)
-
3. 第三优先级:Symbol 型属性(Symbol Properties)
-
定义:属性名是
Symbol类型的属性(如Symbol('id'))。 -
排序规则:严格按照首次赋值的顺序排列(插入顺序),且排在所有字符串属性之后。
- 示例:
const obj = { [Symbol('id')]: 1, name: 'Bob', [Symbol('age')]: 25 }; console.log(Object.keys(obj)); // 输出 ["name"](Symbol 属性不被 keys() 遍历,但内部排序为 name → Symbol(id) → Symbol(age))
- 示例:
二、排序规则的设计原因(基于 ECMA 规范的核心诉求)
ECMA 规范对属性排序的设计,本质是平衡 “开发者直觉” 与 “引擎性能”“历史兼容性” 的结果,具体可拆解为两点:
1. 数值型属性按大小排序:适配 “类数组” 场景的直觉
早期 JavaScript 中,开发者常使用对象模拟数组(如 {0: 'a', 1: 'b'}),但 ES 规范未定义排序前,不同引擎对数值键的处理混乱(如按插入顺序、按哈希表顺序等)。ES2015 明确数值键按大小排序,核心是为了适配开发者对 “索引” 的直觉认知:数值键本质上类似数组的索引(index),而数组的索引天然是按数值升序排列的。这种设计让对象可以更可靠地模拟 “类数组结构”(如 arguments 对象、DOM 集合等),同时避免引擎对数值键的特殊处理逻辑过于复杂。
此外,数值键的排序逻辑基于 “32 位无符号整数”,是因为 JavaScript 数组的索引范围本身就是 0 到 2³²-2(超出则视为常规对象属性),这种对齐减少了引擎的适配成本。
2. 非数值属性按赋值顺序排序:满足 “可预测性” 需求
对于字符串键(如 'name'、'city'),开发者的核心诉求是 “赋值顺序可预测”—— 即先定义的属性先被遍历。ES2015 之前,由于对象底层多基于哈希表实现(哈希表本身无序),遍历顺序不可靠,导致依赖属性顺序的代码(如序列化、配置项遍历)在不同浏览器中表现不一致。
ES2015 强制非数值属性按插入顺序排序,本质是牺牲了部分哈希表的性能优势,换取了开发者对代码行为的可预测性。这一设计符合大多数业务场景的需求(开发者更关心 “顺序可控” 而非极致的属性访问性能),同时也与 Map 数据结构(天然按插入顺序排序)形成了逻辑对齐(Map 是 ES2015 同时引入的,专门用于强调顺序的键值对场景)。
三、特殊注意点
- 兼容性:ES2015 之前的环境(如 IE8 及以下)仍遵循引擎自身的排序逻辑,需避免在旧环境中依赖属性顺序。
- “数值字符串” 的判定:只有能被精确转换为 32 位无符号整数的字符串才视为数值键,例如
'0123'(转换为 83,因前缀 0 视为八进制)、'123.0'(转换为 123)属于数值键;而'123.5'、'abc'属于字符串键。 - 与 Map 的区别:对象的排序是 “混合优先级”(数值键优先),而
Map的所有键(包括数值、字符串、Symbol)均严格按插入顺序排序,且支持更灵活的键类型,适合强依赖顺序的场景。