JavaScript 中的作用域链

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

作用域链(Scope Chain)

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

Variable look up in scope chain

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

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

function fn() {
  console.log(a)
  var a = 3
}

相当于

function fn() {
  var a
  console.log(a)
  a = 3
}

另外,变量会先于函数提升。

先提升变量 a 再提升函数 a

引用未定义的变量会报 is not defined 错误

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

如下图所示:

在编译并创建执行上下文过程中,在执行59行代码前, 函数f1 和 变量v1 v2 都存起来了(提升到顶部), 并将变量初始化为undefined,如上图 debug 到59行时右侧的 Local 变量。
但是v2 是let 创建并在TDZ 里引用,所以会报引用错误。

注意,以var f2 = function() {} 这种形式定义的函数,提升的是初始化为undefined 的 变量 f2

块级作用域(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 ,i 指向同一个内存地址

// 如果将let 改成 var
var a = []
for (var i = 0; i < 10; i++) {
  a[i] = function () { console.log(i); };
}
a[6](); // output: 10 ,每个 i 指向不同的内存地址
// 可以值变参分割作用域
var a = []
for (var i = 0; i < 10; i++) {
  (function(i) {
    a[i] = function () { console.log(i); }
  })(i)
}
a[6](); // output: 6 ,参数i = 变量i, 值拷贝

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

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

块级作用域还是在相同的 foo() 函数作用域内,块级作用域会在执行完时释放。
a1 不在全局作用域,所以this.a1 为undefined, a2 是全局作用域,this.a2 为4

一个冷知识,就是 let 可以作变量