一、什么是块级作用域?
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 函数时,它的执行上下文是这样的:
有几点需要说明:
- 使用 var 关键字声明的变量、及函数声明,会被放入变量环境中
- 使用 let 及 const 关键字声明的变量和常量会被放入词法环境中
- 词法环境内部也类似于一个栈结构,每一个块结构(即有一对大括号,如条件句、循环等)内的变量和常量会单独保存(使用 var 关键字声明的不会)。
- 所以,如上面的例子,函数 foo 的词法环境里,有两个区域。下面的区域保存了函数体内使用 let 声明的变量,上面的区域保存了 if 语句中使用 let 声明的变量。所以即使它们都叫做 b,但却是两个不同的变量。
如此一来,ES6 就通过执行上下文中的词法环境实现了块级作用域。