漫談JavaScript中的提升機制(Hoisting)

  • 2019 年 10 月 3 日
  • 筆記

前言

剛接觸到JavaScript的時候,便知道JavaScript是按順序執行的,是如瀏覽器的解析DOM樹一樣的流程,解析DOM結構的時候,如果遇到JS腳本或者外聯腳本便會停止解析,繼續下載腳本之後,執行腳本,然後再解析DOM。

然而,卻因此常常碰到問題。

看如下程式碼以及輸出:

var name;  console.log(name);   // undefined  name = 'tom';    age  = 10;  var age;  console.log(age);  // 10  

  

上面的程式碼讓我們產生了疑惑,我們僅僅聲明了name的時候,列印出來值是undefined,按理說,重新聲明age之後,age的值應該也是undefined才對,但是輸出來的卻是10。這究竟是怎麼回事兒呢?

我們的通用解釋是,遇到了變數提升。

而這樣的情況,我們在函數中也會看到,請看下面程式碼:

log();  console.log(name);  var name = 'tom';  function log() {      console.log('this is log');  }  

  

上面程式碼的輸出結果是什麼?

輸出結果:  this is log  undefined  

  

為什麼會產生這樣的情形呢?我們通用的解釋是,函數聲明提升了。

而針對這兩種情況,就是我們經常遇到的提升機制,也就是我們常說的Hoisting。

而僅僅只是一句提升機制來解釋這種現象,還是覺得雲里霧裡,要是我之前可能也就不明覺厲的哦了一聲,然後就不再理會這樣的東西了,那麼究竟為什麼會出現這樣的情況呢?

JavaScript是如何被編譯的呢

有時候我們會想,一段JS程式碼是如何執行的呢?其實,在JS程式碼被執行之前,通常都有一個編譯過程。

這個編譯過程其實很複雜,但總體來說,逃不過編譯過程的步驟,只不過JavaScript是在這個步驟之中對程式碼做了優化處理。

第一、詞法分析

詞法分析主要是將一段程式分解成有意義的程式碼塊,便於對分解的程式碼塊做解析。

比如,var age = 10;這一段程式碼將會被分解成 var、age、=、10、;。這是5個詞法單元。

這些單元分析完畢之後,便會給解析器調用,生成相應的AST(抽象語法樹)。

第二、解析詞法單元

解析詞法單元,是為了生成AST,那麼到底什麼是AST呢,我們來看一段程式碼以及解析生成的AST。

同樣是var age = 10;這段程式碼,被解析器解析成了一段樹形結構的結構,這個結構,就是抽象語法樹AST。你可以通過這個網站來查看生產的AST:AST解析器

而抽象語法樹,又是可以轉換成可執行程式碼。這就涉及到編譯的第三個階段。

第三、生成可執行程式碼

生成可執行程式碼的過程,相當於是再把AST轉換成瀏覽器可執行的程式碼,或者是各種語言引擎可執行的程式碼。

比如我們常見的babel,可以讓我們用ES6的語法去開發程式,其實就是依靠babel編譯器,將我們的ES6程式碼編譯成ES6的AST,然後將ES6的AST轉換成ES5的AST或者ES3的AST,最後將AST轉成ES5或ES3的程式碼來讓瀏覽器執行。

同理,TypeScript的TSC也是一個編譯器,做的事情和babel是一樣的,只不過兩者編譯出來的ES6的AST有略微的差別,這樣就造成了TypeScript用不了Babel社區的豐富多樣的插件,如eslint等。

因為eslint語法檢查,正是基於AST做的。

那麼上面這個編譯過程有什麼用呢?

JavaScript中的聲明和賦值

理解了語言的編譯過程,那麼JavaScript中的聲明和賦值又是如何的一個流程呢?

比如,var age = 10;這段程式碼,在JavaScript中的編譯方式是如何呢?

在JavaScript中,這段程式碼大概相當於是如下兩個過程:

var age = undefined;     // 隱式賦值,編譯階段  age = 10;    //變數賦值   執行階段  

  

函數聲明也是如此:

// 這一段程式碼就是一個完整的函數聲明,在編譯階段中,會先執行所有聲明,才會依次執行程式碼操作。  function log() {      console.log('this is log')  }  

  

這個時候,我們再回頭來,想一下提升機制是什麼?

再看提升

JavaScript的執行,被分為了兩個階段,分別是編譯階段,以及執行階段。依照這個來看,所謂的提升機制(有的叫做變數提升,考慮到函數的定義,並未用這個名詞),就是JavaScript引擎把變數聲明和函數聲明在編譯階段首先進行默認賦值,之後,在程式執行階段,才會被程式碼真正的執行。也就是說,針對聲明先提升,後執行。

注意:函數聲明和變數都有提升機制,兩者之間也有優先順序。這都遵循一個原則:函數優先原則。也就是說,函數聲明會提升到普通變數聲明之前。

總結

變數提升,是一個值得去探究的概念,只有理解了這個概念,我們理解JavaScript的執行機制將會變得清晰明了起來。

文中有什麼描述不當的,希望各位大佬能指出,大家共同學習,一起進步。

我的部落格:http://www.gaoyunjiao.fun/?p=143