JavaScript到底是解釋型語言還是編譯型語言?

原文鏈接:https://mp.weixin.qq.com/s/neqAVDUqHA4_qoswk-VWHw

正文從這開始~~

 

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

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

令人困惑的是什麼?

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

  • 如果 JS 是解釋型語言那爲什麼會有變量提升(hoisting)?

  • JIT(及時編譯)會做代碼優化(同時創建代碼的編譯版本);解釋型語言無法做到這些。

有什麼快速的回答嗎?

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

編譯型語言 VS 解釋型語言

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

所以根據概念,編譯型語言是代碼在運行前編譯器將人類可以理解的語言(編程語言)轉換成機器可以理解的語言。

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

許多人認爲解釋型語言意味着當遇到程序中行號爲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 是一個解釋型語言或者是一個今天很多人說的混合型語言,而不是編譯型語言。

關於本文
譯者:@Anx
譯文:https://segmentfault.com/a/1190000013126460
作者:@Paul Shan
原文:http://voidcanvas.com/is-javascript-really-interpreted-or-compiled-language/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章