JavaScript 中的作用域与作用域链

322 阅读5分钟

在 JavaScript 中,作用域(Scope)是决定变量和函数可以访问到哪些数据的规则。理解作用域是掌握 JavaScript 执行上下文、闭包、变量提升等高级特性的重要基础。


目录

  1. 什么是作用域(Scope)
  2. 作用域的类型
  3. 作用域链(Scope Chain)
  4. 作用域的创建与销毁
  5. 变量提升(Hoisting)
  6. 闭包与作用域链的关系

1. 什么是作用域(Scope)

作用域是 JavaScript 中定义变量、函数以及对象可访问区域的概念。简单来说,作用域决定了在哪些地方可以访问到某个变量或函数。

全局作用域与局部作用域

  • 全局作用域:如果一个变量声明在任何函数之外,它就属于全局作用域,所有代码都可以访问。
  • 局部作用域:函数内部声明的变量属于该函数的局部作用域,只有在函数内部可以访问。

示例:全局作用域与局部作用域

let globalVar = "I am global";  // 全局变量

function testScope() {
  let localVar = "I am local";  // 局部变量
  console.log(globalVar);  // 可以访问全局变量
  console.log(localVar);   // 可以访问局部变量
}

testScope();
console.log(globalVar);  // 可以访问全局变量
console.log(localVar);   // 错误:无法访问局部变量

在这个示例中,globalVar 是全局变量,可以在任何地方访问;localVar 是局部变量,只能在 testScope 函数内部访问。


2. 作用域的类型

2.1 全局作用域

全局作用域是 JavaScript 中最外层的作用域,通常是浏览器中的 window 对象或 Node.js 中的 global 对象。

  • 在浏览器中,任何在全局作用域中声明的变量都属于 window 对象。
let globalVar = "Global Variable";
console.log(window.globalVar);  // 在浏览器中输出:Global Variable

2.2 函数作用域

每个函数都有自己的局部作用域。在函数内部声明的变量,只能在该函数内部访问。

function testFunctionScope() {
  let localVar = "Local Variable";
  console.log(localVar);  // 可以访问局部变量
}

testFunctionScope();
console.log(localVar);  // 错误:无法访问局部变量

2.3 块级作用域

JavaScript 在 ES6 中引入了 letconst,这两者有块级作用域的特性,意味着它们声明的变量只在包含它们的代码块(如循环或条件语句)中有效。

if (true) {
  let blockScopedVar = "Block Scoped";
  console.log(blockScopedVar);  // 可以访问块级作用域变量
}

console.log(blockScopedVar);  // 错误:无法访问块级作用域变量

3. 作用域链(Scope Chain)

当我们访问一个变量时,JavaScript 会沿着作用域链查找变量,直到找到为止。作用域链的形成是因为函数可以访问其外部作用域的变量,这样就形成了一个链式结构。

3.1 作用域链的工作原理

  1. JavaScript 中的每个函数都有一个词法作用域,函数内的变量优先级最高。
  2. 如果当前作用域中找不到变量,JavaScript 会继续向外部作用域(父作用域)查找,直到全局作用域。

示例:作用域链

let globalVar = "I am global";

function outer() {
  let outerVar = "I am outer";

  function inner() {
    let innerVar = "I am inner";
    console.log(innerVar);  // 访问局部作用域中的变量
    console.log(outerVar);  // 访问父作用域中的变量
    console.log(globalVar); // 访问全局作用域中的变量
  }

  inner();
}

outer();

在这个例子中,inner 函数的作用域链从内到外依次是:

  1. inner 的局部作用域。
  2. outer 的局部作用域。
  3. 全局作用域。

JavaScript 会从 inner 的作用域开始查找变量,如果找不到,它会去外部的 outer 作用域继续查找,如果还找不到,则去全局作用域查找。


4. 作用域的创建与销毁

4.1 执行上下文

当代码执行时,JavaScript 引擎会为每个函数调用创建一个 执行上下文(Execution Context)。执行上下文中包括了作用域链、变量对象(存储所有变量和函数声明)以及 this 的值。

4.2 作用域的销毁

每当一个执行上下文完成执行后,它所创建的作用域链和变量对象会被销毁。当函数执行结束时,其局部变量将被销毁,释放内存。


5. 变量提升(Hoisting)

变量提升是 JavaScript 中的一个行为,即在函数或全局作用域中声明的变量和函数会被提升到作用域的顶部。

5.1 变量提升的行为

对于 var 声明的变量,声明会被提升,但赋值不会被提升。

console.log(x);  // 输出:undefined
var x = 5;

在这段代码中,var x = 5 实际上被转换为:

var x;
console.log(x);  // 输出:undefined
x = 5;

5.2 函数提升

函数声明会被完全提升,包括函数体。

console.log(myFunction());  // 输出:Hello, World!

function myFunction() {
  return "Hello, World!";
}

但是,函数表达式不会提升。

console.log(myFunction());  // 错误:myFunction is not a function

var myFunction = function() {
  return "Hello, World!";
};

6. 闭包与作用域链的关系

闭包是函数与其词法作用域的结合体。闭包的一个关键特性是,它能够记住并访问其创建时的作用域链,即使该函数已经执行完毕。

function outer() {
  let outerVar = 'I am outside!';
  function inner() {
    console.log(outerVar);  // inner 函数中的作用域链
  }
  return inner;
}

const closure = outer();
closure();  // 输出:I am outside!

这里,inner 函数形成了闭包,尽管 outer 函数已经执行完毕,inner 仍然能访问 outerVar,因为 inner 函数记住了它创建时的作用域链。