JavaScript中变量查找与遮蔽分属作用域链和原型链两种机制,均遵循“就近优先、找到即停”原则:前者用于var/let/const变量访问,后者用于对象属性读取;自有属性会遮蔽原型同名属性,但二者互不干扰。

JavaScript 中的变量查找和遮蔽效应,其实分属两个不同机制:一个是原型链上的属性查找(针对对象属性),另一个是作用域链上的变量查找(针对 var/let/const 声明的变量)。但它们共享一个核心逻辑:就近优先、找到即停。
原型链上的属性查找:从实例到 null 的逐层向上
当你访问 obj.prop 时,引擎按以下顺序查找:
- 先检查
obj自身是否拥有prop(自有属性) - 如果没有,就去
obj.__proto__(即构造函数的prototype)上找 - 还没找到,就继续查
obj.__proto__.__proto__,依此类推 - 最终抵达
Object.prototype,它的__proto__是null,查找终止 - 全程没找到,返回
undefined
原型链的遮蔽效应:自有属性会“挡住”原型上的同名属性
一旦对象自身拥有某个属性,无论值是什么,它都会中断原型链查找——原型上的同名属性被“遮蔽”,完全不可见。
例如:
立即学习“Java免费学习笔记(深入)”;
Java项目代码review工具。分析Git变更+完整调用链路上下文,推断业务需求,进行多维度评分和分类汇总,生成完整PRD文档。包含细粒度Java代码审查清单(Null安全、异常处理、Streams、并发、equals/hashCode、资源管理、API设计、性能、MyBatis/ORM、事务边界、SQL/DD...
function Person() {}
Person.prototype.city = '北京';
const a = new Person();
const b = new Person();
b.city = '上海'; // 在 b 自身上添加 city 属性
console.log(a.city); // '北京'(走原型)
console.log(b.city); // '上海'(不查原型,自有属性已存在)
这和局部变量覆盖全局变量的逻辑类似,但发生位置不同:这里是对象层级的“自有 vs 继承”,不是作用域层级的“局部 vs 全局”。
别混淆:作用域链的遮蔽和原型链的遮蔽是两回事
作用域链遮蔽发生在函数执行时的变量访问中,比如:
let name = '全局';
function foo() {
let name = '局部';
console.log(name); // '局部' —— 作用域链在当前函数作用域就找到了,不往外找
}
它不涉及 __proto__ 或 prototype,只依赖函数定义时形成的作用域链。而原型链遮蔽只在读取对象属性时触发,与 function 或 var 声明无关。
怎么判断一个属性来自哪里?
可以用几个简单方法辅助验证:
-
obj.hasOwnProperty('prop')→ 返回true表示是自有属性(可能遮蔽了原型) -
'prop' in obj→ 返回true表示自有或原型链上有(不区分来源) -
Object.getPrototypeOf(obj)可以手动查看上一级原型 -
console.dir(obj)在浏览器控制台展开,能看到__proto__展开结构

















