知道臨時死區你才能更好的使用 JS 變量

首先,來個一個簡單的問題。下列哪段代碼會產生錯誤:

第一個創建實例,然後定義使用的類:

知道臨時死區你才能更好的使用 JS 變量

或者先於函數定義之前調用函數:

<code>greet('World'); // 是否會報錯?

function greet(who) {
  return `Hello, ${who}!`;
}   /<code>

正確的答案是:第一個代碼片段會報 ReferenceError: Cannot access 'Car' before initialization 錯誤。第二個代碼正常運行。

如果你的答案與上述不同,或者你在不知道這背後的原理是什麼而進行了猜測,那麼你需要掌握臨時死區(TDZ)的知識。

TDZ 管理 let、const 和 class 語法的可用性。變量在 JS 中的工作方式非常重要。

1.什麼是臨時死區

咱們先從一個簡單的 const 變量聲明開始。首先聲明並初始化變量,然後訪問它,一切正常運行:

<code>const white = '#FFFFFF';

white; // => '#FFFFFF'/<code>

那如果在 聲明之前訪問 white 變量,會怎麼樣》

<code>white; // throws `ReferenceError` 


const white = '#FFFFFF';

white;/<code>

在 const white = '#FFFFFF' 語句之前的代碼行中,變量 white 位於臨時死區

TDZ 中訪問 white 後,JS拋出ReferenceError: Cannot access 'white' before initialization

知道臨時死區你才能更好的使用 JS 變量

臨時死區語義禁止在變量聲明之前訪問它。它加強了順序:在聲明之前不要使用任何東西。

2.1 const變量

如前所述,const 變量位於聲明和初始化行之前的 TDZ 中:

<code>// 無法工作
pi; // throws `ReferenceError`

const pi = 3.14;/<code>

咱們必須在聲明之後使用 const 變量:

<code>const pi = 3.14;

// Works!
pi; // => 3.14/<code>

2.2 let 變量

在聲明行之前,let 聲明語句也會受到 TDZ 的影響:

<code>// 無法工作
count; // throws `ReferenceError`

let count;

count = 10;/<code>

同樣,僅在聲明之後使用 let 變量:

<code>let count;

// Works!
count; // => undefined


count = 10;

// Works!
count; // => 10/<code>

正如在介紹中看到的,在定義 class 之前不能使用它:

知道臨時死區你才能更好的使用 JS 變量

2.4 構造函數內部的 super()

如果在構造函數中調用 super()之前擴展父類,則此綁定位於 TDZ 中。

知道臨時死區你才能更好的使用 JS 變量

在構造 constructor() 中,在調用super()之前不能使用 this。

TDZ 建議調用父構造函數來初始化實例。這樣做之後,實例就準備好了,就可以在子構造函數中進行調整。

知道臨時死區你才能更好的使用 JS 變量

2.5 默認函數參數

默認參數存在於一箇中間作用域中,與全局作用域和函數作用域分離。默認參數也遵循 TDZ 限制。

<code>const a = 2;
function square(a = a) {
  return a * a;
}
// Does not work!
square(); // throws `ReferenceError`/<code>

在聲明表達式 a = a之前,在表達式的右側使用參數 a,這將生成關於 a 的引用錯誤。

確保在聲明和初始化之後使用默認參數。咱們可以使用一個特殊的變量 init,該變量在使用前已初始化:

<code>const init = 2;
function square(a = init) {
  return a * a;
}
// Works!
square(); // => 4/<code>

3.var, function, import 語句

與上述陳述相反,var 和 function 定義不受 TDZ 的影響。它們被提升到當前的作用域頂部。

如果在聲明之前訪問 var 變量,則只會得到一個 undefined 的變量

<code>// 正常運行, 但不要這樣做!
value; // => undefined

var value;/<code>

但是,可以根據函數的定義位置來使用它:


知道臨時死區你才能更好的使用 JS 變量

通常,咱們一般對函數的實現不太感興趣,而只是想調用它。因此,有時在定義函數之前先調用該函數是有意義的。

有趣的是,import 模塊也被提升了。

<code>// 正常工作
myFunction();

import { myFunction } from './myModule';/<code>

當然,建議將 import 寫在文件開頭,以便讀寫方法。

4. TDZ 中的 typeof 行為

typeof 操作符用於確定是否在當前作用域內定義了變量。

例如,未定義變量 notDefined。對該變量應用 typeof 操作符不會引發錯誤:

<code>typeof notDefined; // => 'undefined'/<code>

因為變量沒有定義,所以 typeof notDefined 的值為 undefined。

但是 typeof 操作符在與臨時死區中的變量一起使用時具有不同的行為。在本例中,JS 拋出一個錯誤:

<code>typeof variable; // throws `ReferenceError`

let variable;/<code>

此引用錯誤背後的原因是您可以靜態地(僅通過查看代碼)確定已經定義了該變量。

5. TDZ 在當前作用域內採取行動

臨時死區在聲明語句所在的作用域內影響變量。

知道臨時死區你才能更好的使用 JS 變量

來看看例子:

知道臨時死區你才能更好的使用 JS 變量

有 2 個作用域:

  • 函數作用域
  • 定義 let 變量的內部塊作用域

在函數作用域中,typeof variable 的計算結果為 undefined。在這裡,let 變量語句的 TDZ 沒有作用。

在內部作用域中,typeof variable 語句在聲明之前使用一個變量,拋出一個錯誤。ReferenceError:在初始化之前不能訪問‘variable’,TDZ 只存在於這個內部作用域內。

6.總結

TDZ 是影響 const、let 和 class 語句可用性的重要概念。它不允許在聲明之前使用變量。

相反,可以在聲明之前使用 var 變量時,var 變量會繼承較舊的行為,應該避免這樣做。

在我看來,TDZ是語言規範中良好的編碼實踐之一。


原文:https://dmitripavlutin.com/javascript-variables-and-temporal-dead-zone/


分享到:


相關文章: