Object.assign仅复制可枚举的自有属性,不处理原型链、属性描述符或微任务;它忽略不可枚举属性和访问器,丢失writable等特性,与异步机制无关。

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

















