JS 实现对块级作用域的支持

63 阅读2分钟

WX20240817-210853@2x.png

作用域是什么

作用域就是变量和函数的可访问范围

// 函数作用域:
// 变量 myVar 在函数内定义,在函数范围之外就无法访问
const myFunction = () => {
	var myVar = "Hello World";
	console.log(myVar);
};

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

没有块级作用域带来的问题

ES6之前有两种作用域:全局作用域函数作用域

其他语言普遍支持块级作用域。一对大括号包裹的一段代码,就形成了一个块

这个时候,没有块级作用域的JS就和其他语言表现不同,有些情况下会产生一些困惑

// 没有块级作用域(变量提升的设计)导致意外覆盖
var a = 1;
function test() {
    console.log(a); // 代码写到这的时候,认为是打印了全局的变量a
    if (true) {
      var a = 2; // 此处声明的a,提升到函数范围的顶部
      console.log(a);
    }
}
test();
// undefined
// 2

引入 let const

let const 所处的块形成了一个块级作用域

// 块内声明的 a 不会影响外面的 a,执行效果就符合直觉
let a = 1;
function test() {
    console.log(a);
    if (true) {
        let a = 2; // 此处以let声明的 变量a 的可见范围是它所在的大括号
        console.log(a);
    }
}
test();
// 1
// 2

如何实现对块级作用域的支持

我们知道代码在执行的时候,会有一个调用栈

function test() {
    var a = 1;
    let b = 2;
    console.log(a);
    console.log(c);

    // 块结构
    {
        let a = 3;
        let b = 4;
        console.log(a);
        console.log(b);
    }

    var c = 5
    console.log(b);
}

test();
// 1
// undefined
// 3
// 4
// 2

执行这段代码时

  • 先入栈一个全局执行上下文
    • 变量环境里有:函数test

WX20240817-182834@2x.png

  • 执行 test,入栈一个函数执行上下文
    • 变量环境var avar c
    • let 声明的变量b会被放到词法环境词法环境里也维护一个栈结构来管理块级作用域下的变量)

image.png

  • 遇到一个块结构
    • 继续向词法环境栈结构添加内容(let a 以及 let b)

image.png

在块结构里执行 console.log 时,先到函数执行上下文中的词法环境的栈里查找变量,如果再去函数执行上下文变量环境中找

这样一来就实现了对块级作用域的支持(并且兼容之前的 var 声明的变量)


整个流程里有两处对栈结构的利用:调用栈、词法环境中记录块级内容的栈结构。它们的共同点都是,在当前层工作完成后返回上一层

本文参考了 李兵的《浏览器工作原理与实践》