JavaScript 中使用 hasOwnProperty 实现简单的对象深度对比
hasOwnProperty仅判断对象自身属性,无法单独实现深度对比。其核心作用是过滤继承属性,配合递归和类型判断,确保只比较自有属性,避免原型链干扰。基础深度对比函数借助Object.keys和hasOwnProperty实现,for...in场景下不可省略该检查。
深度对比中的 hasOwnProperty:能做什么,不能做什么
判断两个对象是否相等?这是一个经典问题。直接使用 hasOwnProperty 能否完成?答案是不能。它只判断对象自身是否拥有某个属性——注意,是“自身”,不涉及原型链。但如果要实现可靠的深度对比,hasOwnProperty 又是不可或缺的基础工具。关键在于,需要将其与递归、类型判断组合使用。
为什么不能仅靠 hasOwnProperty?
实际上,hasOwnProperty 的职责非常单一——它只回答“这个 key 是否属于对象自身定义”。不比较值、不递归、不处理数组或嵌套对象。一个典型的反例是:两个结构完全相同的对象,但属性分别来自原型链,此时 hasOwnProperty 检查结果可能完全一致,而实际内容却截然不同。因此,不要指望它单靠自身就能完成深度对比。
hasOwnProperty 的真正角色:过滤继承属性
遍历对象时,for...in 会将原型链上的属性也一并取出——toString、valueOf 等方法都会参与进来。如果不加过滤直接对比,结果必然错误。这就是 hasOwnProperty 在深度对比中的核心价值:
- 遍历对象所有 key 时,先用
obj.hasOwnProperty(key)确认该 key 属于实例本身 - 将
for...in默认包含的原型属性全部排除 - 避免因原型链污染或扩展导致的对比错误
简单来说,它就像一个“守门员”,只放行自有属性。
一个可用的基础深度对比函数
以下实现轻量且可读性强,支持普通对象和原始值,核心正是依靠 hasOwnProperty 保证只对比自有属性:
function deepEqual(a, b) {
if (a === b) return true;
if (a == null || b == null) return false;
if (typeof a !== 'object' || typeof b !== 'object') return false;
const keysA = Object.keys(a);
const keysB = Object.keys(b);
if (keysA.length !== keysB.length) return false;
for (const key of keysA) {
// 关键步骤:确保是自有属性,排除原型干扰
if (!a.hasOwnProperty(key) || !b.hasOwnProperty(key)) continue;
if (!deepEqual(a[key], b[key])) return false;
}
return true;
}
这里有一个细节:Object.keys() 本身只返回自有可枚举属性,已经隐含了 hasOwnProperty 的逻辑,因此上述显式检查实际上是冗余的。但之所以写出来,一是为了让意图更清晰,二是为了兼容 for...in 场景——在那种情况下,缺少它就会出问题。
更稳妥的写法:兼容 for...in 场景
如果选择使用 for...in 遍历(例如需要兼容旧环境或特殊枚举控制),hasOwnProperty 就绝对不可省略:
for (let key in a) {
if (!a.hasOwnProperty(key) || !b.hasOwnProperty(key)) continue;
if (!deepEqual(a[key], b[key])) return false;
}
另外需要提醒一点:上述实现只处理了普通对象和原始值。如果遇到数组、Date、RegExp 等特殊类型,它们会被当作普通对象递归处理,结果很可能不符合预期。在实际生产环境中,建议针对这些类型单独处理。
