知道臨時死區(qū)你才能更好的使用 JS 變量
首先,來個一個簡單的問題。下列哪段代碼會產(chǎn)生錯誤:
第一個創(chuàng)建實例,然后定義使用的類:
- new Car('red'); // 是否會報錯?
- class Car {
- constructor(color) {
- this.color = color;
- }
- }
或者先于函數(shù)定義之前調(diào)用函數(shù):
- greet('World'); // 是否會報錯?
- function greet(who) {
- return `Hello, ${who}!`;
- }
正確的答案是:第一個代碼片段會報 ReferenceError: Cannot access 'Car' before initialization 錯誤。第二個代碼正常運行。
如果你的答案與上述不同,或者你在不知道這背后的原理是什么而進行了猜測,那么你需要掌握臨時死區(qū)(TDZ)的知識。
TDZ 管理 let、const 和 class 語法的可用性。變量在 JS 中的工作方式非常重要。
1. 什么是臨時死區(qū)
咱們先從一個簡單的 const 變量聲明開始。首先聲明并初始化變量,然后訪問它,一切正常運行:
- const white = '#FFFFFF';
- white; // => '#FFFFFF'
那如果在 聲明之前訪問 white 變量,會怎么樣?
- white; // throws `ReferenceError`
- const white = '#FFFFFF';
- white;
在 const white = '#FFFFFF' 語句之前的代碼行中,變量 white 位于臨時死區(qū)。
在 TDZ 中訪問 white 后,JS拋出ReferenceError: Cannot access 'white' before initialization
臨時死區(qū)語義禁止在變量聲明之前訪問它。它加強了順序:在聲明之前不要使用任何東西。
2. 受 TDZ 影響的聲明
來看看受 TDZ 影響的聲明。
(1) const變量
如前所述,const 變量位于聲明和初始化行之前的 TDZ 中:
- // 無法工作
- pi; // throws `ReferenceError`
- const pi = 3.14;
咱們必須在聲明之后使用 const 變量:
- const pi = 3.14;
- // Works!
- pi; // => 3.14
(2) let 變量
在聲明行之前,let 聲明語句也會受到 TDZ 的影響:
- // 無法工作
- count; // throws `ReferenceError`
- let count;
- count = 10;
同樣,僅在聲明之后使用 let 變量:
- let count;
- // Works!
- count; // => undefined
- count = 10;
- // Works!
- count; // => 10
(3) class 的聲明
正如在介紹中看到的,在定義 class 之前不能使用它:
- // 無法工作
- const myNissan = new Car('red'); // throws `ReferenceError`
- class Car {
- constructor(color) {
- this.color = color;
- }
- }
(4) 構(gòu)造函數(shù)內(nèi)部的 super()
如果在構(gòu)造函數(shù)中調(diào)用 super()之前擴展父類,則此綁定位于 TDZ 中。
- class MuscleCar extends Car {
- constructor(color, power) {
- this.power = power;
- super(color);
- }
- }
- // Does not work!
- const myCar = new MuscleCar('blue', '300HP'); // `ReferenceError`
在構(gòu)造 constructor() 中,在調(diào)用super()之前不能使用 this。
TDZ 建議調(diào)用父構(gòu)造函數(shù)來初始化實例。這樣做之后,實例就準備好了,就可以在子構(gòu)造函數(shù)中進行調(diào)整。
- class MuscleCar extends Car {
- constructor(color, power) {
- super(color);
- this.power = power;
- }
- }
- // Works!
- const myCar = new MuscleCar('blue', '300HP');
- myCar.power; // => '300HP'
(5) 默認函數(shù)參數(shù)
默認參數(shù)存在于一個中間作用域中,與全局作用域和函數(shù)作用域分離。默認參數(shù)也遵循 TDZ 限制。
- const a = 2;
- function square(aa = a) {
- return a * a;
- }
- // Does not work!
- square(); // throws `ReferenceError`
在聲明表達式 a = a之前,在表達式的右側(cè)使用參數(shù) a,這將生成關(guān)于 a 的引用錯誤。
確保在聲明和初始化之后使用默認參數(shù)。咱們可以使用一個特殊的變量 init,該變量在使用前已初始化:
- const init = 2;
- function square(a = init) {
- return a * a;
- }
- // Works!
- square(); // => 4
3. var, function, import 語句
與上述陳述相反,var 和 function 定義不受 TDZ 的影響。它們被提升到當前的作用域頂部。
如果在聲明之前訪問 var 變量,則只會得到一個 undefined的變量
- // 正常運行, 但不要這樣做!
- value; // => undefined
- var value;
但是,可以根據(jù)函數(shù)的定義位置來使用它:
- // 正常工作
- greet('World'); // => 'Hello, World!'
- function greet(who) {
- return `Hello, ${who}!`;
- }
- // 正常工作
- greet('Earth'); // => 'Hello, Earth!'
通常,咱們一般對函數(shù)的實現(xiàn)不太感興趣,而只是想調(diào)用它。因此,有時在定義函數(shù)之前先調(diào)用該函數(shù)是有意義的。
有趣的是,import 模塊也被提升了。
- // 正常工作
- myFunction();
- import { myFunction } from './myModule';
當然,建議將 import 寫在文件開頭,以便讀寫方法。
4. TDZ 中的 typeof 行為
typeof 操作符用于確定是否在當前作用域內(nèi)定義了變量。
例如,未定義變量 notDefined。對該變量應用 typeof 操作符不會引發(fā)錯誤:
- typeof notDefined; // => 'undefined'
因為變量沒有定義,所以 typeof notDefined 的值為 undefined。
但是 typeof 操作符在與臨時死區(qū)中的變量一起使用時具有不同的行為。在本例中,JS 拋出一個錯誤:
- typeof variable; // throws `ReferenceError`
- let variable;
此引用錯誤背后的原因是您可以靜態(tài)地(僅通過查看代碼)確定已經(jīng)定義了該變量。
5. TDZ 在當前作用域內(nèi)采取行動
臨時死區(qū)在聲明語句所在的作用域內(nèi)影響變量。
來看看例子:
- function doSomething(someVal) {
- // 函數(shù)作用域
- typeof variable; // => undefined
- if (someVal) {
- // 內(nèi)部塊使用域
- typeof variable; // throws `ReferenceError`
- let variable;
- }
- }
- doSomething(true);
有 2 個作用域:
- 函數(shù)作用域
- 定義 let 變量的內(nèi)部塊作用域
在函數(shù)作用域中,typeof variable 的計算結(jié)果為 undefined。在這里,let 變量語句的 TDZ 沒有作用。
在內(nèi)部作用域中,typeof variable 語句在聲明之前使用一個變量,拋出一個錯誤。ReferenceError:在初始化之前不能訪問‘variable’,TDZ 只存在于這個內(nèi)部作用域內(nèi)。
6. 總結(jié)
TDZ 是影響 const、let 和 class 語句可用性的重要概念。它不允許在聲明之前使用變量。
相反,可以在聲明之前使用 var 變量時,var 變量會繼承較舊的行為,應該避免這樣做。
在我看來,TDZ是語言規(guī)范中良好的編碼實踐之一。