JavaScript 中的作用域链

const 和 let 是ES6 新增的块级作用域变量申明方法,其中const 声明的是常量,他们同var 有哪些区别呢。

var a = 0;
if (true) {
  console.log(a)
  let a = 0;
  console.log(a)
}

这段代码会抛出一个引用错误(ReferenceError),在块级作用域中用 let 申明了变量 a, 但在声明变量前使用 a 会进入TDZ暂时性死区,因为在块级作用域已经找到了变量a,就不会再沿作用域链向上查找别的变量 a,在死区里引用就会 throw 一个 ReferenceError

作用域链(Scope Chain)

当代码中引用一个变量时,JavaScript 引擎会从当前作用域开始向上查找该变量,直到找到该变量或者到达全局作用域为止。这个查找过程就是作用域链。如果找到了该变量,就会使用它所在的作用域中的值;否则,就会抛出 ReferenceError 异常。

Variable look up in scope chain

变量提升🏗 和 let const 的暂时性死区

JavaScript 引擎会在代码执行之前的编译过程中扫描整个作用域,并将所有的函数声明和变量声明“提升”🏗 到作用域的顶部。
对于var , let , const 的创建和初始化都是提升的,并将值初始化为 undefined,但赋值不会提升。

但使用 let 或 const 声明的变量或常量存在“暂时性死区”(Temporal Dead Zone, TDZ),TDZ 是指从当前块级作用域开始到变量声明位置结束的区域,如果在这个区域访问该变量或常量会抛出一个引用错误(ReferenceError)。

对于一个function 它的创建、初始化和赋值是同时提升的。

f1 在编译时创建执行上下文时就存起来了,v1 v2 都都初始化为undefined,但是v2 是let 创建并在TDZ 里引用,就会报引用错误。

块级作用域(Block Scope)

ES5 只有全局作用域(Global Scope)和函数作用域(Function Scope),而 ES6 开始有块级作用域(Block Scope),用let const 申明的变量只能在块级作用域里有效。

块级作用域可以避免变量的污染和命名冲突。在块级作用域中声明的变量在块执行完后会被释放,不会污染全局作用域。

var a = 10
let a = 11 // caught SyntaxError: Identifier 'a' has already been declared
{
  let a = 12
  console.log(a) // 12
}
{
  const a = 13
  console.log(a) // 13
}

块级作用域以括号{}分开,还有while(){}, if(){} ,try{} catch(e){},for 循环。

for 循环中每一次循环都是一个独立的作用域。

var a = []
for (let i = 0; i < 10; i++) {
  a[i] = function () { console.log(i); };
}
a[6](); // output: 6 

// 如果将let 改成 var
var a = []
for (var i = 0; i < 10; i++) {
  a[i] = function () { console.log(i); };
}
a[6](); // output: 10 
// 可以值变参分割作用域
var a = []
for (var i = 0; i < 10; i++) {
  (function(i) {
    a[i] = function () { console.log(i); }
  })(i)
}
a[6](); // output: 6 

使用 let 和 const 声明的变量常量将被限制在当前块级作用域内,并且在这个块级作用域外部是不可访问的。

虽然块级作用域可以限制变量的作用域范围,但是它并不会为块中声明的变量分配一个新的执行上下文。在 JavaScript 中,只有函数会创建新的函数执行上下文(Function Execution Context),而块级作用域不会。

块级作用域还是在相同的 foo() 函数作用域内,块级作用域会在执行完时释放。