基于本文回答

播面 播面

文图音视,全方位拆解八股文
0
评论

JavaScript 的作用域(Scope)和作用域链(Scope Chain)

知识点图片

本文阐述JavaScript的作用域与作用域链。作用域是决定变量可访问范围的规则(分全局、函数、块级),作用域链则是变量由内向外逐级查找的机制。

我们来深入浅出地讲解一下 JavaScript 中非常核心的概念:作用域 (Scope)作用域链 (Scope Chain)

理解这两个概念对于编写高质量、无 bug 的 JavaScript 代码至关重要。


一、作用域 (Scope)

1. 什么是作用域?

你可以把作用域想象成一套规则,这套规则用来管理和查找变量。简单来说,作用域定义了变量和函数在代码中哪些区域是可访问的

它的主要目的有两个:

  1. 隔离变量:防止不同代码块中的变量互相冲突(即“命名冲突”)。
  2. 安全性:决定了代码块对外的暴露程度,有些变量只允许内部使用,外部无法访问。

2. 作用域的类型

在 JavaScript 中,主要有三种类型的作用域:

a. 全局作用域 (Global Scope)
  • 定义:在代码的最外层定义的变量,或者在所有函数之外定义的变量,都拥有全局作用域。
  • 特点
    • 在代码的任何地方都可以被访问和修改。
    • 在浏览器环境中,全局作用域的变量会自动成为 window 对象的属性。
    • 滥用全局变量容易导致命名冲突和代码耦合度高,应尽量避免。

示例:

javascript
var globalVar = "我是一个全局变量";

function checkGlobal() {
  console.log(globalVar); //可以访问
}

checkGlobal(); // 输出: "我是一个全局变量"
console.log(window.globalVar); // 在浏览器中,输出: "我是一个全局变量"
b. 函数作用域 (Function Scope)
  • 定义:在函数内部声明的变量,只在该函数及其嵌套的子函数内部可以访问。
  • 特点
    • 这是由 var 关键字声明变量时遵循的规则。
    • 函数外部无法访问函数内部的变量。

示例:

javascript
function myFunction() {
  var functionVar = "我是一个函数作用域变量";
  console.log(functionVar); // 可以访问
}

myFunction(); // 输出: "我是一个函数作用域变量"
// console.log(functionVar); // Uncaught ReferenceError: functionVar is not defined
c. 块级作用域 (Block Scope)
  • 定义:在 {} (花括号) 代码块中声明的变量,只在该代码块内部可以访问。
  • 特点
    • 这是 ES6 引入的 letconst 关键字所遵循的规则。
    • if 语句、for 循环、while 循环等都会创建块级作用域。
    • 它解决了 var 在循环等场景下可能引发的经典问题。

示例:

javascript
if (true) {
  let blockVar = "我是一个块级作用域变量";
  const blockConst = "我也是";
  console.log(blockVar); // 输出: "我是一个块级作用域变量"
}

// console.log(blockVar); // Uncaught ReferenceError: blockVar is not defined

var vs let in for loop (经典对比):

使用 var

javascript
for (var i = 0; i < 3; i++) {
  setTimeout(function() {
    console.log(i); // 循环结束后,i 的值已经是 3,所以会输出三次 3
  }, 100);
}
// 输出: 3, 3, 3

这是因为 var 是函数作用域,三次循环共享同一个变量 i

使用 let

javascript
for (let i = 0; i < 3; i++) {
  setTimeout(function() {
    console.log(i); // let 是块级作用域,每次循环都会创建一个新的 i
  }, 100);
}
// 输出: 0, 1, 2

二、词法作用域 (Lexical Scope)

在讲解作用域链之前,必须先理解一个概念:词法作用域(也叫静态作用域)。

JavaScript 采用的是词法作用域。这意味着,函数的作用域在函数定义的时候就已经决定了,而不是在函数调用的时候

简单来说:无论一个函数在哪里被调用,它的作用域规则只由它被声明时所处的位置决定。

示例:

javascript
var value = "global";

function foo() {
  console.log(value); // 这个 value 指向哪里?
}

function bar() {
  var value = "local";
  foo(); // 在 bar 内部调用 foo
}

bar(); // 输出: "global"

分析:虽然 foo 是在 bar 内部被调用的,但 foo 是在全局作用域中定义的。根据词法作用域的规则,它在查找 value 变量时,会沿着它定义时的作用域链去查找,也就是在它自己的作用域(为空)和全局作用域中查找,所以找到了全局的 "global"


三、作用域链 (Scope Chain)

1. 什么是作用域链?

当代码在一个作用域中查找一个变量时,如果当前作用域中找不到,它就会向上一层作用域继续查找,这个逐层向上查找的过程就形成了一个链条,这就是作用域链

  • 起点:当前执行代码所在的作用域。
  • 终点:全局作用域。

可以把作用域链想象成一个建筑物的楼层

  • 当前作用域:你所在的房间。
  • 外部作用域:你房间外的楼层。
  • 全局作用域:整栋大楼的一楼大厅。

你要找一个东西(变量),会先在自己房间找,找不到就去楼层里找,再找不到就去一楼大厅找。如果一楼大厅也找不到,那就说明这个东西(变量)不存在(ReferenceError)。

2. 作用域链是如何工作的?

让我们通过一个嵌套函数的例子来具体看看:

javascript
// 全局作用域
var a = 1;

function outer() {
  // outer 函数作用域
  var b = 2;

  function inner() {
    // inner 函数作用域
    var c = 3;
    console.log(a + b + c); // 在这里访问 a, b, c
  }

  inner();
}

outer(); // 输出: 6

inner 函数执行 console.log(a + b + c) 时,查找变量的作用域链如下:

  1. 查找 c

    • 首先在 inner 函数的当前作用域中查找。
    • 找到了!c 的值是 3。
  2. 查找 b

    • 首先在 inner 函数的当前作用域中查找。没找到。
    • 沿着作用域链向上,进入 outer 函数的作用域。
    • 找到了!b 的值是 2。
  3. 查找 a

    • 首先在 inner 函数的当前作用域中查找。没找到。
    • 向上进入 outer 函数的作用域。还是没找到。
    • 再向上,进入全局作用域
    • 找到了!a 的值是 1。

这个查找路径 inner Scope -> outer Scope -> Global Scope 就是作用域链。

作用域链与闭包 (Closure)

作用域链是理解闭包的关键。闭包是指一个函数能够“记住”并访问其词法作用域(即定义时的作用域),即使该函数在其词法作用域之外执行。

javascript
function createCounter() {
  let count = 0;
  
  return function increment() {
    count++;
    console.log(count);
  };
}

const counterA = createCounter(); // createCounter 执行完毕,其作用域本应销毁
counterA(); // 输出: 1
counterA(); // 输出: 2

// 为什么 counterA 能够记住 count 的值?
// 因为 increment 函数形成了一个闭包。
// 它的作用域链中包含了 createCounter 的作用域,所以它始终可以访问并修改变量 count。

总结

  1. 作用域 (Scope):是一套管理变量访问权限的规则。分为全局作用域、函数作用域和块级作用域。
  2. 词法作用域 (Lexical Scope):JavaScript 的作用域规则是静态的,在代码写下时就已确定,与函数如何被调用无关。
  3. 作用域链 (Scope Chain):是一个变量的查找机制。当需要访问一个变量时,解释器会从当前作用域开始,沿着链条逐级向上查找,直到全局作用域。
  4. var, let, const 的区别
    • var 遵循函数作用域
    • letconst 遵循块级作用域,提供了更精细的控制,是现代 JavaScript 的首选。
00:00
00:00