概述 js是一種非常靈活的語言,理解js引擎的執行過程對我們學習javascript非常重要,但是網上講解js引擎的文章也大多是淺嘗輒止或者只局部分析,例如只分析事件迴圈(Event Loop)或者變數提升等等,並沒有全面深入的分析其中過程。所以我一直想把js執行的詳細過程整理成一個較為詳細的知識體 ...
概述
js是一種非常靈活的語言,理解js引擎的執行過程對我們學習javascript非常重要,但是網上講解js引擎的文章也大多是淺嘗輒止或者只局部分析,例如只分析事件迴圈(Event Loop)或者變數提升等等,並沒有全面深入的分析其中過程。所以我一直想把js執行的詳細過程整理成一個較為詳細的知識體系,幫助我們理解和整體認識js。
在分析之前我們先瞭解以下基礎概念:
-
javascript是單線程語言
在瀏覽器中一個頁面永遠只有一個線程在執行js腳本代碼(在不主動開啟新線程的情況下)。
-
javascript是單線程語言,但是代碼解析卻十分的快速,不會發生解析阻塞。
javascript是非同步執行的,通過事件迴圈(Event Loop)的方式實現。
下麵我們先通過一段較為簡單的代碼(暫不存在事件迴圈(Event Loop))來檢驗我們對js引擎執行過程的理解是否正確,如下:
<script>
|
我們可以先分析上面的代碼,按自己的理解分析輸出的順序是什麼,然後在瀏覽器執行一次,結果一樣的話,那麼代表你已經對js引擎執行過程有了正確的理解;如果不是,則代表還存在模糊或者概念不清晰等問題。結果我們不在這裡進行討論,我們利用上面簡單的例子全面分析js引擎執行過程,相信在理解該過程後我們就不難得出結果的,js引擎執行過程分為三個階段:
-
語法分析
-
預編譯階段
-
執行階段
註:瀏覽器首先按順序載入由<script>
標簽分割的js代碼塊,載入js代碼塊完畢後,立刻進入以上三個階段,然後再按順序查找下一個代碼塊,再繼續執行以上三個階段,無論是外部腳本文件(不非同步載入)還是內部腳本代碼塊,都是一樣的原理,並且都在同一個全局作用域中。
語法分析
js腳本代碼塊載入完畢後,會首先進入語法分析階段。該階段主要作用是:
分析該js腳本代碼塊的語法是否正確,如果出現不正確,則向外拋出一個語法錯誤(SyntaxError),停止該js代碼塊的執行,然後繼續查找並載入下一個代碼塊;如果語法正確,則進入預編譯階段
語法錯誤報錯如下圖:
預編譯階段
js代碼塊通過語法分析階段後,語法正確則進入預編譯階段。在分析預編譯階段之前,我們先瞭解一下js的運行環境,運行環境主要有三種:
-
全局環境(JS代碼載入完畢後,進入代碼預編譯即進入全局環境)
-
函數環境(函數調用執行時,進入該函數環境,不同的函數則函數環境不同)
-
eval(不建議使用,會有安全,性能等問題)
每進入一個不同的運行環境都會創建一個相應的執行上下文(Execution Context),那麼在一段JS程式中一般都會創建多個執行上下文,js引擎會以棧的方式對這些執行上下文進行處理,形成函數調用棧(call stack),棧底永遠是全局執行上下文(Global Execution Context),棧頂則永遠是當前執行上下文。
函數調用棧
函數調用棧就是使用棧存取的方式進行管理運行環境,特點是先進後出,後進先出。
我們分析下段簡單的JS腳本代碼來理解函數調用棧:
function bar() {
|
上面的代碼塊通過語法分析後,進入預編譯階段,如下圖:
-
首先進入全局環境,創建全局執行上下文(Global Execution Context),推入stack棧中
-
調用bar函數,進入bar函數運行環境,創建bar函數執行上下文(bar Execution Context),推入stack棧中
-
在bar函數內部調用foo函數,則再進入foo函數運行環境,創建foo函數執行上下文(foo Execution Context),推入stack棧中
-
此刻棧底是全局執行上下文(Global Execution Context),棧頂是foo函數執行上下文(foo Execution Context),如上圖,由於foo函數內部沒有再調用其他函數,那麼則開始出棧
-
foo函數執行完畢後,棧頂foo函數執行上下文(foo Execution Context)首先出棧
-
bar函數執行完畢,bar函數執行上下文(bar Execution Context)出棧
-
Global Execution Context則在瀏覽器或者該標簽頁關閉時出棧。
註:不同的運行環境執行都會進入代碼預編譯和執行兩個階段,語法分析則在代碼塊載入完畢時統一檢驗語法
創建執行上下文
執行上下文可理解為當前的執行環境,與該運行環境相對應。創建執行上下文的過程中,主要做了以下三件事件,如圖:
-
創建變數對象(Variable Object)
-
建立作用域鏈(Scope Chain)
-
確定this的指向
創建變數對象
創建變數對象主要經過以下幾個過程,如圖:
-
創建arguments對象,檢查當前上下文中的參數,建立該對象的屬性與屬性值,僅在函數環境(非箭頭函數)中進行,全局環境沒有此過程
-
檢查當前上下文的函數聲明,按代碼順序查找,將找到的函數提前聲明,如果當前上下文的變數對象沒有該函數名屬性,則在該變數對象以函數名建立一個屬性,屬性值則為指向該函數所在堆記憶體地址的引用,如果存在,則會被新的引用覆蓋。
-
檢查當前上下文的變數聲明,按代碼順序查找,將找到的變數提前聲明,如果當前上下文的變數對象沒有該變數名屬性,則在該變數對象以變數名建立一個屬性,屬性值為undefined;如果存在,則忽略該變數聲明
註:在全局環境中,window對象就是全局執行上下文的變數對象,所有的變數和函數都是window對象的屬性方法。
所以函數聲明提前和變數聲明提升是在創建變數對象中進行的,且函數聲明優先順序高於變數聲明。
我們分析一段簡單的代碼,幫助我們理解該過程,如下:
function fun(a, b) {
|
這裡我們在全局環境調用fun函數,創建fun執行上下文,這裡為了方便大家理解,暫時不講解作用域鏈以及this指向,如下:
funEC = {
|
-
funEC表示fun函數的執行上下文(fun Execution Context簡寫為funEC)
-
funE的變數對象中arguments屬性,上面的寫法僅為了方便大家理解,但是在瀏覽器中展示是以類數組的方式展示的
-
<test reference>
表示test函數在堆記憶體地址的引用
註:創建變數對象發生在預編譯階段,但尚未進入執行階段,該變數對象都是不能訪問的,因為此時的變數對象中的變數屬性尚未賦值,值仍為undefined,只有進入執行階段,變數對象中的變數屬性進行賦值後,變數對象(Variable Object)轉為活動對象(Active Object)後,才能進行訪問,這個過程就是VO –> AO過程。
建立作用域鏈
作用域鏈由當前執行環境的變數對象(未進入執行階段前)與上層環境的一系列活動對象組成,它保證了當前執行環境對符合訪問許可權的變數和函數的有序訪問。
理清作用域鏈可以幫助我們理解js很多問題包括閉包問題等,下麵我們結合一個簡單的例子來理解作用域鏈,如下:
var num = 30;
|
在上面的例子中,當執行到調用innerTest函數,進入innerTest函數環境。全局執行上下文和test函數執行上下文已進入執行階段,innerTest函數執行上下文在預編譯階段創建變數對象,所以他們的活動對象和變數對象分別是AO(global),AO(test)和VO(innerTest),而innerTest的作用域鏈由當前執行環境的變數對象(未進入執行階段前)與上層環境的一系列活動對象組成,如下:
innerTestEC = {
|
我們這裡直接使用數組表示作用域鏈,作用域鏈的活動對象或變數對象可以直接理解為作用域。
-
作用域鏈的第一項永遠是當前作用域(當前上下文的變數對象或活動對象);
-
最後一項永遠是全局作用域(全局執行上下文的活動對象);
-
作用域鏈保證了變數和函數的有序訪問,查找方式是沿著作用域鏈從左至右查找變數或函數,找到則會停止查找,找不到則一直查找到全局作用域,再找不到則會拋出引用錯誤。
在這裡我們順便思考一下,什麼是閉包?
我們先看下麵一個簡單例子,如下:
function foo() {
|
因為對於閉包有很多不同的理解,包括我看的一些書籍(例如js高級程式設計),我這裡直接以瀏覽器解析,以瀏覽器理解的閉包為準來分析閉包,如下圖:
如上圖所示,chrome瀏覽器理解閉包是foo,那麼按瀏覽器的標準是如何定義閉包的,我總結為三點:
-
在函數內部定義新函數
-
新函數訪問外層函數的局部變數,即訪問外層函數環境的活動對象屬性
-
新函數執行,創建新的函數執行上下文,外層函數即為閉包
確定this指向
在全局環境下,全局執行上下文中變數對象的this屬性指向為window;函數環境下的this指向卻較為靈活,需根據執行環境和執行方法確定,需要舉大量的典型例子概括,本文先不做分析。
總結
由於涉及的內容過多,這裡將第三個階段(執行階段)單獨分離出來。另開新文章進行詳細分析,下篇文章主要介紹js執行階段中的同步任務執行和非同步任務執行機制(事件迴圈(Event Loop))。本文如果錯誤,敬請指正。
[原址鏈接](https://heyingye.github.io/2018/03/19/js%E5%BC%95%E6%93%8E%E7%9A%84%E6%89%A7%E8%A1%8C%E8%BF%87%E7%A8%8B%EF%BC%88%E4%B8%80%EF%BC%89/)參考書籍
- 你不知道的javascript(上捲)