基于一道面试题,了解当代浏览器块级作用域的变态机制

1,084 阅读3分钟

基于一道面试题,了解当代浏览器块级作用域的变态机制

var a = 0;
if (true) {
    a = 1;
    function a({};
    a = 21;
    console.log(a)
}
console.log(a);

按照正常逻辑思维和JS运行机制(变量提升、块级作用域...)思考这道题,我们会得出答案:分别输出21和21;当你信心满满的在浏览器验证自己的答案时,感觉自己心态崩了,竟然输出21和1!

  • 我们把解题过程给呈现一下
    • EC(G)变量提升(var a、function a)
    • 一旦条件成立,进来的第一件事就是把之前未定义的函数进行定义赋值,当你此时在判断体第一行输出一下a,输出的是函数a
    • 当你继续执行代码时,会遇到a=1,此时改变的是全局变量a的值
    • 继续向下执行代码,执行function函数,在现有浏览器机制中,如果判断题中(块级作用域)遇到function定义的变量,它下面的代码中出现的变量重新赋值,都只对当前块起作用,对外层上下文没有影响
    • 所以再继续执行代码时,执行a=21时,改变的是自己块级作用域下的a的值,不会对外层作用域的a有影响,所以继续执行代码输出a时,输出21
    • 当输出外层a的值时,输出的是1

这里需要注意一下:在新版本浏览器中,变量提升阶段判断体(if)中的函数只提前声明(不会赋值),老版本浏览器中是声明+定义同时处理

  • 如果此时我们改变一下源代码,将function声明的变量a改成:var a=function(){};这样我们从新执行代码就会依次输出21和21,就会跟我们第一次预想的一样,之所以出现以上题的答案,是因为浏览器要同时兼容ES6和ES3语法规范,导致出现这种变态的机制
  • 这道题我们还可以简单的理解为,在块级作用域下,只要用function声明的变量,在function上面的代码还是会按照规改变预想的代码,在它之后的改变变量代码会存放在自己块级作用域中,不影响外界代码

块级作用域: 如果{}中(例如:判断体和循环体)出现了let/const/.../function 则在代码执行的时候产生一个块级作用域(私有作用域,声明的变量是私有变量); =>如果是let/const不存在变量提升,所以不允许在声明变量之前使用变量; =>如果是function则比较特殊:首先保留了原有ES3中的相关特性,例如变量提升,在块级作用域刚形成的时候,首先就会声明+定义函数(把其理解为块级作用域中私有的,也不会像LET一样,他在定义的代码之前也能使用这个变量);其次,为了更好的兼容ES3规范,截止目前,把function定义代码之前的操作结果,在块作用域结束后,同步到上级上下文的变量中(之后的代码是不管的)

本文使用 mdnice 排版