不得不说的 Chrome DevTools 中的 Scope

32 阅读5分钟

前言

最近看到阮一峰老师写的技术写作的首要诀窍,其中的内容令我深受启发

阮一峰老师在文中首先强调了技术写作的好坏,跟语文水平关系不大,更多是一个技巧问题。优秀的技术文章需要表达清楚明白、让读者快速轻松学会文章中内容。所以笔者本人觉得

一篇技术文章的内容不需要包罗万象,容易贪多嚼不烂,倾向选取一个小知识点,从头到尾采用平铺直叙的方式将知识点讲清楚、讲明白

由此笔者打算在本篇博客尝试这样的写作方式。前端调试一直是前端中非常重要的内容,涉及解释器、runtime、调试工具和调试原理等等内容,这里面可以写很多篇技术文章。

本篇文章只讲一讲使用 Chrome DevTools 调试工具在浏览器中如何使用 Scope 面板

什么是作用域?

在 JavaScript 中,作用域(Scope)是指变量和函数在程序中可见和可访问的范围。作用域决定了变量和函数的生命周期,以及它们的可见性。JS 只有静态作用域,所以 JS 的作用域与函数定义时的位置有关、与函数调用位置无关,函数嵌套会形成长长的作用域链

Scope 分类

上面说的作用域的定义,那么 JS 中有那些作用域呢,由于 JS 中的不断发展,JS 中的作用域越来越多,目前 Scope 主要分为下面六类:

  • Block:代码中由 let 和 const 声明的块作用域(Block Scope)中的变量
  • Local:当前函数作用域内声明的变量
  • Closure:外层函数内声明的变量
  • Module:Module 文件中的声明的变量
  • Script:以 const、let 声明的变量
  • Global:以 var 声明的变量或者挂载在 window 对象上的属性

以下面的程序代码为例,暂停在console.log 时,a、b、c、d、e 变量正好属于五种不同的作用域

// index.mjs
var a = "a";
let b = "b";
const c = "c";

function outer() {
  let d = "d";
  function inner() {
    let e = "e";
    if (true) {
      let f = "f";
      console.log(a, b, c, d, e, f);
    }
  }
  inner();
}

outer();

如图所示,Module 文件的 Scope 如图所示

image.png

从上图可以看出,程序代码出现五种 Scope,实际上对于支持 ES Module 的文件在浏览器中解析执行时叫 Module Scope,当普通的 JS 文件在浏览器中解析执行叫 Script Scope

image.png

下面详细讲解这些 Scope

Block

块作用域(Block Scope)是一种变量作用域的规则,主要与变量在代码块中的可见性和生命周期相关。块作用域的基本概念是,变量的作用范围仅限于其所在的代码块中。这种作用域通常由大括号 {} 来定义,如在函数、条件语句或循环中

用大白话讲,使用 const 或 let 关键字声明变量,那么 {} 则形成块作用域,变量的作用范围仅限于其所在{}中

请看下面的示例代码。在条件语句中的 Block Scope

if (true) {
  let a = "a";
  console.log(a); // 只能在这里访问到a
}

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

将断点选在第四行的位置,在浏览器中会出现下面的效果

image.png

此时,Scope 面板中只有两类 Scope,对于任何的 JS 代码,Global Scope 是必定存在的,时作为兜底的 Scope,然后是 Block Scope,通过 Block Scope 可以查看当前的 Block Scope里有哪些变量

在循环语句中的 Block Scope

for (let index = 0; index < 3; index++) {
  console.log(index);
}

将断点运行在第二行的位置,在浏览器中出现下面的效果

image.png

最后特别说明一下在函数体中使用 const 和 let 声明变量的效果

根据块作用域的定义使用 const 或 let 关键字声明变量,那么 {} 则形成块作用域,在函数体中使用 const 和 let 声明变量也会产生块作用域

function foo() {
  let a = "a";
  console.log(a);
}
foo()

但是函数本身有函数作用域,所以在在函数体中使用 const 和 let 声明变量,函数作用域即是块作用域,块作用域也是函数作用域

Local

Local Scope 是指变量、函数或其他标识符仅在某个特定代码块或函数内可访问的范围。局部作用域的作用是将变量限定在特定的范围内,防止它们在外部代码中被误用或修改。这样可以提高代码的封装性,避免命名冲突,并使代码更加易于理解和维护

请看如下的示例代码

function outer() {
    const b = "b";

    function inner() {
      const a = "a";
      console.log(a, b);
    }

    inner();
  }

  outer();

在 console.log 加上断点,重新运行后,可以看到如下效果

image.png

Closure

虽然名字叫Closure,但是没有闭包作用域概念,Closure Scope是指当前Local Scope的作用域的外层函数。

在Local中的示例代码上,外层函数是 outer 方法

Module

Module Scope(模块作用域)指的是在模块化编程中,一个模块内部的作用域。模块作用域使得模块内定义的变量、函数或类在模块外部不可直接访问,形成了一种封闭的作用域。

请看下面的示例代码

<script type='module'>
const a = 'a'

console.log(a)
</script>

在浏览器中的运行效果如下所示

image.png

<script type="module"></script> 是 HTML5 引入的一种用于在浏览器中使用 JavaScript 模块(ES6 模块)的方式。通过设置 type="module",在浏览器中直接使用 ES6 的模块化特性,例如 importexport

console.log的行号槽加上断点,重新执行后,可以在Scope面板中看到变量a属于Module Scope

Script

Script Scope指使用<script> 标签中声明的变量。请看如下代码示例

只需要将 Module 中的示例代码删除 type='module'

<script>
const a = 'a'

console.log(a)
</script>

在浏览器中的运行效果如下所示

image.png

console.log的行号槽加上断点,重新执行后,可以在Scope面板中看到变量a属于Script Scope

Global

Global Scope(全局作用域)指的是在程序中最外层的作用域范围。任何在全局作用域中声明的变量或函数,都可以在整个程序的任何地方被访问和使用