浅谈JS中let与var为什么不能声明同名变量

1,361 阅读4分钟

在JavaScript中,当使用var重复声明同名变量,程序是不会报错的,但当使用let和var声明同名变量时却会报错。这是一个有趣的现象,对于这种情况,浅谈一下我对它的理解。

1. 同一变量的两种状态

在翻阅书籍时,书上对这种现象做了解释:

对声明冗余报错不会因混用 let 和 var 而受影响。这两个关键字声明的并不是不同类型的变量, 它们只是指出变量在相关作用域如何存在。

如何解释这段话?首先我们要知道,对于一个函数,JS为了在执行时更方便的调用变量,会在函数执行前一刻进行预编译。在预编译过程中,编译引擎会为函数开辟一个作用域,其中有函数的执行上下文。执行上下文中存有各种数据,其中就包含着函数声明的变量。需要注意的是,此时因为还是处于预编译状态,函数还未执行,故此时的执行上下文中只存储着变量的声明,并未对变量进行操作(var声明的变量会先自动赋值为undefind,let则不会)。

无标题.png

在函数内部,由var声明的变量,无论是否处于块级区域,都只储存到函数执行上下文中变量环境下的作用域中。而由let声明的变量,则根据当前所处的块级(图中为{ }所包含的区域),在词法环境下为其开辟一个块级作用域(绿色方框),然后将变量放入该块级作用域中。

在词法环境中,块级作用域是以入栈的方式开辟,出栈的方式销毁,即先创建在下方,后创建的在上方,执行完毕后依次由上而下销毁。当查找、使用某一变量时,JS引擎会依据当前所处的块级作用域在词法环境中依次向下方查找该变量,找到则停止查找,使用该变量,若到栈底未找到,则到变量环境下查找。若仍未找到,则会去全局作用域下查找。所以下方的代码中变量a是不会报错的,因为它们处于不同的作用域下。

function foo() {
  var a;
  {
    let a;  // 合法
    var b;
    let b;  // 报错
    {
      let a;  // 合法
    }
  }
}

但当它们处于同一作用域时,例如上方代码中的变量b,首先编译器会判断它们所指的是同一个变量,但对变量b分配到变量环境还是词法环境,编译器无法进行判断,故会产生报错。换句话说,无论应当处于变量环境,还是处于词法环境,这只是在描述变量b的两种状态。对于同一变量,它理应只存在一种状态。

2. 变量提升与暂时性死区

或许我们还能从另一方面考虑,对于var声明的变量,它存在着变量提升,即无论你在何处用var声明变量,系统会自动将该变量的声明提升到当前作用域的顶部。而对于let声明的变量,则存在另一种特性:暂时性死区。let声明的变量会“绑定”当前作用域,如果在此let声明之前使用该变量,则会抛出错误,即形成暂时性死区。

无标题2.png

显然,当你在同一作用域下分别用var和let声明同名变量时,哪怕你是在代码块最底部用var声明的,系统都会自动将它的声明“置顶”。而对于let声明,它并不允许它声明的变量在此之前被使用,那么冲突便发生了。

3. 总结

无论以哪种方式去理解,关于let和var,同级下声明同名变量总是错误的。哪怕不会报错,在实际编码中,我们也理应尽量去避免声明同名变量。以上只是我个人的一些观点和看法,以便于理解let和var的一些特殊现象。