处理如何用 Object.assign 实现简单的多级配置对象合并这类问题时,先确认目标场景,再按步骤核对配置或玩法细节。
Object.assign仅浅拷贝,多级对象会覆盖而非递归合并;需手写deepAssign实现深层合并,注意过滤null、数组、日期及__proto__等危险键;现代环境可用structuredClone+递归提升安全性。
它只会做浅拷贝,遇到对象属性时直接覆盖而非递归合并。比如 Object.assign({a: {x: 1}}, {a: {y: 2}}) 结果是 {a: {y: 2}},x 丢了。这不是配置合并想要的行为——你通常希望 a.x 和 a.y 同时保留。
可以封装一个 deepAssign 函数,在遇到同为对象的属性时递归调用自身,其他情况交给 Object.assign 处理。注意要跳过 null、数组、日期等非 plain object 类型:
function deepAssign(target, ...sources) { sources.forEach(source => { if (source == null) return; Object.keys(source).forEach(key => { const targetVal = target[key]; const sourceVal = source[key]; if ( targetVal != null && typeof targetVal === 'object' && sourceVal != null && typeof sourceVal === 'object' && !Array.isArray(targetVal) && !Array.isArray(sourceVal) ) { deepAssign(targetVal, sourceVal); } else { target[key] = sourceVal; } }); }); return target;}
使用示例:deepAssign({}, {db: {host: 'localhost', port: 5432}}, {db: {port: 5433, ssl: true}}) → {db: {host: 'localhost', port: 5433, ssl: true}}
Object.assign 只复制可枚举自有属性,但你的配置对象若来自 Object.create(null) 或带自定义 toString 等方法,可能漏掉关键字段__proto__ 或 constructor 字段(尤其从 JSON 解析或用户输入来),直接合并可能触发原型链污染 —— 建议在递归前过滤掉这些危险键名Date、RegExp 当普通对象递归,它们的 typeof 也是 'object',但没 keys 可遍历,会报错现代环境(Chrome 98+、Node.js 17+)可用 structuredClone 先深拷贝目标,再逐层 Object.assign,避免修改原对象;但要注意它不支持函数、undefined、循环引用。对于配置合并这类纯数据场景够用:
function safeDeepMerge(target, ...sources) { const result = structuredClone(target); sources.forEach(src => { Object.keys(src).forEach(key => { const t = result[key]; const s = src[key]; if (t && s && typeof t === 'object' && typeof s === 'object' && !Array.isArray(t) && !Array.isArray(s)) { result[key] = safeDeepMerge(t, s); } else { result[key] = s; } }); }); return result;}
真正复杂配置(如含函数、Symbol、特殊类实例)建议用专门库如 lodash.merge,自己手写容易漏边界条件。