JavaScript原型链属性查找优先级为:实例自有属性 > 自定义原型属性 > 内置原型属性;自有属性会完全屏蔽继承属性,内置方法虽不可枚举但可覆盖,需谨慎操作以避免意外行为。

JavaScript 中原型链查找时,内置属性和自定义属性不会“冲突”成错误,而是按固定顺序决定谁生效——实例自有属性 > 自定义原型属性 > 内置原型属性(如 toString、hasOwnProperty)。关键不是避免同名,而是清楚哪一层的值会被读到、哪一层被遮蔽。
属性查找优先级:自有永远胜过继承
只要你在实例上直接赋值,比如 obj.toString = () => "custom",这个自有属性就会完全屏蔽 Object.prototype.toString。引擎不会报错,也不会合并,它只取第一个匹配项。
- 实例上定义
obj.valueOf = 42→ 调用obj.valueOf()执行你写的函数,不是Object.prototype.valueOf - 给数组实例加
arr.push = null→ 后续调用arr.push()报TypeError,因为查到了自有属性null,根本不会走到Array.prototype.push - 哪怕只是
obj.constructor = MyOtherClass,也会让obj.constructor返回你设的值,不再沿链找原始构造器
内置属性通常不可枚举、不可写,但可被覆盖
像 toString、valueOf、isPrototypeOf 这些方法,在 Object.prototype 上默认是 enumerable: false、writable: true(部分浏览器对 writable 有差异),所以它们不会出现在 for...in 或 Object.keys() 中,但能被直接赋值覆盖。
-
Object.defineProperty(Object.prototype, 'toString', { writable: false })可锁死它,之后任何obj.toString = ...都会静默失败(严格模式下报错) - 用
Object.getOwnPropertyDescriptors(Object.prototype)能看到每个内置方法的configurable和writable状态,这是判断能否覆盖的关键 - 注意:
__proto__是访问器属性,不能简单赋值覆盖;而constructor是可写的普通属性,容易被意外改写
识别属性来源:别只看有没有,要看在哪
光用 "toString" in obj 或 obj.hasOwnProperty("toString") 不够——前者为 true 只说明链上有,后者为 true 才代表真正在实例上。要定位源头,得组合使用:
立即学习“Java免费学习笔记(深入)”;
-
Object.getOwnPropertyDescriptor(obj, "toString")→ 返回undefined表示不在实例上;若返回对象,看value和get是不是你设的 -
Object.getPrototypeOf(obj) === Object.prototype→ 判断是否直接继承自Object,再查Object.prototype.toString是否被改过 - 逐层检查:
Object.getPrototypeOf(obj).hasOwnProperty("toString")可确认该方法是否被定义在直接原型上
安全覆盖内置属性的实用建议
除非明确需要(如 polyfill 或调试),否则不建议覆盖内置方法。若必须,优先用 Symbol 或前缀隔离,而不是硬顶替:
- 用
const CUSTOM_TOSTRING = Symbol("toString")存自定义逻辑,避免和原生toString同名碰撞 - 加前缀更直观:
obj.customToString = function() { ... },既保留原生功能,又提供扩展入口 - 在构造函数里主动保护:
if (this.hasOwnProperty("toString")) console.warn("toString 已被覆盖"),提前暴露问题 - 测试时用
expect(obj.toString).toBe(Object.prototype.toString)验证未被篡改,尤其在共享环境或微前端中


















