JavaScript 是如何執行代碼的

準備知識

在講正文以前,咱們須要先了解幾個角色和概念:segmentfault

  • 引擎: 引擎爸爸的工做:負責整個 JavaScript 程序的編譯及執行過程
  • 編譯器: 引擎的好朋友,負責詞法、語法分析及代碼生成等髒活累活
  • 做用域: 引擎的另外一個好朋友,負責建立並維護全部的聲明(變量,函數),並實施一套嚴格的規則,規定了如何查找變量,也就是肯定當前執行代碼對變量的訪問權限。
若是想深刻學習做用域的相關知識請查看《JavaScript深刻之做用域》

咱們常常說 JavaScript 是一門解釋型語言,區別於編譯型語言,可是實際上 JavaScript 是一門編譯語言。但與傳統的編譯語言不一樣, 它不是提早編譯的, 編譯結果也不能在分佈式系統中進行移植。分佈式

事實上,任何 JavaScript 代碼片斷在執行前都要進行編譯,而後作好執行它的準備,一般編譯後就會立刻執行。函數

咱們如下面這段代碼爲例來講明 JavaScript 究竟是如何執行代碼的。學習

var a = 2;
function m() {
    console.log('m');
}
m();

JavaScript 是如何執行代碼的

上面咱們說了 JavaScript 在執行代碼前是先進行編譯的,上述代碼雖然只有三塊(變量聲明、函數聲明、函數調用),可是對於引擎來講卻至關於四個指令:spa

  • var afunction m() {}:編譯階段執行;(全部的變量和函數聲明都是在編譯階段執行的
  • a = 2m();:執行階段執行;

下面咱們將詳細介紹一下在編譯和執行階段具體是如何處理的。code

編譯階段

若是不瞭解編譯原理的相關知識(詞法分析、語法分析、AST、代碼生成),請查看 《編譯原理之基礎篇》
  1. 編譯器首先會將這段程序分解成詞法單元(詞法分析),而後將詞法單元解析成 AST(語法分析),而後開始根據 AST 生成機器指令(代碼生成)。
  2. 在代碼生成階段:
    當編譯器遇到 var a 時,編譯器會詢問做用域是否已經存在一個該名稱的標識符,若是存在,編譯器則忽略該指令,繼續編譯;不然它會要求做用域在當前做用域的集合中聲明一個命名爲 a 的標識符(變量)。
    同理,在編譯器遇到 function m() {} 時,也會去詢問做用域是否存在命名爲 m 的函數聲明,若是存在,編譯器則忽略該指令,繼續編譯;不然它會要求做用域在當前做用域的集合中聲明一個命名爲 m 的函數。
  3. 編譯器爲引擎生成運行時所需代碼以後,引擎開始執行代碼。

執行階段

引擎在執行代碼時:blog

遇到 a = 2 時,會詢問做用域,在當前做用域是否存在一個叫做 a 的標識符,若是是,做用域就會將其返還給引擎,引擎則會使用這個標識符;若是否,引擎會繼續查找該變量(詢問當前做用域的父級做用域,依次類推,直到頂層(全局做用域))。ip

若是引擎最終找到了 a ,就會將 2 賦值給它。作用域

不然在嚴格模式下,引擎會拋出一個 ReferenceError 異常(同做用域判別失敗相關,做用域中沒有找到想要的標識符);get

在非嚴格模式下,若是在頂層(全局做用域) 中也沒法找到目標標識符,全局做用域中就會隱式建立一個具備該名稱的標識符, 並將其返還給引擎。

總結:變量的賦值操做會執行兩個動做,首先編譯器會在當前做用域中聲明一個變量(若是以前沒有聲明過),而後再運行時引擎會在做用域中查找該變量,若是可以找到就會對它賦值。


當引擎遇到m();時,查找 m 標識符的過程同上。不一樣點是:在找不到 m 是,在非嚴格模式下,不會隱式建立一個具備該名稱的標識符,在下面【執行過程當中引擎是如何查找變量的】中會解釋緣由。

還有一個須要注意的點是,當找到 m 標識符後,引擎會開始執行該函數。此時若是該標識符表明的是一個函數,那麼函數能夠正常執行;可是若是標識符表明的是一個變量,那麼就會拋出TypeError 異常 (表明做用域判別成功了, 可是對結果的操做是非法或不合理的),以下圖所示:
image.png
此時 a 是一個變量,因此當嘗試執行它時,會報錯 a 不是一個函數。~~~~

執行過程當中引擎是如何查找變量的

上面咱們留了一個疑問,就是爲何在找不到函數標識符的時候,全局做用域不會像變量聲明同樣也隱式建立一個,下面咱們就來看看是爲何。

引擎在執行代碼時,會經過查找標識符 a 和 m 來判斷它是否已經聲明過。查找的過程由做用域進行協助,可是引擎是怎麼查找的呢?引擎查找變量有兩種方式,分別是:

  • LHS 查詢
    若是查找的目的是對變量進行賦值,則使用 LHS 查詢(告訴做用域我須要對 a 變量進行 LHS 引用,你見過它嘛?)
    不成功的 LHS 引用會致使自動隱式建立一個全局變量(非嚴格模式),嚴格模式下拋出ReferenceError 異常
  • RHS 查詢
    若是查找的目的是獲取變量的值,則使用 RHS 查詢(告訴做用域我須要對 a 變量進行 RHS 引用,你見過它嘛?)
    不成功的 RHS 查詢會拋出 ReferenceError 異常

思考題

請看下面這個例子,其中 RHS 共使用了三次,LHS 共使用了兩次,你能找到都是在哪裏使用了 RHS 和 LHS 嗎?

function add(a, b) {
    return a + b;
}
add(1, 2)
參考資料:《你不知道的JavaScript》 上篇
相關文章
相關標籤/搜索