“作用域链”和“原型链” 区别

3 阅读3分钟

作用域链原型链是 JavaScript 中两条完全独立的查找“链条”,理解它们的区别至关重要。

简单记忆:

  • 作用域链:管变量var/let/const)的查找,与函数定义位置有关,静态。
  • 原型链:管对象属性(如 obj.prop)的查找,与对象继承有关,动态。

下面详细对比。

一、本质与用途不同

作用域链原型链
查找目标标识符(变量名、函数名)对象的属性(包括方法)
产生时机代码编写时(词法作用域)决定,运行时不变对象创建时通过 [[Prototype]] 链接,运行时可修改
数据结构由执行上下文栈中的词法环境嵌套形成由对象通过 __proto__ 或 Object.getPrototypeOf 链接形成的链条
终点全局作用域(浏览器中 window 或 globalThisnullObject.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]] 嵌套
何时确定?函数定义时(词法分析阶段)对象创建时(newObject.create、字面量等)
能否动态修改?不能(除了 eval 这种特殊情况)能(Object.setPrototypeOf,但不推荐)
查找失败结果ReferenceErrorundefined
影响性能因素作用域嵌套深度原型链长度

四、容易混淆的场景(两者同时出现)

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 等非严格模式特性中产生交集(不推荐使用)。在日常开发中,牢记“变量看作用域,属性看原型”即可。