JS 變量生命週期:爲何 let 沒有被提高

譯者:前端小智html

原文:dmitripavlutin.com/variables-l…前端


阿里雲最近在作活動,低至2折,有興趣能夠看看promotion.aliyun.com/ntms/yunpar…git


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

提高是將變量或函數定義移動到做用域頭部的過程,一般是 var 聲明的變量和函數聲明function fun() {...}算法

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

ES6 爲let提供了一個不一樣的和改進的機制。它要求更嚴格的變量聲明,在定義以前不能使用,從而提升代碼質量。函數

想閱讀更多優質文章請猛戳GitHub博客,一年百來篇優質文章等着你!學習

爲了回饋讀者,《大遷世界》不按期舉行(每月一到三次),現金抽獎活動,保底200,外加用戶讚揚,但願你能成爲大遷世界的小錦鯉,快來試試吧阿里雲

1. 容易出錯的 var 提高

有時候咱們會在zuo內做用域內看到一個奇怪的變量var varname和函數函數function funName() {...} 聲明:spa

// 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)**是爲初始化的變量賦值。

變量在經過聲明階段時還沒有初始化狀態,但未達到初始化狀態。

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

3.var 變量的生命週期

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

假設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() {...}的狀況下,它比變量聲明生命週期更簡單。

聲明、初始化和賦值階段同時發生在封閉函數做用域的開頭(只有一步)。能夠在做用域的任何位置調用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不一樣,主要區別在於聲明和初始化階段是分開的

如今來看看一個場景,當解釋器進入一個包含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 依然會報錯?

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

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

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

  3. 因爲 x 沒法被初始化,因此 x 永遠處在暫時死區

  4. 有人會以爲 JS 坑,怎麼能出現這種狀況;其實問題不大,由於此時代碼已經報錯了,後面的代碼想執行也沒機會。

參考:

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

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

阿里雲最近在作活動,低至2折,有興趣能夠看看:promotion.aliyun.com/ntms/yunpar…

乾貨系列文章彙總以下,以爲不錯點個Star,歡迎 加羣 互相學習。

github.com/qq449245884…

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

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

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

相關文章
相關標籤/搜索