本篇部落格我們來說一下JavaScript的解析機制。
JavaScript的解析過程分為編譯和執行兩個階段,編譯在此處指的是JavaScript的預先處理(先行編譯)。在先行編譯期,JavaScript解譯器完成對JavaScript代碼的預先處理,轉換為位元組碼。執行期間,JavaScript解譯器把位元組碼轉換成二進位碼,按照順序執行
先行編譯編譯器
JavaScript是一種解釋型語言,也就是邊編譯邊執行,一般的編譯器和工作流程如:
這屬於編譯原理,可以參見我的部落格《編譯原理之概述》,但是對於JavaScript而言,它只需要詞法分析和文法分析階段,建立文法樹後,即開始解釋執行。
詞法分析
在詞法分析階段,JavaScript解譯器先把代碼的字元流轉換為記號流,如:
a=(b-c)
轉換為記號流:
NAME "a"EQUALSOPEN_PARENTHESISNAME "b"MINUSNAME "c"CLOSE_PARENTHESISSEMICOLON
詞法分析階段可以實現的是:
文法分析
文法分析階段就是把詞法分析階段產生的記號,產生文法樹,即把從程式中收集的資訊儲存到資料結構中,資料結構在此處為兩種:
- 符號表:記錄變數、函數、類
- 文法樹:程式結構的樹形表示,將此樹形結構產生中間代碼。
例如把下面的語句轉換為文法樹:
if(typeof a=="undefined"){a=0;}
文法樹:
當構建文法樹的過程中,無法構造,則報出語法錯誤,並結束整個代碼塊的解析。
詞法分析和文法分析階段是交錯進行的,每取一個詞法記號,就送入文法分析器進行分析。
執行期
經過編譯階段的準備,代碼在記憶體中已經構建成文法樹,JavaScript引擎會根據這個此法術結構邊解釋邊執行。解釋過程中,引擎嚴格按照範圍機制執行。JavaScript採用的詞法範圍,簡單說就是變數和函數的範圍在定義時決定,取決於原始碼結構。
函數
引擎解釋執行每個函數時,先建立一個執行環境,在這個環境中建立一個調用對象,這個對象記憶體儲著當前域中所有局部變數、參數、嵌套函數、引用函數和父級列表。調用對象聲明周期與函數一致,當函數調用完畢且沒有外部參考的情況下,被記憶體回收機制回收。
同時解譯器通過範圍鏈把多個嵌套的範圍串在一起,並藉助這個鏈,由內而外尋找變數值,直到全域對象,如果沒有找到,返回"undefined"。
這個會在後續部落格中繼續討論。
閉包
如果函數引用外部變數的值,解譯器會為該函數建立一個閉包,閉包是一個完全封閉和獨立的範圍,不會在函數調用完畢後被記憶體回收,可以長期存在,只有閉包的外部參考被全部設定為null時,才會被回收;缺點是容易引發垃圾泛濫
這個會在後續的部落格中繼續討論。