块级作用域
块级作用域是在ES6提出的,在块中定义的 let
const
只能在块中使用,原因是执行上下文构建时,只会 登记 顶级
let
const
。
块级作用域的原理基于如下事实:
- 执行块级作用域代码时,会新建一个文本环境(不是执行上下文)
- 将新建的文本环境(Lexical Environment) 与当前执行上下文链接
- 代码块执行结束后恢复原来的连接
代码块创建文本环境的步骤与前两节提到的过程几乎是一样的,只不过不会登记 var
类型变量,因为代码块内的 var
变量在之前已经登记在 执行上下文
中了。
简单再阐述一下创建的过程
- 登记变量
- 查重
- 赋值
然后开始执行
let i = "item1";
if (true) {
let i = "item2";
console.log(i); //item2
}
代码块执行结束后恢复原来的连接
let i = "item1";
if (true) {
let i = "item2";
}
console.log(i); //item1
块级作用域中的函数
-
为了避免ES6的块级作用域导致之前的项目出现过多错误,对函数提供了一定的宽限,具体的表现为,块级作用域内的函数声明,在外部环境创建执行上下文时,如果上下文中没有出现过同名的变量,就会被登记为
undefined
console.log(foo); //undefined if (true) { function foo() { console.log(1); } }
-
退出块时,如果在外部的上下文中,不存在
let
const
声明的同名变量就会进行覆盖
function foo() {
console.log(2);
}
if (true) {
function foo() {
console.log(1);
}
}
foo();//1
let foo = function () {
console.log(2);
}
if (true) {
function foo() {
console.log(1);
}
}
foo();//2
for循环中的块级作用域
在for循环的括号中使用 let
,每次循环会迭代创建多个文本环境,具体流程如下:
let methods = [];
for (let i = 0; i < 5; i++) {
methods[i] = function () {
console.log(i);
}
}
for (let m of methods) {
m();// 0 1 2 3 4
}
这段代码执行步骤:
1. 第一次进入循环的括号,创建文本环境,保存 i = 0
2. 紧接着新建一个文本环境记录 i = 0
,作为第一次进入执行体的准备,判断i是否满足条件,满足则进入执行体
3. 执行体是一个块级作用域,前面说过,块级作用域会新建一个文本环境插入到当前的上下文中
值得注意的是,这里新建的函数对象中的 `[[scope]]` 属性指向当前的文本环境,也是正因为这个原因,才能保证每次打印的 i 都是基于当前循环的
4. 接着就是循环第2,3步,直到 i = 5,退出循环,上下文 恢复
到原来的指向状态
注意点:
-
块级作用域中的文本环境对象并没有被释放,因为被函数对象引用
-
括号的
i
和执行体
是两个不同的文本环境(看图可知)let methods = []; let index = 0; for (let i = 0; i < 5; i++) { let i = 100; methods[index++] = function () { console.log(i); } } for (const iterator of methods) { iterator(); }
因此上面的结果时打印 五个
100
,而不是直接退出