作者:Dmitri Pavlutin翻譯:瘋狂的技術宅
原文:https://dmitripavlutin.com/ja...
未經允許嚴禁轉載
我問一個簡單的問題。以下哪個代碼片段將會產生錯誤?
第一個創建實例,然後定義所用的類:
new Car('red'); // Does it work?
class Car {
constructor(color) {
this.color = color;
}
}
第二個先調用然後定義函數:
greet('World'); // Does it work?
function greet(who) {
return `Hello, ${who}!`;
}
正確答案:第一個代碼段(帶有類)將生成 ReferenceError
。第二個工作正常。
如果你的答案與上述不同,或者在不知道底層發生了什麼的情況下進行了猜測,那麼你需要掌握臨時死區(TDZ)。
TDZ 管理 let
,const
和 class
語句的可用性。對於變量在 JavaScript 中的工作方式非常重要。
1.什麼是臨時死區(Temporal Dead Zone)
讓我們從一個簡單的 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)語義禁止在聲明變量之前訪問變量。它強制執行紀律:在聲明之前不要使用任何東西。
2. 受 TDZ 影響的語句
讓我們看看受 TDZ 影響的語句。
2.1 const 變量
正如你已經看到的,const
變量在 TDZ 中聲明和初始化行之前:
// Does not work!
pi; // throws `ReferenceError`
const pi = 3.14;
你必須在聲明後使用 const
變量:
const pi = 3.14;
// Works!
pi; // => 3.14
2.2 let 變量
在聲明行之前,let
聲明語句也會受到 TDZ 的影響:
// Does not work!
count; // throws `ReferenceError`
let count;
count = 10;
同樣,僅在聲明後使用 let
變量:
let count;
// Works!
count; // => undefined
count = 10;
// Works!
count; // => 10
2.3 class 語句
從簡介中可以看出,在定義類之前不能使用它:
// 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'
2.4 constructor()內部的 super()
如果擴展父類,則在構造函數內部調用 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'
2.5 默認函數參數
默認參數存在於 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
3. var,function,import 語句
與前面相反,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 文件的開頭加載模塊的依賴項是一個好的做法。
4. TDZ 中的 typeof 行爲
typeof
運算符可用於確定變量是否在當前作用域內定義。
例如,變量 notDefined
未定義,在這個變量上應用 typeof
運算符不會引發錯誤:
typeof notDefined; // => 'undefined'
由於未定義變量,因此 typeof notDefined
的值爲 undefined
。
但是當與臨時死區中的變量一起使用時,typeof
運算符有着不同的行爲。在這種情況下,JavaScript 會報錯:
typeof variable; // throws `ReferenceError`
let variable;
這個引用錯誤背後的原因是,你可以靜態地(僅通過查看代碼即可)確定已經定義了variable
。
5. TDZ 在當前作用域內運行
臨時死區會在存在聲明語句的作用域內影響變量。
讓我們來看一個例子:
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 僅存在於此內部作用域內。
6. 結論
TDZ 是一個重要概念,會影響 const
,let
和 class
語句的可用性。不允許在聲明前使用變量。
當你可以在聲明之前使用 var
變量時,它們會繼承舊的行爲。你應該避免這樣做。
在我看來,當把良好的編碼實踐進入語言規範時,TDZ 就是其中的一個好東西。
本文首發微信公衆號:前端先鋒
歡迎掃描二維碼關注公衆號,每天都給你推送新鮮的前端技術文章
歡迎繼續閱讀本專欄其它高贊文章:
- 深入理解Shadow DOM v1
- 一步步教你用 WebVR 實現虛擬現實遊戲
- 13個幫你提高開發效率的現代CSS框架
- 快速上手BootstrapVue
- JavaScript引擎是如何工作的?從調用棧到Promise你需要知道的一切
- WebSocket實戰:在 Node 和 React 之間進行實時通信
- 關於 Git 的 20 個面試題
- 深入解析 Node.js 的 console.log
- Node.js 究竟是什麼?
- 30分鐘用Node.js構建一個API服務器
- Javascript的對象拷貝
- 程序員30歲前月薪達不到30K,該何去何從
- 14個最好的 JavaScript 數據可視化庫
- 8 個給前端的頂級 VS Code 擴展插件
- Node.js 多線程完全指南
- 把HTML轉成PDF的4個方案及實現