Javascript 變數生命週期

前端小智發表於2019-07-18

譯者:前端小智

原文:dmitripavlutin.com/variables-l…

你知道的越多,你不知道的越多

點贊再看,養成習慣


本文 GitHub:github.com/qq449245884… 上已經收錄,更多往期高贊文章的分類,也整理了很多我的文件,和教程資料。歡迎Star和完善,大家面試可以參照考點複習,希望我們一起有點東西。

為了保證的可讀性,本文采用意譯而非直譯。

提升是將變數或函式定義移動到作用域頭部的過程,通常是 var 宣告的變數和函式宣告function fun() {...}

當 ES6 引入let(以及與let類似宣告的constclass)宣告時,許多開發人員都使用提升定義來描述如何訪問變數。但是在對這個問題進行了更多的探討之後,令我驚訝的是提升並不是描述let變數的初始化和可用性的正確術語。

ES6 為let提供了一個不同的和改進的機制。它要求更嚴格的變數宣告,在定義之前不能使用,從而提高程式碼質量。

想閱讀更多優質文章請猛戳GitHub部落格,一年百來篇優質文章等著你!

為了回饋讀者,《大遷世界》不定期舉行(每個月一到三次),現金抽獎活動,保底200,外加使用者讚賞,希望你能成為大遷世界的小錦鯉,快來試試吧

1. 容易出錯的 var 提升

有時候我們會在zuo內作用域內看到一個奇怪的變數var varname和函式函式function funName() {...} 宣告:

// var hoisting
num;     // => undefined
var num;
num = 10;
num;     // => 10
// function hoisting
getPi;   // => function getPi() {...}
getPi(); // => 3.14
function getPi() {
  return 3.14;
}
複製程式碼

變數num在宣告var num之前被訪問,因此它被賦值為undefinedfucntion getPi(){…}在檔案末尾定義。但是,可以在宣告getPi()之前呼叫該函式,因為它被提升到作用域的頂部。

事實證明,先使用然後宣告變數或函式的可能性會造成混淆。假設您滾動一個大檔案,突然看到一個未宣告的變數,它到底是如何出現在這裡的,以及它在哪裡定義的?

當然,一個熟練的JavaScript開發人員不會這樣編寫程式碼。但是在成千上萬的JavaScript中,GitHub repos是很有可能處理這樣的程式碼的。

即使檢視上面給出的程式碼示例,也很難理解程式碼中的宣告流。

當然,首先要宣告再使用。let 鼓勵我們們使用這種方法處理變數。

2. 理解背後原理:變數生命週期

當引擎處理變數時,它們的生命週期由以下階段組成:

  1. **宣告階段(Declaration phase)**是在作用域中註冊一個變數。

  2. **初始化階段(Initialization phase)**是分配記憶體併為作用域中的變數建立繫結。 在此步驟中,變數將使用undefined自動初始化。

  3. **賦值階段(Assignment phase)**是為初始化的變數賦值。

變數在通過宣告階段時尚未初始化狀態,但未達到初始化狀態。

Javascript 變數生命週期

請注意,就變數生命週期而言,宣告階段與變數宣告是不同的概念。 簡而言之,JS引擎在3個階段處理變數宣告:宣告階段,初始化階段和賦值階段。

3.var 變數的生命週期

熟悉生命週期階段之後,讓我們使用它們來描述JS引擎如何處理var變數。

Javascript 變數生命週期

假設JS遇到一個函式作用域,其中包含var變數語句。變數在執行任何語句之前通過宣告階段,並立即通過作用域開始處的初始化階段(步驟1)。函式作用域中var變數語句的位置不影響宣告和初始化階段。

在宣告和初始化之後,但在賦值階段之前,變數具有undefined 的值,並且已經可以使用。

在賦值階段variable = 'value' 時,變數接收它的初值(步驟2)。

嚴格意義的提升是指在函式作用域的開始處宣告並初始化一個變數。宣告階段和初始化階段之間沒有差別。

讓我們來研究一個例子。下面的程式碼建立了一個包含var語句的函式作用域

function multiplyByTen(number) {
  console.log(ten); // => undefined
  var ten;
  ten = 10;
  console.log(ten); // => 10
  return number * ten;
}
multiplyByTen(4); // => 40
複製程式碼

開始執行multipleByTen(4)並進入函式作用域時,變數ten在第一個語句之前通過宣告和初始化步驟。因此,當呼叫console.log(ten)時,列印undefined。語句ten = 10指定一個初值。賦值之後,console.log(ten) 將正確地列印10

