JavaScript究竟是解釋型語言仍是編譯型語言?

幾天前一個剛接觸 JavaScript 的朋友問我 JavaScript 是編譯型語言仍是解釋型語言。從一個初學者那裏聽到這樣的問題讓我有些驚訝,由於全部初學者都知道 JS 是一個解釋型語言;特別是像她這樣以前使用過 Java 這類語言的初學者。node

當一些人深刻 JavaScript 而且開始研究 V8 引擎、SpiderMonkey、JIT 之類東西的時候,他們開始對於解釋型仍是編譯型有更多的疑問。很高興看到她已經在這個階段了。編程

使人困惑的是什麼?

最開始的時候,JavaScript 的聖經 —— MDN 明確地說 JavaScript 是一個解釋型語言(同時還說到了 JIT 及時編譯,後文會說起)。可是下面幾點仍然會讓 JavaScript 是否真的是一個解釋型語言產生疑問:瀏覽器

  • 若是 JS 是解釋型語言那爲何會有變量提高(hoisting)?
  • JIT(及時編譯)會作代碼優化(同時建立代碼的編譯版本);解釋型語言沒法作到這些。

有什麼快速的回答嗎?

因爲 JavaScript 規範沒有對這一點作明確說明,困惑和疑問是都是存在的,不能片面地回答。讓咱們基於理論定義和 JavaScript 工做流程來弄清楚 JavaScript 究竟是什麼語言。編程語言

編譯型語言 VS 解釋型語言

主要問題是沒有團體或者組織規定這些;例如:編譯型語言和解釋型語言的定義以及如何劃分。 而這兩個都是概念。ide

因此根據概念,編譯型語言是代碼在運行前編譯器將人類能夠理解的語言(編程語言)轉換成機器能夠理解的語言。函數

解釋型語言也是人類能夠理解的語言(編程語言),也須要轉換成機器能夠理解的語言才能執行,可是是在運行時轉換的。因此執行前須要環境中安裝瞭解釋器;可是編譯型語言編寫的應用在編譯後能直接運行。工具

許多人認爲解釋型語言意味着當遇到程序中行號爲xyz時直接將其傳給CPU就能運行;可是事實不是這樣。全部的編程語言都是爲人類建立的。他們是人類可以理解的。必須將編程語言轉換爲機器語言才能運行。編譯器獲取整個代碼,轉換它,作合適的優化而且建立一個能夠運行的輸出文件。編譯器根據上下文來轉換語句。性能

那麼變量提高呢?

我以爲你應該已經知道了 JavaScript 的變量提高。在函數做用域內的任何變量的聲明都會被提高到頂部而且值爲undeinfed優化

因此 JavaScript 引擎好像解釋了同一個腳本文件兩次?第一次完成全部的聲明提高而後第二次才執行代碼?仍是先編譯整個代碼而後運行它?這兩種都不對。網站

下面是 JavaScript 處理聲明語句的過程:

  • 一旦 V8 引擎進入一個執行具體代碼的執行上下文(函數),它就對代碼進行詞法分析或者分詞。這意味着代碼將被分割成像foo = 10這樣的原子符號(atomic token)。
  • 在對當前的整個做用域分析完成後,引擎將 token 解析翻譯成一個AST(抽象語法樹)。
  • 引擎每次遇到聲明語句,就會把聲明傳到做用域(scope)中建立一個綁定。每次聲明都會爲變量分配內存。只是分配內存,並不會修改源代碼將變量聲明語句提高。正如你所知道的,在JS中分配內存意味着將變量默認設爲undefined
  • 在這以後,引擎每一次遇到賦值或者取值,都會經過做用域(scope)查找綁定。若是在當前做用域中沒有查找到就接着向上級做用域查找直到找到爲止。
  • 接着引擎生成 CPU 能夠執行的機器碼。
  • 最後, 代碼執行完畢。

因此變量提高不過是執行上下文的小把戲,而不是許多網站描述的源代碼修改。在執行任何語句以前,解釋器就要從建立執行上下文後已經存在的做用域(scope)中找到變量的值。

解釋 JavaScript 中的即時編譯(JIT)

JIT 或 及時編譯 編譯器不是 JavaScript 所特有的。其餘語言好比 Java 也有一些在執行前編譯代碼的機制。

現代 JavaScript 引擎一樣有 JIT。是的,它們有編譯器。讓我來爲你解釋一下爲何它們須要 JIT 以及 JIT 在 JavaScript 的執行中是如何工做的。

編譯型和解釋型語言最重要的區別是編譯型語言須要很長的時間來準備執行。由於它須要對整個代碼進行詞法分析、作一些極致的優化等工做。另外一方面解釋型語言幾乎在執行後一瞬間就開始,可是沒有任何代碼優化。因此每一條語句都是分開轉換(編譯)的,考慮下面這一段代碼。

for(i=0; i < 1000; i++){
    sum += i;
}

在編譯型語言中sum += i部分在循環運行時已經編譯成了機器碼,機器碼將直接運行一千次。

可是在解釋型語言中,執行時會將sum += i轉換(編譯)一千次。對相同的代碼進行一千次轉換會形成很是大的性能損耗。

這就是 Google 和 Mozilla 的開發人員將 JIT 加入 JavaScript 的緣由。

編譯

在 JavaScript 中若是一段代碼運行超過一次,那麼就稱爲 warm。若是一個函數開始變得 warmer(譯者注:即運行更屢次),JIT 將把這段代碼送到編譯器中編譯而且保存一個編譯後的版本。下一次一樣代碼執行的時候,引擎會跳過翻譯過程直接使用編譯後的版本。

這將優化性能。在真正的編譯器中,由於編譯器能訪問整個代碼因此作了除此以外更多的事情。

優化

若是一段 warm 的代碼變得 hot 或者 hotter(譯者注:指運行更屢次以及比更多還要多的次數)JIT 會嘗試更多的優化而且保存優化後的版本。在編譯器進行優化的過程當中會作一些關於變量類型和運行環境中值的假設,若是假設不成立就將這個優化的版本回退,若是假設成立的話,這將讓代碼性能更高。

想要了解更多 JIT 的知識能夠閱讀 Lin Clarks 關於JIT的課程

總結

如今咱們瞭解了 JavaScript 執行時到底發生了什麼,因此應該能夠區分 JavaScript 究竟是編譯型仍是解釋型語言了。下面是這篇文章的要點。

  • JavaScript 代碼須要在機器(node 或者瀏覽器)上安裝一個工具(JS 引擎)才能執行。這是解釋型語言須要的。編譯型語言程序可以自由地直接運行。
  • 變量提高不是代碼修改。在這個過程當中沒有生成中間代碼。變量提高只是 JS 解釋器處理事情的方式。
  • JIT 是惟一一點咱們能夠對 JavaScript 是不是一個解釋型語言提出疑問的理由。可是 JIT 不是完整的編譯器,它在執行前進行編譯。並且 JIT 只是 Mozilla 和 Google 的開發人員爲了提高瀏覽器性能才引入的。JavaScript 或 TC39 歷來沒有強制要求使用 JIT。

所以,雖然 JavaScript 執行時像是在編譯或者像是一種編譯和解釋的混合,我仍然認爲 JavaScript 是一個解釋型語言或者是一個今天不少人說的混合型語言,而不是編譯型語言。

相關文章
相關標籤/搜索