let/const能实现块级作用域和阻止变量提升
先来一道“开胃菜“:
console.log(name1);
var name1 = "demo1";
console.log(name2);
let name2 = "demo2";
来,控制台要输出什么?
很显然,控制台将第一次输出undefined。
第一次非常容易理解:使用var 声明的变量会变量提升,控制台输出将在变量赋值之前,你可以理解成是下面这样的代码:
var name1 ;
console.log(name1);
name1 = "demo1";
第二次将直接报错,原因是es6的语法当中,let声明的变量在未声明前不允许使用,即“暂时性死区”。
这里就不解释了,比较简单且基础,但是我还是想额外地说明一下这样设计本身的缺陷性。
对这个点不感兴趣的读者,可以跳过,直接进入标题二:
1.var变量声明导致变量提升暗藏的语法缺陷
我还是用代码来说明JavaScript在变量和作用域设计上的缺陷性(尽管这并不影响JavaScript在我心目中的重要性)
var num = 1;
function sayNum(){
var num = 2;
if(true){
var num = 3;
console.log(num)
}
console.log(num)
}
sayNum();
好,现在提问,控制台2次分别输出什么?
毫无疑问,两次都应该是3。
在我写下这段代码的时候,我实际上是希望它能在第1次输出3,第2次输出2。
然而,JavaScript是一门没有块级作用域的语言,这样的缺陷会导致if代码块内声明的变量提升到函数作用域内。
这也就是为什么在es6语法中,要引入块级作用域的原因:变量在没有声明之前就被访问,这在编程语言当中是不合理的,同时也是难以被理解的一个逻辑。
2.从JavaScript的编译原理来谈谈const/let
尽管es6语法引入了let/const,让开发者可以解决上面的代码导致的缺陷,但是,JavaScript本身仍然保留着变量提升的特色
这在我学习的途中常常使我感到迷惑:既要保留变量提升的“缺陷”,又要修补和避开它,这是为什么?
这就需要了解JavaScript的编译原理。
还是用代码说明:
function sayNum(){
var num1 = 1;
let num2 = 2;
{
var num3 = 3;
let num4 = 4;
}
}
sayNum();
JavaScript是一门“解释执行”的语言,代码在执行的时候,会预先经过一段“编译”的过程。
在编译阶段:
使用var声明的变量会进入变量环境,而使用const/let声明的变量会进入词法环境
3.let/const怎样实现块级作用域
回归代码,我来结合词法环境和变量环境来谈谈let/const怎样实现块级作用域的。
const sayNum = () => {
const num1 = 1;
var num2 = 2;
{
var num3 = 3;
const num4 = 4;
{
var num5 = 5;
const num6 = 6;
}
}
console.log(num1);
console.log(num2);
console.log(num3);
console.log(num4);
console.log(num5);
console.log(num6);
}
sayNum();
假设我们在这里忽略JavaScript的单线程原理,然后我们来看下控制台会打印什么。
按照顺序如下:
console.log(num1); //1
console.log(num2); //2
console.log(num3); //3
console.log(num4); //报错,变量未声明
console.log(num5); //5
console.log(num6); //报错,变量未声明
在编译阶段,执行上下文如下:
编译阶段
在编译阶段,通过var声明的变量,会提升到函数变量环境中。
而let/const声明的变量,会被推入词法环境中。
在代码块内,通过let/const声明的变量,既不会推入变量环境,也不会推入词法环境。
现在我们会看代码,当代码执行到代码块时,词法环境会推入一个小型栈结构,如下所示:
执行到代码块时候的执行上下文
这个时候,词法环境会形成一个小型的栈结构,当前代码块内的let/const生命的变量,会遵循“后进先出”的原则被推入首位,查询变量的时候,会先从栈的首位开始查找。
栈的每个结构之间相互独立,不受影响。
同样的道理,在执行到下一个代码块的时候,也会出现代码块中的let/const声明的变量被推入栈的情况。
当作用域被执行完后,该作用域的信息会被从栈顶依次弹出,进而我们无法在代码块外访问到我们在代码块内声明的变量,因而这就是let/const命令能形成块级作用域的原因