做者:Dmitri Pavlutin翻譯:瘋狂的技術宅javascript
原文:https://dmitripavlutin.com/ja...前端
未經容許嚴禁轉載java
我問一個簡單的問題。如下哪一個代碼片斷將會產生錯誤?程序員
第一個建立實例,而後定義所用的類:面試
new Car('red'); // Does it work? class Car { constructor(color) { this.color = color; } }
第二個先調用而後定義函數:segmentfault
greet('World'); // Does it work? function greet(who) { return `Hello, ${who}!`; }
正確答案:第一個代碼段(帶有類)將生成 ReferenceError
。第二個工做正常。服務器
若是你的答案與上述不一樣,或者在不知道底層發生了什麼的狀況下進行了猜想,那麼你須要掌握臨時死區(TDZ)。微信
TDZ 管理 let
,const
和 class
語句的可用性。對於變量在 JavaScript 中的工做方式很是重要。多線程
讓咱們從一個簡單的 const
變量聲明開始。若是首先聲明並初始化變量,而後訪問它,那麼一切都會按預期進行:框架
const white = '#FFFFFF'; white; // => '#FFFFFF'
如今讓咱們試着在聲明以前訪問 white
變量:
white; // throws `ReferenceError` const white = '#FFFFFF'; white;
在到 const white = '#FFFFFF'
語句的代碼行以前,變量 white
位於時間死區中。
在 TDZ 中訪問了 white
以後,JavaScript 會拋出 ReferenceError: Cannot access 'white' before initialization
。
TDZ(Temporal Dead Zone)語義禁止在聲明變量以前訪問變量。它強制執行紀律: 在聲明以前不要使用任何東西。
讓咱們看看受 TDZ 影響的語句。
正如你已經看到的,const
變量在 TDZ 中聲明和初始化行以前:
// Does not work! pi; // throws `ReferenceError` const pi = 3.14;
你必須在聲明後使用 const
變量:
const pi = 3.14; // Works! pi; // => 3.14
在聲明行以前,let
聲明語句也會受到 TDZ 的影響:
// Does not work! count; // throws `ReferenceError` let count; count = 10;
一樣,僅在聲明後使用 let
變量:
let count; // Works! count; // => undefined count = 10; // Works! count; // => 10
從簡介中能夠看出,在定義類以前不能使用它:
// Does not work! const myNissan = new Car('red'); // throws `ReferenceError` class Car { constructor(color) { this.color = color; } }
爲了使它起做用,請在定義後保留類用法:
class Car { constructor(color) { this.color = color; } } // Works! const myNissan = new Car('red'); myNissan.color; // => 'red'
若是擴展父類,則在構造函數內部調用 super()
以前,this
綁定位於 TDZ 中:
class MuscleCar extends Car { constructor(color, power) { this.power = power; super(color); } } // Does not work! const myCar = new MuscleCar('blue', '300HP'); // `ReferenceError`
在constructor()
內部,this
在調用 super()
以前不能使用。
TDZ 建議調用父構造函數來初始化實例。完成以後,實例已準備就緒,你能夠在子構造函數中進行調整。
class MuscleCar extends Car { constructor(color, power) { super(color); this.power = power; } } // Works! const myCar = new MuscleCar('blue', '300HP'); myCar.power; // => '300HP'
默認參數存在於 intermidiate 做用域內,與全局做用域和函數做用域分開。默認參數還遵循 TDZ 限制:
const a = 2; function square(a = a) { return a * a; } // Does not work! square(); // throws `ReferenceError`
在聲明前,在表達式 a = a
的右側使用參數 a
。這會產生關於 a
的引用錯誤。
要確保在聲明和初始化以後使用默認參數。讓咱們使用特殊的變量 init
,該變量在使用前已初始化:
const init = 2; function square(a = init) { return a * a; } // Works! square(); // => 4
與前面相反,var
和 function
的定義不受 TDZ 的影響。它們在當前做用域內被提高。
若是在聲明以前訪問 var
變量,則只會獲得 undefined
:
// Works, but don't do this! value; // => undefined var value;
However, a function can be used regarding where it is defined:
可是,可使用函數定義其位置:
// Works! greet('World'); // => 'Hello, World!' function greet(who) { return `Hello, ${who}!`; } // Works! greet('Earth'); // => 'Hello, Earth!'
一般來講你對函數的實現不太感興趣,而只是想調用它。因此有時在定義函數以前先調用該函數是有意義的。
有趣的是, import
模塊也被提高:
// Works! myFunction(); import { myFunction } from './myModule';
import
時,在 JavaScript 文件的開頭加載模塊的依賴項是一個好的作法。
typeof
運算符可用於肯定變量是否在當前做用域內定義。
例如,變量 notDefined
未定義,在這個變量上應用 typeof
運算符不會引起錯誤:
typeof notDefined; // => 'undefined'
因爲未定義變量,所以 typeof notDefined
的值爲 undefined
。
可是當與臨時死區中的變量一塊兒使用時,typeof
運算符有着不一樣的行爲。在這種狀況下,JavaScript 會報錯:
typeof variable; // throws `ReferenceError` let variable;
這個引用錯誤背後的緣由是,你能夠靜態地(僅經過查看代碼便可)肯定已經定義了variable
。
臨時死區會在存在聲明語句的做用域內影響變量。
讓咱們來看一個例子:
function doSomething(someVal) { // Function scope typeof variable; // => undefined if (someVal) { // Inner block scope typeof variable; // throws `ReferenceError` let variable; } } doSomething(true);
There are 2 scopes:
有 2 個做用域:
let
變量的內部塊做用域在函數做用域內,typeof variable
僅計算爲 undefined
。在這裏, let variable
語句的 TDZ 無效。
在內部做用域中,在聲明以前使用變量的 typeof variable
語句引起錯誤ReferenceError: Cannot access 'variable' before initialization
。 TDZ 僅存在於此內部做用域內。
TDZ 是一個重要概念,會影響 const
,let
和 class
語句的可用性。不容許在聲明前使用變量。
當你能夠在聲明以前使用 var
變量時,它們會繼承舊的行爲。你應該避免這樣作。
在我看來,當把良好的編碼實踐進入語言規範時,TDZ 就是其中的一個好東西。