1. 程式人生 > >【ES6】let命令詳解

【ES6】let命令詳解

let 命令

ES6 新增了 let 命令,用來宣告變數。用法類似於 var,但是所宣告的變數,只在 let 命令所在的程式碼塊內有效。

{
    let a = 10;
    var b = 5;
}
alert(a)    // Uncaught ReferenceError: a is not defined
alert(b)

for 迴圈

for 迴圈的計數器,很合適使用 let 命令。
let 宣告的計數器 i 在 for 迴圈體內有效,在迴圈體外引用就會報錯。

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

在上面的程式碼中,變數 i 是 var 宣告的,在全域性範圍內都有效,所以全域性只有一個變數 i。每一次迴圈,變數 i 的值都會發生改變,而迴圈內被賦值給陣列 a 的函式內部的 console.log(i), 裡面的 i 指向的就是全域性的 i。也就是說,所有陣列 a 的成員裡面的 i ,指向的都是同一個 i ,導致執行時輸出的最後一輪的 i 的值,也就是 10。

如果我們使用 let ,宣告的變數僅在塊級作用域內有效,最後輸出的是 6。 因為用 let 宣告的 i ,只在本輪迴圈內有效,所以每一次迴圈的 i 其實都是一個新的變數,所以最後輸出的是 6。

如果每一輪迴圈的變數 i 都是重新宣告的,那它怎麼知道上一輪迴圈的值?
這是因為 JavaScript 引擎內部會記住上一輪迴圈的值,初始化本輪的 i 時,就在上一輪迴圈的基礎上進行計算。

另外,for 迴圈還有一個特別之處:設定迴圈變數的那部分是一個父作用域,而迴圈內部是一個單獨的子作用域。

for (let i = 0; i < 3; i++) {
  let i = 'abc';
  console.log(i);
}
// abc
// abc
// abc

上面程式碼輸出三次 abc ,說明函式內部的變數 i 與迴圈變數 i 不在同一個作用域。


沒有變數提升

var命令會發生”變數提升“現象,即變數可以在宣告之前使用,值為undefined。這種現象多多少少是有些奇怪的,按照一般的邏輯,變數應該在宣告語句之後才可以使用。

為了糾正這種現象,let命令改變了語法行為,它所宣告的變數一定要在聲明後使用,否則報錯。

// var 的情況
console.log(foo); // 輸出undefined
var foo = 2;

// let 的情況
console.log(bar); // 報錯ReferenceError
let bar = 2;

上面程式碼中,變數foo用var命令宣告,會發生變數提升,即指令碼開始執行時,變數foo已經存在了,但是沒有值,所以會輸出undefined。變數bar用let命令宣告,不會發生變數提升。這表示在宣告它之前,變數bar是不存在的,這時如果用到它,就會丟擲一個錯誤。


暫時性死區(TDZ)

只要塊級作用域記憶體在let命令,它所宣告的變數就“繫結”(binding)這個區域,不再受外部的影響。

var tmp = 123;

if (true) {
  tmp = 'abc'; // ReferenceError
  let tmp;
}

上面程式碼中,存在全域性變數tmp,但是塊級作用域內let又聲明瞭一個區域性變數tmp,導致後者繫結這個塊級作用域,所以在let宣告變數前,對tmp賦值會報錯。

ES6 明確規定,如果區塊中存在let和const命令,這個區塊對這些命令宣告的變數,從一開始就形成了封閉作用域。凡是在宣告之前就使用這些變數,就會報錯。

總之,在程式碼塊內,使用let命令宣告變數之前,該變數都是不可用的。這在語法上,稱為“暫時性死區”(temporal dead zone,簡稱 TDZ)。

if (true) {
  // TDZ開始
  tmp = 'abc'; // ReferenceError
  console.log(tmp); // ReferenceError

  let tmp; // TDZ結束
  console.log(tmp); // undefined

  tmp = 123;
  console.log(tmp); // 123
}

上面程式碼中,在 let 命令宣告變數 tmp 之前,都屬於變數 tmp 的“死區”。

“暫時性死區”也意味著 typeof 不再是一個百分之百安全的操作。
如果變數x使用 let 命令宣告,那在宣告之前,都屬於x的“死區”,只要用到該變數就會報錯。因此,typeof執行時就會丟擲一個ReferenceError。
作為比較,如果一個變數根本沒有被宣告,使用typeof反而不會報錯。

ES6 規定暫時性死區和let、const語句不出現變數提升,主要是為了減少執行時錯誤,防止在變數宣告前就使用這個變數,從而導致意料之外的行為。這樣的錯誤在 ES5 是很常見的,現在有了這種規定,避免此類錯誤就很容易了。


不允許重複宣告

let不允許在相同作用域內,重複宣告同一個變數。

// 報錯
function func() {
  let a = 10;
  var a = 1;
}

// 報錯
function func() {
  let a = 10;
  let a = 1;
}

因此,不能在函式內部重新宣告引數。

function func(arg) {
  let arg; // 報錯
}

function func(arg) {
  {
    let arg; // 不報錯
  }
}