LuckyWinty / fe-weekly-questions

A pro to record some interview questions every week...
MIT License
341 stars 34 forks source link

JavaScript 是如何支持块级作用域的? #15

Open LuckyWinty opened 4 years ago

LuckyWinty commented 4 years ago

块级作用域就是通过词法环境的栈结构来实现的,而变量提升是通过变量环境来实现,通过这两者的结合,JavaScript 引擎也就同时支持了变量提升和块级作用域了。

词法环境跟函数上下文,都是通过栈结构实现的。函数内部通过 var 声明的变量,在编译阶段全都被存放到变量环境(函数上下文)中,而通过let和const申明的变量会被追加到词法环境中,当这个块执行结束之后,追加到词法作用域的内容又会销毁掉。

举个例子:

function foo() {
    var test = 1
    let myname= 'LuckyWinty'
    {
        console.log(myname) 
        let myname= 'winty'
    }
    console.log(test,'---',myname) 
}
foo()
//思考一下会输出什么?

执行到第一个console.log前的执行上下文是这样的:

GitHub

从图中看,第一个console.log理论上应该输出 undefined。但是语法规定了一个"暂时性死区(TDZ,当进入它的作用域,它不能被访问(获取或设置)直到执行到达声明)",也就是说虽然通过let声明的变量已经在词法环境中了,但是在没有赋值之前,访问该变量JavaScript引擎就会抛出一个错误。

因此,第一个console.log会抛错,[Uncaught ReferenceError: Cannot access 'myname' before initialization]。抛错则函数会中断执行,为了能让我们的代码继续分析,我们先加个 try-catch ,然后继续分析:

function foo() {
    var test = 1
    let myname= 'LuckyWinty'
    try{
        {
            console.log(myname) 
            let myname= 'winty'
        }
    }catch(ex){
        console.error(ex)
    }
    console.log(test,'---',myname) 
}
foo()
//思考一下会输出什么?

执行到第二个console.log前的执行上下文是这样的:

GitHub

此时,{}块作用域中的内容已执行完毕,被销毁掉了。第二个console.log会输出1 "---" "LuckyWinty"