展开运算符 {...obj} 浅拷贝遵循 JavaScript 对象属性遍历规则:数字字符串键(如"0"、"2")按数值排序前置,其余键按插入顺序排列;非数字键顺序由定义或赋值顺序决定;JSON序列化或for...in会破坏顺序;顺序敏感场景应优先使用Map。

展开运算符 {...obj} 做浅拷贝时,属性顺序默认保持原样,但这个“保持”不是无条件的——它严格遵循 JavaScript 引擎对对象属性的遍历规则。关键不在拷贝动作本身,而在于你创建原始对象的方式和键的类型。
数字字符串键会自动前置
如果你的对象里有像 "0"、"1"、"10" 这样的字符串形式数字键,它们会被引擎识别并排序后放在最前面,其余键按插入顺序排列。
-
示例:
const obj = { a: 1, "2": "two", b: 2, "0": "zero" };→Object.keys(obj)返回["0", "2", "a", "b"] - 展开拷贝后的对象也会沿用这个顺序,因为
{...obj}内部依赖的就是Object.keys()的遍历逻辑 - 注意:"10" 会排在 "2" 后面(按数值大小排序),不是按字符串字典序
非数字键严格按插入顺序
只要键不是纯数字字符串(比如 "id"、"name"、"_flag"),它们的顺序就完全由你添加到对象中的先后决定。
- 用字面量定义:
{ x: 1, y: 2, z: 3 }→ 顺序就是 x → y → z - 动态赋值:
const o = {}; o.b = 2; o.a = 1;→Object.keys(o)是["b", "a"] - 展开拷贝不会打乱这个顺序,也不会“修复”或“标准化”它
哪些操作会悄悄破坏顺序?
看似相似的写法,可能绕过标准遍历机制,导致顺序不可靠:
立即学习“Java免费学习笔记(深入)”;
- 不用 for...in:它会遍历原型链上可枚举属性,顺序不保证,且可能混入意外键
-
避免 JSON 序列化再解析:
JSON.parse(JSON.stringify(obj))会把所有键转为字符串,数字键失去“前置”特性,顺序逻辑被重置 - 慎用 Object.assign() 替代展开:行为一致,但语义模糊,容易让人误以为它更“底层”或更可控,其实并无优势
顺序敏感时,直接换 Map
如果 UI 渲染、配置序列、或调试输出依赖精确键序,尤其是涉及 Symbol 键、重复键处理、或需要稳定迭代行为,Map 是更稳妥的选择。
-
const map = new Map([["a", 1], ["b", 2]]);→ 插入顺序即迭代顺序,无例外 - 浅拷贝只需
new Map(map),安全、标准、无歧义 - 展开运算符不能用于 Map(
{...map}无效),这反而提醒你:该用 Map 的地方,就别硬套对象


















