[JavaScriptWeird]No.61 let 、 const 、 TDZ

前言

前面三節提到的變數均使用 var 宣告,原因是 ES6 之後加入的 let & const 在這部分的特性表現不一樣,本節將記錄它們有何不同之處。

let & const 的 hoisting

當你看過之前的文章,或許你會試著觀察 letconst 對於 hoisting 上的表現,於是我們可能這樣子寫。

1
2
console.log(a); // a is not defined  
let a = 10;

或是這樣子

1
2
console.log(a); // a is not defined  
const a = 10;

然後我們可能就直接下了定論「 letconst 沒有 hoisting」

但是這個小例子也許會讓我們更混淆

1
2
3
4
5
6
let a = 10;  
function test() {
console.log(a);
let a = 10;
}
test(); // a is not defined

混淆的點在於

  • 如果 let & const 沒有 hoisting ,那麼變數 a 會往全域找到外部的變數 a ,但實際執行卻得到「a is not defined
  • 但如果 let & const 有 hoisting,為什麼印出變數 a 時也會得到「a is not defined」而不是 「 undefined 」?

於是假設 let & const 有 hoisting,只是表現出來的特性不一樣,導致我們認為它們沒有 hoisting 。

那麼 let & const 的 hoisting 特性是什麼呢?

暫時性死區 (Temporal Dead Zone)

暫時性死區 (Temporal Dead Zone) 以下簡稱 TDZ,是 let & const 在 進行 hoisting 過程中產生的一種現象。

1
2
3
4
5
function test() {  
console.log(a); // a 的 TDZ 開始
let a = 10; // a 的 TDZ 結束
}
test(); // a is not defined

換句話說 let & const 是有 hoisting 的,只是表現出來的特性不一樣

  • var 宣告不同,這兩者不會於創造執行環境階段時被初始化為 undefined
  • 創造執行環境階段後,對於 let 宣告如果沒有賦值,執行到該行時則賦值該變數為 undefined
  • 提升後~賦值前會產生一個稱為暫時性死區 (TDZ) 的區域,在這個區域中不能對該變數做任何的存取,否則就會發生錯誤。
  • 必須等到賦值後,也就是 TDZ 結束後才能對變數進行存取。

使用 let 宣告但沒有賦值

1
2
3
4
5
6
function test() {  
// a 的 TDZ 開始
let a; // a 的 TDZ 結束
console.log(a);
}
test(); // undefined

以上就是 let & const & TDZ 的簡單觀念,其實這部分還有相當多細部的觀念可以寫,其餘較詳細的部分可以參考這一篇

後記

其實在寫這一篇的時候,因為糾結於部分觀念,導致寫作時花了很多時間。主要是為了確認:

  • let 於 創造執行環境階段時會不會被初始化為 undefined

遲疑的原因:

  • 在奇怪部份時,講師提到 「創造執行環境階段時全部的變數都會被初始化為 undefined 」,但考量時間因素,這支影片是在 ES6 之前推出,因此待求證。
  • 但本小節影片提到「 let 不會被賦予初始值 undefined

例子

1
2
3
4
5
function test() {  
let a;
console.log(a);
}
test();
  • 如果 let 於創造執行環境階段時不會被初始化為 undefined ,那麼為什最後印出的結果會是 undefined?

因此我有以下兩個想法

  • 「創造執行環境階段後,對於 let 宣告如果沒有賦值,執行到該行時則賦值該變數為 undefined
  • 「創造執行環境階段時全部的變數都會被初始化為 undefined ,只是 let 因為 TDZ 的關係,必須等到實際程式執行到宣告的那一行時才能對變數進行存取」

於是我寫信求指點~獲得回應如下:

的確兩種情況都有可能,到底是哪一種只能看 spec 來確認。

我在《我知道你懂 hoisting,可是你了解到多深?》的最後面附了一大堆參考資料,其實都是很有用的資源。

這兩篇有你要的解答:

連結內的文章有一段這麼寫:

let and const declarations define variables that are scoped to the running execution context’s LexicalEnvironment. The variables are created when their containing Lexical Environment is instantiated but may not be accessed in any way until the variable’s LexicalBinding is evaluated. A variable defined by a LexicalBinding with an Initializer is assigned the value of its Initializer’s AssignmentExpression when the LexicalBinding is evaluated, not when the variable is created. If a LexicalBindingin a let declaration does not have an Initializer the variable is assigned the value undefined when the LexicalBinding is evaluated.

上面寫說執行環境被創建時,變數就被建立了,但是一直到「the variable’s LexicalBinding is evaluated」之前都沒辦法訪問,這就是 TDZ。

那什麼是「the variable’s LexicalBinding is evaluated」?

  • 就是實際上宣告變數的那一行: let a

接著又提到「If a LexicalBindingin a let declaration does not have an Initializer the variable is assigned the value undefined when the LexicalBinding is evaluated.」

如果宣告變數時沒給值,預設值會是 undefined

  • 這邊的「宣告變數」指的是「when the LexicalBinding is evaluated
  • 也就是實際上 let a 這一行

所以總結以上,你的第一個理解才是正確的。

到此我才確定自己的理解是正確的,感謝 Huli 大的熱心解答。

結論是:

  • let 在創造執行環境階段時,不會被賦予初始值 undefined
  • 創造執行環境階段後,對於 let 宣告如果沒有賦值,執行到該行時則賦值該變數為 undefined

本來是不用特地把這一段再補上來的,但我認為可能也有很多初學者跟我有一樣的疑問,所以把來龍去脈整理出來,也有助於加深自己的理解。

0%