4. 函式宣告生命週期

在函式宣告語句function funName() {...}的情況下,它比變數宣告生命週期更簡單。

Javascript 變數生命週期

宣告、初始化和賦值階段同時發生在封閉函式作用域的開頭(只有一步)。可以在作用域的任何位置呼叫funName(),而不依賴於宣告語句的位置(甚至可以在末尾呼叫)。

下面的程式碼示例演示了函式提升:

function sumArray(array) {
  return array.reduce(sum);
  function sum(a, b) {
    return a + b;
  }
}
sumArray([5, 10, 8]); // => 23
複製程式碼

當執行sumArray([5,10,8])時,它進入sumArray函式作用域。在這個作用域內,在任何語句執行之前,sum都會通過所有三個階段:宣告、初始化和賦值。這樣,array.reduce(sum)甚至可以在它的宣告語句sum(a, b){…}之前使用sum

5. let 變數的生命週期

let 變數的處理方式與var不同,主要區別在於宣告和初始化階段是分開的

Javascript 變數生命週期

現在來看看一個場景,當直譯器進入一個包含let變數語句的塊作用域時。變數立即通過宣告階段,在作用域中註冊其名稱(步驟1)。

然後直譯器繼續逐行解析塊語句。

如果在此階段嘗試訪問變數,JS 將丟擲 ReferenceError: variable is not defined。這是因為變數狀態未初始化,變數位於暫時死區 temporal dead zone

當直譯器執行到語句let variable時,傳遞初始化階段(步驟2)。變數退出暫時死區。

接著,當賦值語句variable = 'value'出現時,將傳遞賦值階段(步驟3)。

如果JS 遇到let variable = 'value',那麼初始化和賦值將在一條語句中發生。

讓我們看一個例子,在塊作用域中用 let 宣告變數 number

let condition = true;
if (condition) {
  // console.log(number); // => Throws ReferenceError
  let number;
  console.log(number); // => undefined
  number = 5;
  console.log(number); // => 5
}
複製程式碼

當 JS 進入if (condition) {...} 塊作用域,number立即通過宣告階段。

由於number已經處於單一化狀態,並且處於的暫時死區,因此訪問該變數將引發ReferenceError: number is not defined。接著,語句let number進行初始化。現在可以訪問變數,但是它的值是undefined

constclass 型別與let具有相同的生命週期,只是分配只能發生一次。

5.1 提升在let生命週期中無效的原因

如上所述,提升是變數在作用域頂部的耦合宣告和初始化階段。然而,let生命週期分離宣告和初始化階段。解耦消除了let的提升期限。

這兩個階段之間的間隙產生了暫時死區,在這裡變數不能被訪問。

總結

使用var宣告變數很容易出錯。在此基礎上,ES6 引入了let。它使用一種改進的演算法來宣告變數,並附加了塊作用域。

由於宣告和初始化階段是解耦的,提升對於let變數(包括constclass)無效。在初始化之前,變數處於暫時死區,不能訪問。

為了保持變數宣告的流暢性,建議使用以下技巧

  • 宣告、初始化然後使用變數,這個流程是正確的,易於遵循。

  • 儘量隱藏變數。公開的變數越少,程式碼就越模組化。

番外

如何理解 let x = x 報錯之後,再次 let x 依然會報錯?

Javascript 變數生命週期

這個問題說明:如果 let x 的初始化過程失敗了,那麼

  1. x 變數就將永遠處於 created 狀態。

  2. 你無法再次對 x 進行初始化(初始化只有一次機會,而那次機會你失敗了)。

  3. 由於 x 無法被初始化,所以 x 永遠處在暫時死區

  4. 有人會覺得 JS 坑,怎麼能出現這種情況;其實問題不大,因為此時程式碼已經報錯了,後面的程式碼想執行也沒機會。

參考:

我用了兩個月的時間才理解 let

交流(歡迎加入群,群工作日都會發紅包,互動討論技術)

乾貨系列文章彙總如下,覺得不錯點個Star,歡迎 加群 互相學習。

github.com/qq449245884…

我是小智,公眾號「大遷世界」作者,對前端技術保持學習愛好者。我會經常分享自己所學所看的乾貨,在進階的路上,共勉!

關注公眾號,後臺回覆福利,即可看到福利,你懂的。

Javascript 變數生命週期

每次整理文章,一般都到2點才睡覺,一週4次左右,挺苦的,還望支援,給點鼓勵

Javascript 變數生命週期

相關文章