[TOC]
# 運行環境
JavaScript 是伴隨著瀏覽器的誕生而誕生,所以 JavaScript 的執行最多還是在瀏覽器環境之內。但是 JavaScript 作為服務端腳本的概念在誕生之初就有,1995年網景公司就提出了服務端 JavaScript 的概念,并研發了 Netscape Enterprise Server;1996年微軟發布的 JScript 也可以運行在服務端。
隨著技術的發展各種JavaScript引擎出現,2009年5月Node.js的發布將 JavaScript 作為服務端腳本推向了一個高潮。關于JavaScript服務端的實現可以參看 [List_of_server-side_JavaScript_implementations](https://link.jianshu.com?t=https%3A%2F%2Fen.wikipedia.org%2Fwiki%2FList_of_server-side_JavaScript_implementations)。
JavaScript 的運行不像 C 語言等其他編譯型語言編譯后直接在操作系統上運行,因為它是腳本語言,運行時必須要借助引擎(解釋器)來運行,所以它可以在封裝了引擎的環境下運行。
封裝了JavaScript引擎的環境可以分為兩類,一類是瀏覽器環境;一類是非瀏覽器環境,比如 Node.js、MongoDB。我沒有采用 wikipedia 中 clent-side 和 server-side 的直接翻譯,因為 JavaScript 既可以編寫服務端腳本也可以編寫 shell 腳本,甚至圖形界面應用程序。
把運行環境分為瀏覽器環境和非瀏覽器環境是因為他們提供了截然不同的操作模塊。
1. 瀏覽器環境下 JavaScript 由三部分組成,分別是 ECMAScript、DOM 和 BOM,**BOM 和 DOM 是針對瀏覽器環境所擴展的操作方法**。
2. 非瀏覽器環境,比如 Node.js ,也是以 ECMAScript 為基礎,擴展出了 I/O 操作、文件操作、數據庫操作等等;在 MongoDB 中則是可以作為 shell 腳本操作數據庫;在 Eclipse e4 中可以編寫擴展。
# 運行機制
了解了JavaScript的運行環境,我們來看看運行機制。這里我們不再談微軟的JScript,一方面寫本文時我沒有找到詳盡的介紹JScript的資料,另一方面 JScript 的應用現在不常見。
JavaScript是個什么樣子,取決于它初始應用于哪里,它是作為瀏覽器的腳本出現,主要用途是解決網頁中的用戶交互。頁面中的用戶交互行為會讓頁面中的 DOM 元素產生變化,比如用戶輸入信息后的反饋提示等等。JavaScript在瀏覽器環境中操作DOM,為避免復雜的同步問題,決定了它采用單線程。如果同時有多個線程,有的在DOM節點上添加內容,有的修改了整個節點,甚至有的刪除了整個節點,這個時候很難判斷到底采用哪個線程的結果。
JavaScript最大的特點就是單線程,在瀏覽器環境中是,在非瀏覽器環境中同樣也是。單線程也就意味著JavaScript在同一時間只能進行一項任務,如果有多項任務的話,需要對任務進行排隊,完成一個才能繼續下一個。
不同的瀏覽器、不同的引擎、不同的執行環境,執行 JavaScript 的細節會有差異,但是不變的是單線程和隊列。
# 運行過程
在瀏覽器環境中,JavaScript引擎按標簽代碼塊從上到下的順序加載并立即解釋執行。
我們在這里不探究引擎的詳盡解釋執行細節,比如詞法分析、語法分析以及語法樹的構造等等,只說它解釋執行過程中非常重要的兩個時期預編譯期(預解析期)和執行期。理解這兩個階段十分有助于理解 JavaScript 中的一些“奇特”的現象。
在預編譯期JavaScript會對 var 和 function 的聲明在其所在作用域內進行提升,提升的位置相當于所在作用域開始位置。預編譯期需要注意下面幾個問題:
1.預編譯首先是全局預編譯,**函數體在未調用時不進行預編譯 **
2.只有 `var` 和 `function` 聲明會提升
3.注意是在所在作用域內提升,不會擴展到其他作用域
4.預編譯后順序執行
# 三個階段
**全面分析js引擎的執行過程,分為三個階段**
1、語法分析
2、預編譯階段
3、執行階段
說明:**瀏覽器先按照js的順序加載標簽分隔的代碼塊,js代碼塊加載完畢之后,立刻進入到上面的三個階段,然后再按照順序找下一個代碼塊,再繼續執行三個階段,無論是外部腳本文件(不異步加載)還是內部腳本代碼塊,都是一樣的,并且都在同一個全局作用域中。**
## 語法分析
js 的代碼塊加載完畢之后,會首先進入到語法分析階段,該階段的主要作用:
分析 js 腳本代碼塊的語法是否正確,如果出現不正確會向外拋出一個**語法錯誤(syntaxError)**,停止改 js 代碼的執行,然后繼續查找并加載下一個代碼塊;如果語法正確,則進入到預編譯階段。
類似的語法報錯的如下圖所示:

## 預編譯階段
js代碼塊通過語法分析階段之后,語法都正確的,會進入預編譯階段。
在分析預編譯階段之前,我們先來了解一下 js 的**運行環境**,運行環境主要由三種:
1、全局環境( js 代碼加載完畢后,進入到預編譯也就是進入到全局環境)
2、函數環境(函數調用的時候,進入到該函數環境,不同的函數,函數環境不同)
3、`eval` 環境(不建議使用,存在安全、性能問題)
每進入到一個不同的運行環境都會創建 一個相應的**執行上下文(execution context)**,那么在一段 `js` 程序中一般都會創建多個執行上下文,`js` 引擎會以棧的數據結構對這些執行進行處理,形成**函數調用棧(call stack),**棧底永遠是**全局執行上下文(global execution context)**,棧頂則永遠時當前的執行上下文。
# JavaScript到底是編譯型還是解釋型語言
* JavaScript 代碼需要在機器(node或者瀏覽器)上安裝一個工具(JS引擎)才能執行。這是解釋型語言需要的。編譯型語言產品能夠自由地直接運行。
* 聲明提升等不是代碼修改。在這個過程中沒有生成中間代碼。這只是JS解釋器處理事情的方式。
* JIT 是唯一一點我們可以對JavaScript是否是一個解釋型語言提出疑問的理由。但是JIT不是純粹的編譯器,它在執行前進行編譯。而且 JIT 只是 Mozilla 和 Google 的開發人員為了在他們的瀏覽器產品中提升性能才引入的。JavaScript 或 TC39 從來沒有要求這樣做。
更多關于JIT的事情你可以閱讀 Lin Clarks 的[關于JIT的課程](https://hacks.mozilla.org/2017/02/a-crash-course-in-just-in-time-jit-compilers/)。
因此,雖然 JavaScript 執行時像是編譯過的或者是一種混合,但是我仍然認為說 JavaScript 是一個解釋型語言要好過說它是一個編譯型語言或者很多人說的一個混合型的語言。
## 參考
[瀏覽器環境概述](http://javascript.ruanyifeng.com/bom/engine.html)
[javascript引擎執行的過程的理解--語法分析和預編譯階段](https://www.cnblogs.com/chengxs/p/10240163.html)
[JavaScript 編譯原理、編譯器、引擎及作用域](https://www.jianshu.com/p/5ebf2ad6def2)
[JavaScript運行環境、運行機制與運行過程](https://www.jianshu.com/p/47ce0a9def7b)
- 步入JavaScript的世界
- 二進制運算
- JavaScript 的版本是怎么回事?
- JavaScript和DOM的產生與發展
- DOM事件處理
- js的并行加載與順序執行
- 正則表達式
- 當遇上this時
- Javascript中apply、call、bind
- JavaScript的編譯過程與運行機制
- 執行上下文(Execution Context)
- javascript 作用域
- 分組中的函數表達式
- JS之constructor屬性
- Javascript 按位取反運算符 (~)
- EvenLoop 事件循環
- 異步編程
- JavaScript的九個思維導圖
- JavaScript奇淫技巧
- JavaScript:shim和polyfill
- ===值得關注的庫===
- ==文章==
- JavaScript框架
- Angular 1.x
- 啟動引導過程
- $scope作用域
- $q與promise
- ngRoute 和 ui-router
- 雙向數據綁定
- 規范和性能優化
- 自定義指令
- Angular 事件
- lodash
- Test