Object.assign 合并配置会丢失原型链,因其只复制自有可枚举属性;应区分配置数据与行为原型,合并前提取原型默认值、合并后显式控制目标对象原型,或主动清空原型以确保安全。

直接用 Object.assign 合并配置对象时,原型链信息会彻底丢失——这不是 bug,而是它的设计本意。它只复制自有可枚举属性,对 __proto__、constructor、原型上的方法或默认值一概无视。要避免“非预期丢失”,关键不是让 Object.assign 去处理原型,而是**在合并前明确原型角色,在合并后主动重建或保留必要继承关系**。
明确区分“配置数据”和“行为原型”
配置对象通常只需状态,不需要方法或动态计算逻辑。若源对象的原型上定义了 timeout、retry 等默认值,不应依赖 Object.assign 自动继承,而应显式提取:
- 用
Object.getPrototypeOf(obj)拿到原型对象 - 用
Object.getOwnPropertyNames()获取原型上的自有属性名(如['timeout', 'baseURL']) - 构造一个纯数据默认配置对象:
{ timeout: 5000, baseURL: '/api' } - 再用
Object.assign(defaults, userConfig)实现“原型值为底、实例值覆盖”
合并目标对象必须控制原型归属
别把 {} 当万能目标。它原型是 Object.prototype,容易被污染或干扰:
- 安全场景(如纯配置字面量):用
Object.create(null)创建无原型目标,杜绝__proto__、toString等意外注入 - 需保留特定行为(如自定义
toJSON):先用Object.setPrototypeOf({}, MyConfig.prototype)显式挂载,再合并 - 绝对避免:
Object.assign(userInput, maliciousObj)—— 用户输入若含{"__proto__": {"admin": true}}会直接污染全局原型链
对多级原型配置,手动遍历+扁平化合并
Object.assign 不支持多级原型自动拉取。若配置类有父类、祖父类多层默认值,需递归提取:
- 写一个辅助函数,从实例开始,逐层调用
Object.getPrototypeOf,直到到达Object.prototype或null - 每层用
Object.getOwnPropertyNames收集键,跳过constructor、__proto__等危险名 - 按“子类优先”顺序合并(即实例属性 > 子类原型 > 父类原型),保证就近覆盖
- 最终结果仍是普通对象,但内容已包含完整层级的默认配置
不依赖原型链时,就彻底切断它
很多配置合并根本不需要原型参与。此时最稳妥的做法是“主动清空”:
- 对每个源对象,先做一次浅克隆:
const cleanSrc = { ...src }或Object.assign({}, src) - 这一步天然剥离了所有原型属性,只留下自身可枚举字段
- 再用
Object.assign合并这些干净副本,逻辑清晰、行为可预测 - 特别适合 JSON 解析结果、表单提交数据等不可信输入源

















