属性遮蔽是JavaScript属性访问的硬性规则:对象自有属性存在时立即终止查找,不访问原型;删除自有属性后恢复原型访问;方法、getter、defineProperty添加的属性均遵循此逻辑,且不改变原型链结构。

属性遮蔽是原型链中属性访问的默认优先行为:只要对象自身有该属性,就不再继续向上查找原型链——它不是“可选机制”,而是 JavaScript 引擎执行属性访问时的硬性规则。
遮蔽发生的前提是“自有属性存在”
引擎每次读取 obj.prop 时,第一步永远是检查 obj.hasOwnProperty('prop')。只要返回 true,查找立即终止,不看原型上有没有同名属性。
- 哪怕原型上定义了更早、更通用的值(如 Person.prototype.name = 'unknown'),只要实例设置了 p.name = 'Alice',访问结果就是 'Alice'
- 删除操作会还原遮蔽效果:delete p.name 后,再次访问 p.name 就会落到原型上,得到 'unknown'
- 使用 Object.defineProperty 添加不可枚举或只读属性,同样触发遮蔽,且可能影响 for...in 遍历结果
方法和数据属性遵循完全相同的遮蔽逻辑
函数在 JS 中也是普通属性,不存在“方法例外”。obj.sayHi() 的调用过程,本质仍是先找 obj.sayHi 这个属性值,再执行它。
- 如果 p.sayHi 是实例上定义的箭头函数,那每次 new 都新建一个函数对象,无法共享
- 如果 p.sayHi 没定义,引擎才会去 p.__proto__.sayHi(即 Person.prototype.sayHi)上取函数引用
- 即使原型上的 sayHi 是 getter,只要实例自身定义了 sayHi(哪怕是数据属性),getter 就不会被触发
遮蔽不改变原型链结构,只改变查找起点
遮蔽不是删除或覆盖原型属性,只是让查找在实例层“停住”。原型链本身(__proto__ 指向关系)完全不受影响。
- p.constructor 仍指向 Person,p instanceof Person 仍为 true
- 修改 Person.prototype.species,所有未遮蔽该属性的实例都会立刻反映新值
- 但已设置 p.species = 'Engineer' 的实例,不受该修改影响
识别有害遮蔽的关键信号
不是所有遮蔽都有问题,但以下情况通常意味着设计偏差:
- 构造函数里重复赋值原型上已有的方法(如 this.render = function() {...})
- 多个实例各自保存相同的大对象(如配置项、常量表),本可放在 prototype 上共享
- 子类中用字段直接覆盖父类的 getter,导致计算逻辑被跳过
- 调试时发现 console.dir(obj) 显示属性在“自身”下,但预期应来自原型

















