展开运算符不改变对象属性遍历顺序,严格遵循es2015+插入顺序规则:数字字符串键自动前置,其余字符串键按插入顺序排列;需保序时优先使用map。

展开运算符(...)本身不改变对象属性的遍历顺序,但要注意:它只对**可枚举、自有、字符串键**的属性生效,且遵循 JavaScript 对象属性的遍历规则——即按**插入顺序**(ECMAScript 2015+ 规范明确要求)。
对象属性顺序由插入顺序决定
现代 JavaScript 引擎(V8、SpiderMonkey、JavaScriptCore)均保证:对象中字符串键的遍历顺序 = 属性被添加到对象中的顺序。数字索引键(如 "0"、"1")会被自动排序并前置,其余字符串键按插入顺序排列。
例如:
const obj = { a: 1, 0: 'zero', b: 2, 1: 'one', c: 3 };
console.log(Object.keys(obj)); // ['0', '1', 'a', 'b', 'c']
展开运算符会按 Object.keys() 的顺序提取属性,所以浅拷贝后顺序与原对象一致(含数字键前置规则)。
避免意外打乱顺序的操作
以下操作可能破坏你预期的顺序,需注意:
- 不要用 for...in 遍历后再手动拼对象:虽然通常也按插入顺序,但若原型链上有同名可枚举属性,会混入;且依赖引擎实现细节,不如展开可靠。
-
避免使用 JSON.parse(JSON.stringify(obj)):会丢失函数、undefined、Symbol 键,且所有键转为字符串后,数字键不再“前置”,顺序逻辑被重置(如
{1: 'a', a: 'b'}可能变成{a: 'b', '1': 'a'})。 -
不要用 Object.assign({}, obj) 替代展开:它和展开行为一致(都基于
[[OwnPropertyKeys]]),顺序相同;但语义稍弱,可读性略低,不是问题根源。
需要严格保序?优先用 Map
如果顺序极其关键(比如 UI 渲染依赖键序、或含 Symbol/非字符串键),建议改用 Map:
const map = new Map([['a', 1], [Symbol('x'), 2], ['b', 3]]);
const copiedMap = new Map(map); // 深拷贝 Map 实例(浅拷贝其键值引用)
// Map 迭代器严格按插入顺序,且支持任意类型键
展开运算符不适用于 Map({...map} 无效),但 new Map(map) 是标准、可靠的保序浅拷贝方式。
总结关键点
- 展开运算符拷贝普通对象时,属性顺序与原对象一致(数字字符串键前置 + 其余按插入顺序)。
- 顺序保障依赖引擎符合 ES2015+ 规范,主流环境均满足,无需额外处理。
- 真正影响顺序的是对象创建方式(如字面量书写顺序、动态赋值顺序),而非展开本身。
- 对顺序敏感场景,优先选用 Map,它从设计上就保证插入顺序且无键类型限制。











