可以实现基于原型链的对象合并函数,它通过object.getprototypeof逐级上溯收集所有可枚举属性,用weakmap避免循环引用,并按原型链顺序(object.prototype→构造函数原型→实例自身)合并,后出现同名属性覆盖先出现者。

可以实现一个基于原型链的对象合并函数,它在遍历时不仅处理自身属性,还沿原型链向上收集可枚举属性(包括来自 Object.prototype 的方法),同时避免重复和循环引用。关键在于用 Object.getPrototypeOf 逐级上溯,并用 WeakMap 记录已访问对象防止死循环。
核心思路:遍历原型链 + 属性收集
JavaScript 中每个对象都有隐式原型(__proto__ 或 Object.getPrototypeOf),属性查找会沿这条链进行。合并时若只用 Object.assign 或展开运算符,只能拿到对象自身的可枚举属性,无法获取原型上的同名方法(如 toString、hasOwnProperty)。要“模拟”原型链的属性可见性,需手动遍历整条原型链,按从远到近(即从 Object.prototype 开始,到构造函数原型,再到实例自身)的顺序收集所有可枚举属性,后出现的同名属性覆盖先出现的——这与 JS 属性查找机制一致。
基础实现(不考虑不可枚举/符号/循环)
以下是一个简洁可用的版本:
function mergeByPrototype(...objs) {
const result = {};
const visited = new WeakMap();
function collect(obj) {
if (!obj || typeof obj !== 'object') return;
if (visited.has(obj)) return;
visited.set(obj, true);
// 先递归处理原型,保证父级属性先被设(后被子级覆盖)
const proto = Object.getPrototypeOf(obj);
if (proto && proto !== obj) {
collect(proto);
}
// 再拷贝当前对象自身可枚举属性
for (const key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
result[key] = obj[key];
}
}
}
for (const obj of objs) {
collect(obj);
}
return result;
}
- 使用
WeakMap标记已访问对象,防止原型链成环(比如手动设置obj.__proto__ = obj)导致无限递归 - 先递归处理原型再拷贝自身属性,确保继承链顺序:Object.prototype → Array.prototype → 实例自身,这样
toString不会被实例上同名属性意外覆盖(除非显式定义) -
for...in会遍历原型链上所有可枚举属性,但hasOwnProperty过滤后只保留当前对象“自有”属性;这里反其道而行——我们**需要**原型上的可枚举属性,所以不加hasOwnProperty判断;但为避免重复(比如多个对象共享同一原型),靠visited控制只处理一次每个原型对象
增强版:支持不可枚举属性与 Symbol 键
若需更完整地复现“能被 in 操作符或 for…in 看到”的全部能力,还需补充 Object.getOwnPropertyNames 和 Object.getOwnPropertySymbols,但注意:这些返回的是“自有”属性,不是原型链上的。要获取原型链上**所有可枚举属性(含不可枚举?)**,实际上 JS 没有直接 API——for...in 只返回可枚举的,getOwnProperty*() 只返回自有的。因此常见做法是:
- 用
for...in获取整条链的可枚举属性(最常用,符合运行时行为) - 额外调用
Object.getOwnPropertyNames和Object.getOwnPropertySymbols获取目标对象自身的所有属性(无论是否可枚举),并显式标记来源(如加前缀或存入不同字段) - 若明确知道某些原型上有关键不可枚举方法(如
Array.prototype.map),可手动添加
一般业务中,“原型链合并”指的就是模拟 for...in 的行为,所以上述基础版已满足多数场景。
使用示例与注意事项
例如:
const parent = { a: 1, toString() { return 'parent'; } };
const child = Object.create(parent);
child.b = 2;
console.log(mergeByPrototype(child));
// → { a: 1, toString: [Function: toString], b: 2 }
- 结果包含
a(来自 parent 原型)和toString(来自 Object.prototype),说明原型链被正确遍历 - 若多个对象有相同键,后传入的对象优先级更高(与 Object.assign 一致)
- 不能合并 null/undefined;原始值会被自动包装,但通常应提前过滤
- 不会深拷贝嵌套对象,仅浅合并——如需深合并,需在赋值前递归调用自身
不复杂但容易忽略细节。











