1、let命令
ES6新增了let
命令,用來聲明變量。它的用法類似於var
,但是所聲明的變量,只在let
命令所在的代碼塊內有效。
for
循環的計數器,就很合適使用let
命令。
下面的代碼如果使用var
,最后輸出的是10
。
var a = []; for (var i = 0; i < 10; i++) { a[i] = function () { console.log(i); }; } a[6](); // 10
上面代碼中,變量i
是var
聲明的,在全局范圍內都有效。所以每一次循環,新的i
值都會覆蓋舊值,導致最后輸出的是最后一輪的i
的值。(常見於閉包的考察)
如果使用let
,聲明的變量僅在塊級作用域內有效,最后輸出的是6。
var a = []; for (let i = 0; i < 10; i++) { a[i] = function () { console.log(i); }; } a[6](); // 6
上面代碼中,變量i
是let
聲明的,當前的i
只在本輪循環有效,所以每一次循環的i
其實都是一個新的變量,所以最后輸出的是6
。
JavaScript 引擎內部會記住上一輪循環的值,初始化本輪的變量i
時,就在上一輪循環的基礎上進行計算。
另外,for
循環還有一個特別之處,就是循環語句部分是一個父作用域,而循環體內部是一個單獨的子作用域。
不存在變量提升
var
命令會發生”變量提升“現象,即變量可以在聲明之前使用,值為undefined
。這種現象多多少少是有些奇怪的,按照一般的邏輯,變量應該在聲明語句之后才可以使用。
為了糾正這種現象,let
命令改變了語法行為,它所聲明的變量一定要在聲明后使用,否則報錯。
暫時性死區
只要塊級作用域內存在let
命令,它所聲明的變量就“綁定”(binding)這個區域,不再受外部的影響。
ES6明確規定,如果區塊中存在let
和const
命令,這個區塊對這些命令聲明的變量,從一開始就形成了封閉作用域。凡是在聲明之前就使用這些變量,就會報錯。
總之,在代碼塊內,使用let
命令聲明變量之前,該變量都是不可用的。這在語法上,稱為“暫時性死區”(temporal dead zone,簡稱 TDZ)。
“暫時性死區”也意味着typeof
不再是一個百分之百安全的操作(會使typeof報錯)。
總之,暫時性死區的本質就是,只要一進入當前作用域,所要使用的變量就已經存在了,但是不可獲取,只有等到聲明變量的那一行代碼出現,才可以獲取和使用該變量。
不允許重復聲明
let不允許在相同作用域內,重復聲明同一個變量。
2、塊級作用域
ES5只有全局作用域和函數作用域,沒有塊級作用域,這帶來很多不合理的場景。
第一種場景,內層變量可能會覆蓋外層變量。
第二種場景,用來計數的循環變量泄露為全局變量。
let
實際上為JavaScript新增了塊級作用域。
function f1() { let n = 5; if (true) { let n = 10; } console.log(n); // 5 }
上面的函數有兩個代碼塊,都聲明了變量n
,運行后輸出5。這表示外層代碼塊不受內層代碼塊的影響。如果使用var
定義變量n
,最后輸出的值就是10。
塊級作用域的出現,實際上使得獲得廣泛應用的立即執行函數表達式(IIFE)不再必要了。
// IIFE 寫法 (function () { var tmp = ...; ... }()); // 塊級作用域寫法 { let tmp = ...; ... }
塊級作用域與函數聲明
ES5 規定,函數只能在頂層作用域和函數作用域之中聲明,不能在塊級作用域聲明。
ES6 引入了塊級作用域,明確允許在塊級作用域之中聲明函數。
ES6 規定,塊級作用域之中,函數聲明語句的行為類似於let
,在塊級作用域之外不可引用。
考慮到環境導致的行為差異太大,應該避免在塊級作用域內聲明函數。如果確實需要,也應該寫成函數表達式,而不是函數聲明語句。