別在不知道臨時死區的情況下使用 JavaScript 變量

作者: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 管理 letconstclass 語句的可用性。對於變量在 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

image.png

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. varfunctionimport 語句

與前面相反,varfunction 的定義不受 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 在當前作用域內運行

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

image.png

讓我們來看一個例子:

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 個作用域:

  1. 函數作用域
  2. 定義 let 變量的內部塊作用域

在函數作用域內,typeof variable 僅計算爲 undefined。在這裏, let variable 語句的 TDZ 無效。

在內部作用域中,在聲明之前使用變量的 typeof variable 語句引發錯誤ReferenceError: Cannot access 'variable' before initialization。 TDZ 僅存在於此內部作用域內。

6. 結論

TDZ 是一個重要概念,會影響 constletclass 語句的可用性。不允許在聲明前使用變量。

當你可以在聲明之前使用 var 變量時,它們會繼承舊的行爲。你應該避免這樣做。

在我看來,當把良好的編碼實踐進入語言規範時,TDZ 就是其中的一個好東西。


本文首發微信公衆號:前端先鋒

歡迎掃描二維碼關注公衆號,每天都給你推送新鮮的前端技術文章

歡迎掃描二維碼關注公衆號,每天都給你推送新鮮的前端技術文章

歡迎繼續閱讀本專欄其它高贊文章:


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章