Object.assign仅复制可枚举的自有属性,不处理原型链、属性描述符或微任务;它忽略不可枚举属性和访问器,丢失writable等特性,与异步机制无关。
Object.assign 本身不处理继承描述符属性,也不涉及微任务对象。所谓“带有继承描述符属性的微任务对象”并不是 JavaScript 中的标准概念,这个提法容易引发误解。关键要厘清三点:原型链上的属性不会被拷贝、属性描述符(如
、
)不会被复制、Object.assign 和微任务(如
、
)完全无关。
它只复制可枚举的自有属性,不碰描述符
Object.assign 只读取属性的值,不读取或还原
设置的描述符。即使源对象某个属性是
或
,只要它不可枚举,就不会出现在拷贝结果中;如果可枚举,拷贝过去后也只是一个普通数据属性,原始的
、
等描述符全部丢失。
不可枚举属性(例如用
定义的)——直接跳过,不会出现在目标对象上
访问器属性(
/
)——会被执行 getter 并把返回值作为普通数据属性写入,setter 逻辑彻底丢失
只读属性(
)——拷贝后变成可写的普通属性,原保护失效
原型链属性静默忽略,不是“隐患”,是设计如此
Object.assign 不遍历
或
,它内部等价于遍历
的结果。因此,所有定义在原型上的属性,无论是否可枚举、是否有特殊描述符,都完全不会被触及。
比如
、
,或者你手动挂到
上的方法——统统不参与拷贝
这不是 bug,也不是需要“处理”的隐患,而是方法的明确边界:它只做自身属性的扁平合并
若误以为它能“继承合并”,真正的问题在于需求理解偏差,而非 API 行为异常
和微任务毫无关系,别被术语带偏
微任务是事件循环中的执行时机机制(如
),而 Object.assign 是同步的纯数据操作。它既不创建微任务,也不接收微任务对象,更不会因“微任务上下文”改变行为。所谓“微任务对象”并不存在——JavaScript 中没有这种类型。
如果你在 Promise 回调里用了
,它只是恰好在微任务阶段执行,行为与在同步代码中完全一致
异步逻辑中使用浅拷贝带来的问题(如嵌套对象被意外修改),根源是浅拷贝本身,和执行时机无关
真正在意时序或响应性,应关注状态更新是否及时、是否触发了预期副作用,而不是给 Object.assign 强加异步语义
该怎么做才稳妥
根据真实场景选对方式,比强行“处理隐患”更重要:
只需要当前实例状态快照 → 用
或展开运算符
,干净利落
需要保留原型方法或继承结构 → 不要用拷贝,改用
手动重建链
要合并多层配置且含 Symbol 或不可枚举字段 → 改用
配合
显式提取再赋值
嵌套数据可能被修改 → 直接上深拷贝(
(现代环境)、
(简单结构)、或 lodash.merge)
enumerablewritablePromise.thenqueueMicrotaskObject.definePropertyenumerable: falsewritable: falsewritableconfigurableObject.defineProperty(obj, 'x', { value: 1, enumerable: false })getsetwritable: false__proto__Object.getPrototypeOf()Object.keys()Array.prototype.mapObject.prototype.toStringMyClass.prototypePromise.resolve().then()Object.assignObject.assign({}, obj){...obj}Object.setPrototypeOf(clone, Object.getPrototypeOf(obj))Reflect.ownKeys(obj).reduce()Object.getOwnPropertyDescriptor()structuredCloneJSON.parse(JSON.stringify())