展开运算符合并对象时,后出现的同名属性覆盖前面的值,生成新对象不修改原对象;支持symbol但忽略不可枚举属性;性能适合中小数据量,大数据量时开销大;与object.assign区别在于是否修改原对象。
展开运算符(...)在对象合并时,属性覆盖由**书写顺序决定**:后出现的同名属性会直接覆盖前面的值;它不修改原对象,始终生成新对象。性能上,它写法简洁、可读性高,适合中小数据量场景,但底层需遍历所有可枚举属性,属于浅拷贝,大数据量时开销明显。
属性覆盖规则看位置,不看来源
合并多个对象时,JavaScript 按对象在字面量中从左到右的顺序依次展开:
- 先写的对象属性靠前,后写的同名属性会覆盖它
- 即使两个对象来自变量,也只看它们在
{...a, ...b}中的排列顺序 - Symbol 属性会被展开,但不可枚举属性(如通过
Object.defineProperty设置且enumerable: false)不会被复制
与 Object.assign() 的关键区别
两者都按“后覆盖前”逻辑处理同名属性,但行为本质不同:
- 展开运算符创建全新对象,原对象完全不受影响
-
Object.assign(target, source)是直接修改target,若第一个参数传的是已有对象,它就被改了 - 想安全合并又兼容老环境,可用
Object.assign({}, a, b)模拟展开效果
性能表现要看数据规模
展开运算符不是万能加速器,它的优势和限制都很明确:
- 小数据(几十个键值对以内):语法干净、开发效率高,推荐使用
- 大数据或高频调用:每次都要遍历+分配新内存,时间复杂度 O(n),GC 压力上升
- 替代方案更优的情况:比如需原地更新配置,用
Object.assign(config, updates);或合并超大数组,改用push.apply()或分片处理
React 中 className 的覆盖特例
在 JSX 里,className 遵循同样顺序规则,但常因写法顺序产生混淆:
- 写成
<div classname="a"></div>:props.className 覆盖 "a" - 写成
<div classname="b"></div>:"b" 覆盖 props.className - 实际生效的永远是最后声明的那个
className值,和是否来自展开无关










