ES6是如何解决变量提升的呢?

ES6引入了 let和const关键字,使Javascript 也能像其他语言一样拥有了块级作用域

  1. function varTest() {
  2. var x = 1;
  3. if (true) {
  4. var x = 2; // 同样的变量! 变量覆盖
  5. // let x = 2 // let 支持块级作用域,不会被提升作用域块内声明的变量不影响块外面的变量。
  6. console.log(x); // 2
  7. }
  8. console.log(x); // 2
  9. }

问题1:JavaScript 是如何支持块级作用域的?

  1. function foo(){
  2. var a = 1
  3. let b = 2
  4. {
  5. let b = 3
  6. var c = 4
  7. let d = 5
  8. console.log(a)
  9. console.log(b)
  10. }
  11. console.log(b)
  12. console.log(c)
  13. console.log(d)
  14. }
  15. foo()

第一步是编译并创建执行上下文
ES6如何实现块级作用域 - 图1

  • 函数内部通过 var 声明的变量,在编译阶段全都被存放到变量环境里面了。
  • 通过 let 声明的变量,在编译阶段会被存放到词法环境(Lexical Environment)中。
  • 在函数的作用域块内部,通过 let 声明的变量并没有被存放到词法环境中。

第二步继续执行代码,当执行到代码块里面时,变量环境中 a 的值已经被设置成了 1,词法环境中 b 的值已经被设置成了 2,这时候函数的执行上下文就如下图所示

ES6如何实现块级作用域 - 图2
当进入函数的作用域块时,作用域块中通过let声明的变量,会被存放在此法环境的一个单独的区域中,这个区域中的变量并不影响作用域外面的变量,比如作用域外声明了变量b,在该作用域内部也声明了变量b,当执行到作用域内部时,他们都是独立的存在。
其实,在词法环境内部,维护了一个小型栈结构,栈底是函数最外层的变量,进入一个作用域块后,就会把该作用域块内部的变量压到栈顶;当作用域执行完成之后,该作用域的信息就会从栈顶弹出,这就是词法环境的结构。需要注意下,我这里所讲的变量是指通过 let 或者 const 声明的变量。
再接下来,当执行到作用域块中的console.log(a)这行代码时,就需要在词法环境和变量环境中查找变量 a 的值了,具体查找方式是:沿着词法环境的栈顶向下查询,如果在词法环境中的某个块中查找到了,就直接返回给 JavaScript 引擎,如果没有查找到,那么继续在变量环境中查找。

ES6如何实现块级作用域 - 图3
当作用域块执行结束之后,其内部定义的变量就会从词法环境的栈顶弹出,最终执行上下文如下:
ES6如何实现块级作用域 - 图4

总结:

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