object.assign 底层是受控的可枚举自有属性复制,非继承;需遍历源对象、校验 hasownproperty 和 enumerable,用 getownpropertydescriptor + defineproperty 处理 getter/setter,区分混入原型或实例,并显式支持 symbol 与不可枚举属性。

重写 Object.assign 的底层逻辑,不是为了替代它,而是为了看清混入(Mixin)真正发生什么:它本质是**受控的属性复制**,而非继承或类型融合。理解这一点,才能避开常见陷阱,比如方法覆盖、this丢失、getter/setter失效。
混入的核心动作就是“复制可枚举自有属性”
原生 Object.assign 的行为可简化为三步:遍历源对象、检查是否为自有属性、检查是否可枚举、然后赋值到目标对象。自己实现一个最小可用版,能立刻暴露关键细节:
- 只处理
for...in能访问到的属性(即可枚举),跳过Object.defineProperty设置的不可枚举属性 - 只复制源对象自身的属性(
hasOwnProperty为 true),不复制原型链上的方法 - 遇到同名属性时,后一个源对象会直接覆盖前一个——混入顺序决定优先级
手动实现时必须显式处理 getter/setter
原生 Object.assign 会把 getter 当作普通属性值读取一次,再写死为静态值,导致响应式丢失。要真正复用访问器逻辑,得用 Object.getOwnPropertyDescriptor + Object.defineProperty:
- 对每个属性调用
getOwnPropertyDescriptor,拿到包括get、set、enumerable、configurable在内的完整描述符 - 再用
defineProperty把整个描述符原样“嫁接”过去,而不是简单赋值 - 否则像
computed类逻辑或依赖this的 getter 就会失效
混入到原型 vs 混入到实例,目的完全不同
把混入应用到 Constructor.prototype(如 Object.assign(Person.prototype, Mixin)),所有实例共享方法,节省内存;而混入到单个实例(如 Object.assign(person, Mixin)),方法私有、可独立修改,但每实例一份,开销大。重写时需明确目标:
- 混入原型:适合通用行为(日志、事件绑定),注意避免污染全局原型
- 混入实例:适合配置化扩展(如给某个对象临时加校验能力),需确保方法内
this指向正确 - 若混入对象含
constructor,必须手动删除,否则影响instanceof判断
现代写法中,Symbol 属性和不可枚举方法仍需额外支持
Object.assign 默认忽略 Symbol 键和不可枚举属性(如 Object.keys 不返回的那些)。如果你的 Mixin 包含 Symbol 方法(如 [Symbol.iterator])或通过 Object.defineProperty 添加的工具方法,手动实现时就得主动收集:
- 用
Object.getOwnPropertyNames+Object.getOwnPropertySymbols合并所有键 - 对每个键调用
getOwnPropertyDescriptor,统一处理 - 否则像 Vue 的
$emit或自定义迭代器这类能力就无法被混入











