ES6新增了let
命令,用來聲明變量。它的用法類似于var
,但是所聲明的變量,只在let
命令所在的代碼塊內有效。
上面代碼在代碼塊之中,分別用let
和var
聲明了兩個變量。然后在代碼塊之外調用這兩個變量,結果let
聲明的變量報錯,var
聲明的變量返回了正確的值。這表明,let
聲明的變量只在它所在的代碼塊有效。
for
循環的計數器,就很合適使用let
命令。
上面代碼中,計數器i
只在for
循環體內有效,在循環體外引用就會報錯。
下面的代碼如果使用var
,最后輸出的是10
。
上面代碼中,變量i
是var
聲明的,在全局范圍內都有效。所以每一次循環,新的i
值都會覆蓋舊值,導致最后輸出的是最后一輪的i
的值。
如果使用let
,聲明的變量僅在塊級作用域內有效,最后輸出的是6。
上面代碼中,變量i
是let
聲明的,當前的i
只在本輪循環有效,所以每一次循環的i
其實都是一個新的變量,所以最后輸出的是6
。你可能會問,如果每一輪循環的變量i
都是重新聲明的,那它怎么知道上一輪循環的值,從而計算出本輪循環的值?這是因為 javaScript 引擎內部會記住上一輪循環的值,初始化本輪的變量i
時,就在上一輪循環的基礎上進行計算。
另外,for
循環還有一個特別之處,就是循環語句部分是一個父作用域,而循環體內部是一個單獨的子作用域。
上面代碼輸出了3次abc
,這表明函數內部的變量i
和外部的變量i
是分離的。
let
不像var
那樣會發生“變量提升”現象。所以,變量一定要在聲明后使用,否則報錯。
上面代碼中,變量foo
用var
命令聲明,會發生變量提升,即腳本開始運行時,變量foo
已經存在了,但是沒有值,所以會輸出undefined
。變量bar
用let
命令聲明,不會發生變量提升。這表示在聲明它之前,變量bar
是不存在的,這時如果用到它,就會拋出一個錯誤。
只要塊級作用域內存在let
命令,它所聲明的變量就“綁定”(binding)這個區域,不再受外部的影響。
上面代碼中,存在全局變量tmp
,但是塊級作用域內let
又聲明了一個局部變量tmp
,導致后者綁定這個塊級作用域,所以在let
聲明變量前,對tmp
賦值會報錯。
ES6明確規定,如果區塊中存在let
和const
命令,這個區塊對這些命令聲明的變量,從一開始就形成了封閉作用域。凡是在聲明之前就使用這些變量,就會報錯。
總之,在代碼塊內,使用let
命令聲明變量之前,該變量都是不可用的。這在語法上,稱為“暫時性死區”(temporal dead zone,簡稱 TDZ)。
上面代碼中,在let
命令聲明變量tmp
之前,都屬于變量tmp
的“死區”。
“暫時性死區”也意味著typeof
不再是一個百分之百安全的操作。
上面代碼中,變量x
使用let
命令聲明,所以在聲明之前,都屬于x
的“死區”,只要用到該變量就會報錯。因此,typeof
運行時就會拋出一個ReferenceError
。
作為比較,如果一個變量根本沒有被聲明,使用typeof
反而不會報錯。
上面代碼中,undeclared_variable
是一個不存在的變量名,結果返回“undefined”。所以,在沒有let
之前,typeof
運算符是百分之百安全的,永遠不會報錯。現在這一點不成立了。這樣的設計是為了讓大家養成良好的編程習慣,變量一定要在聲明之后使用,否則就報錯。
有些“死區”比較隱蔽,不太容易發現。
function bar(x = y, y = 2) { return [x, y];}bar(); // 報錯上面代碼中,調用bar
函數之所以報錯(某些實現可能不報錯),是因為參數x
默認值等于另一個參數y
,而此時y
還沒有聲明,屬于”死區“。如果y
的默認值是x
,就不會報錯,因為此時x
已經聲明了。
另外,下面的代碼也會報錯,與var
的行為不同。
上面代碼報錯,也是因為暫時性死區。使用let
聲明變量時,只要變量在還沒有聲明完成前使用,就會報錯。上面這行就屬于這個情況,在變量x
的聲明語句還沒有執行完成前,就去取x
的值,導致報錯”x 未定義“。
ES6規定暫時性死區和let
、const
語句不出現變量提升,主要是為了減少運行時錯誤,防止在變量聲明前就使用這個變量,從而導致意料之外的行為。這樣的錯誤在 ES5 是很常見的,現在有了這種規定,避免此類錯誤就很容易了。
總之,暫時性死區的本質就是,只要一進入當前作用域,所要使用的變量就已經存在了,但是不可獲取,只有等到聲明變量的那一行代碼出現,才可以獲取和使用該變量。
let不允許在相同作用域內,重復聲明同一個變量。
// 報錯function () { let a = 10; var a = 1;}// 報錯function () { let a = 10; let a = 1;}因此,不能在函數內部重新聲明參數。
function func(arg) { let arg; // 報錯}function func(arg) { { let arg; // 不報錯 }}ES5只有全局作用域和函數作用域,沒有塊級作用域,這帶來很多不合理的場景。
第一種場景,內層變量可能會覆蓋外層變量。
var tmp = new Date();function f() { console.log(tmp); if (false) { var tmp = "hello world"; }}f(); // undefined上面代碼中,函數f執行后,輸出結果為undefined
,原因在于變量提升,導致內層的tmp變量覆蓋了外層的tmp變量。
第二種場景,用來計數的循環變量泄露為全局變量。
var s = 'hello';for (var i = 0; i < s.length; i++) { console.log(s[i]);}console.log(i); // 5上面代碼中,變量i只用來控制循環,但是循環結束后,它并沒有消失,泄露成了全局變量。
let
實際上為Javascript新增了塊級作用域。
上面的函數有兩個代碼塊,都聲明了變量n
,運行后輸出5。這表示外層代碼塊不受內層代碼塊的影響。如果使用var
定義變量n
,最后輸出的值就是10。
ES6允許塊級作用域的任意嵌套。
{{{{{let insane = 'Hello World'}}}}};上面代碼使用了一個五層的塊級作用域。外層作用域無法讀取內層作用域的變量。
{{{{ {let insane = 'Hello World'} console.log(insane); // 報錯}}}};內層作用域可以定義外層作用域的同名變量。
{{{{ let insane = 'Hello World'; {let insane = 'Hello World'}}}}};塊級作用域的出現,實際上使得獲得廣泛應用的立即執行函數表達式(IIFE)不再必要了。
// IIFE 寫法(function () { var tmp = ...; ...}());// 塊級作用域寫法{ let tmp = ...; ...}函數能不能在塊級作用域之中聲明,是一個相當令人混淆的問題。
ES5規定,函數只能在頂層作用域和函數作用域之中聲明,不能在塊級作用域聲明。
// 情況一if (true) { function f() {}}// 情況二try { function f() {}} catch(e) {}上面代碼的兩種函數聲明,根據ES5的規定都是非法的。
但是,瀏覽器沒有遵守這個規定,為了兼容以前的舊代碼,還是支持在塊級作用域之中聲明函數,因此上面兩種情況實際都能運行,不會報錯。不過,“嚴格模式”下還是會報錯。
// ES5嚴格模式'use strict';if (true) { function f() {}}// 報錯ES6 引入了塊級作用域,明確允許在塊級作用域之中聲明函數。
// ES6嚴格模式'use strict';if (true) { function f() {}}// 不報錯ES6 規定,塊級作用域之中,函數聲明語句的行為類似于let
,在塊級作用域之外不可引用。
上面代碼在 ES5 中運行,會得到“I am inside!”,因為在if
內聲明的函數f
會被提升到函數頭部,實際運行的代碼如下。
ES6 的運行結果就完全不一樣了,會得到“I am outside!”。因為塊級作用域內聲明的函數類似于let
,對作用域之外沒有影響,實際運行的代碼如下。
很顯然,這種行為差異會對老代碼產生很大影響。為了減輕因此產生的不兼容問題,ES6在附錄B里面規定,瀏覽器的實現可以不遵守上面的規定,有自己的行為方式。
允許在塊級作用域內聲明函數。函數聲明類似于var
,即會提升到全局作用域或函數作用域的頭部。同時,函數聲明還會提升到所在的塊級作用域的頭部。注意,上面三條規則只對ES6的瀏覽器實現有效,其他環境的實現不用遵守,還是將塊級作用域的函數聲明當作let
處理。
前面那段代碼,在 Chrome 環境下運行會報錯。
// ES6的瀏覽器環境function f() { console.log('I am outside!'); }(function () { if (false) { // 重復聲明一次函數f function f() { console.log('I am inside!'); } } f();}());// Uncaught TypeError: f is not a function上面的代碼報錯,是因為實際運行的是下面的代碼。
// ES6的瀏覽器環境function f() { console.log('I am outside!'); }(function () { var f = undefined; if (false) { function f() { console.log('I am inside!'); } } f();}());// Uncaught TypeError: f is not a function考慮到環境導致的行為差異太大,應該避免在塊級作用域內聲明函數。如果確實需要,也應該寫成函數表達式,而不是函數聲明語句。
// 函數聲明語句{ let a = 'secret'; function f() { return a; }}// 函數表達式{ let a = 'secret'; let f = function () { return a; };}另外,還有一個需要注意的地方。ES6的塊級作用域允許聲明函數的規則,只在使用大括號的情況下成立,如果沒有使用大括號,就會報錯。
// 不報錯'use strict';if (true) { function f() {}}// 報錯'use strict';if (true) function f() {}轉載地址:http://es6.ruanyifeng.com/#docs/let
新聞熱點
疑難解答