JS 块级作用域 工作机理

403 阅读3分钟

块级作用域

块级作用域是在ES6提出的,在块中定义的 let const 只能在块中使用,原因是执行上下文构建时,只会 登记 顶级 let const

块级作用域的原理基于如下事实:

  • 执行块级作用域代码时,会新建一个文本环境(不是执行上下文)
  • 将新建的文本环境(Lexical Environment) 与当前执行上下文链接
  • 代码块执行结束后恢复原来的连接

代码块创建文本环境的步骤与前两节提到的过程几乎是一样的,只不过不会登记 var 类型变量,因为代码块内的 var 变量在之前已经登记在 执行上下文 中了。

简单再阐述一下创建的过程

  • 登记变量
  • 查重
  • 赋值

然后开始执行

let i = "item1";
if (true) {
  let i = "item2";
  console.log(i); //item2
}

image.png

代码块执行结束后恢复原来的连接

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

image.png

2. 紧接着新建一个文本环境记录 i = 0,作为第一次进入执行体的准备,判断i是否满足条件,满足则进入执行体

3. 执行体是一个块级作用域,前面说过,块级作用域会新建一个文本环境插入到当前的上下文中

image.png

值得注意的是,这里新建的函数对象中的 `[[scope]]` 属性指向当前的文本环境,也是正因为这个原因,才能保证每次打印的 i 都是基于当前循环的

4. 接着就是循环第2,3步,直到 i = 5,退出循环,上下文 恢复 到原来的指向状态

image.png

注意点:

  • 块级作用域中的文本环境对象并没有被释放,因为被函数对象引用

  • 括号的 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 ,而不是直接退出