JS 中的块级作用域

167 阅读3分钟

一、什么是块级作用域?

1.1 什么是作用域?

作用域是编程中一个非常核心的概念,指程序中变量、函数的有效/可见范围。这是一个抽象概念。在 JS 中是通过执行上下文来实现的。

1.2 全局作用域、函数作用域、块级作用域

JS 中,有三种作用域,分别为:全局作用域、函数作用域、块级作用域。
当执行一个项目或一个文件时,会先生成全局作用域;
在执行每一个函数时,会生成对应的函数作用域;
这两个作用域都是 JS 设计之初就有的,而块级作用域则是 ES6 才实现的。这篇文章主要讲 ES6 实现的块级作用域

二、为什么有块级作用域?

看一个例子:

**

function loopCount() {
    for (var i = 0; i < 10; i++) {
    }
    console.log(i)
}
loopCount()  // 10

在这个例子中,有一个循环,循环里声明了一个变量 i ,只是用于循环内使用,但是当循环完毕后,在循环外打印变量 i 时,依然能够打印出来,结果为10。
说明在循环体内声明的变量,在整个函数内部都是可见的。

这会造成一些看起来违背常理的问题。造成函数作用域甚至全局作用域内的变量污染。

过去 JS 中没有引入块级作用域,主要是由于当时创造这门语言的目的:只是为了能够在浏览器运行脚本,不希望很系统复杂,让使用者难以上手。
而现在,JS 的作用早已不止于此,且可以预见到需要处理更多复杂的场景和逻辑,因此,在 ES6 中引入了块级作用域。

二、JS 如何实现了块级作用域?

正常情况下,JS 引擎在编译代码阶段会生成全局执行上下文和函数执行上下文。这其中,每个上下文又分为了两个部分:变量环境和词法环境。

这两个环境中存储的是不同的东西,举例说明:

**

var a = 0
let b = 1
function foo() {
    var a = 1
    let b = 2
    if (true) {
        let b = 3
        console.log(a, b)
    }
}
foo()  // 1, 3

在执行 foo 函数时,它的执行上下文是这样的:

1.webp

有几点需要说明:

  1. 使用 var 关键字声明的变量、及函数声明,会被放入变量环境中
  2. 使用 let 及 const 关键字声明的变量和常量会被放入词法环境中
  3. 词法环境内部也类似于一个栈结构,每一个块结构(即有一对大括号,如条件句、循环等)内的变量和常量会单独保存(使用 var 关键字声明的不会)。
  4. 所以,如上面的例子,函数 foo 的词法环境里,有两个区域。下面的区域保存了函数体内使用 let 声明的变量,上面的区域保存了 if 语句中使用 let 声明的变量。所以即使它们都叫做 b,但却是两个不同的变量。

如此一来,ES6 就通过执行上下文中的词法环境实现了块级作用域。