作用域链和原型链是 JavaScript 中两条完全独立的查找“链条”,理解它们的区别至关重要。
简单记忆:
- 作用域链:管变量(
var/let/const)的查找,与函数定义位置有关,静态。 - 原型链:管对象属性(如
obj.prop)的查找,与对象继承有关,动态。
下面详细对比。
一、本质与用途不同
| 作用域链 | 原型链 | |
|---|---|---|
| 查找目标 | 标识符(变量名、函数名) | 对象的属性(包括方法) |
| 产生时机 | 代码编写时(词法作用域)决定,运行时不变 | 对象创建时通过 [[Prototype]] 链接,运行时可修改 |
| 数据结构 | 由执行上下文栈中的词法环境嵌套形成 | 由对象通过 __proto__ 或 Object.getPrototypeOf 链接形成的链条 |
| 终点 | 全局作用域(浏览器中 window 或 globalThis) | null(Object.prototype 的原型是 null) |
| 是否受调用方式影响 | 否(静态作用域) | 否(但属性访问受 this 绑定影响?注意:原型链只是查找属性,this 指向调用者) |
| 典型应用 | 闭包、变量提升、块级作用域 | 继承、方法复用、instanceof |
二、查找机制的差异
1. 作用域链示例
let global = 'global';
function outer() {
let outerVar = 'outer';
function inner() {
let innerVar = 'inner';
console.log(outerVar); // 沿着作用域链找到 outer 中的变量
}
inner();
}
outer();
- 查找
outerVar时:先在inner函数内部找 → 没有,则到定义 inner 的位置的上层(即outer函数作用域)找 → 找到。 - 与调用方式无关:即使你把
inner函数赋值给全局对象再调用,作用域链依然不变。
2. 原型链示例
const parent = { a: 1 };
const child = Object.create(parent);
child.b = 2;
console.log(child.a); // 1,来自原型链
console.log(child.b); // 2,自身属性
console.log(child.c); // undefined,原型链最终到 null 未找到
- 查找
child.a时:先在child自身找 → 没有,则沿着__proto__到parent对象找 → 找到。 - 与定义位置无关:只看对象的原型链接关系。
三、关键区别总结表
| 维度 | 作用域链 | 原型链 |
|---|---|---|
| 是谁的链? | 执行上下文的词法环境嵌套 | 对象的 [[Prototype]] 嵌套 |
| 何时确定? | 函数定义时(词法分析阶段) | 对象创建时(new、Object.create、字面量等) |
| 能否动态修改? | 不能(除了 eval 这种特殊情况) | 能(Object.setPrototypeOf,但不推荐) |
| 查找失败结果 | ReferenceError | undefined |
| 影响性能因素 | 作用域嵌套深度 | 原型链长度 |
四、容易混淆的场景(两者同时出现)
let name = 'global';
const obj = {
name: 'obj',
say: function() {
console.log(name); // 作用域链:找到全局的 'global'
console.log(this.name); // 原型链不涉及,这里是 this 指向 + 对象属性查找
}
};
obj.say();
JavaScript:对象属性不是变量,在方法中必须通过 this.属性名 才能访问;直接写 name 是在沿着作用域链查找变量(与对象属性无关)。
name:走作用域链,与obj无关 →'global'。this.name:先确定this指向obj,然后在obj对象上查找属性'name',如果obj自身没有,会沿原型链查找。
五、一个更直观的对比代码
javascript
// 作用域链:由函数定义位置决定
function testScope() {
let a = 1;
function inner() {
console.log(a); // 作用域链找到 a = 1
}
inner();
}
testScope();
// 原型链:由对象原型关系决定
const proto = { a: 1 };
const obj2 = Object.create(proto);
obj2.a = 2; // 屏蔽原型链上的 a
console.log(obj2.a); // 2(自身属性),原型链未访问
delete obj2.a;
console.log(obj2.a); // 1(来自原型链)
六、常见面试题结论
- 作用域链:变量查找的“立体嵌套结构”,类似俄罗斯套娃,每层是词法环境。
- 原型链:属性查找的“链表结构”,类似单链表,每个节点是一个对象,尾部是
null。
两者完全独立,但会在 with 语句、eval 等非严格模式特性中产生交集(不推荐使用)。在日常开发中,牢记“变量看作用域,属性看原型”即可。