JS 語言的特點
在深入瞭解 JavaScript 的預編譯之前,不得不先來回憶一下 JS 語言的特點:
- 解釋型語言: 區別於編譯型語言,逐行編譯,就是編譯一行,執行一行,而且對速度要求並不是太高;
- JS 引擎是單線程的;
- JS 符合 ECMA 標準;
- JS 執行隊列類似於輪轉時間片。
JS 運行三部曲
在這裏,最重要的就是第一點:解釋型語言的運行過程。 JS 運行有三部曲:
- 語法分析:很簡單,就是通篇掃描一下有沒有低級語法(語義)錯誤;
- 預編譯: 簡單地說就是在內存中開闢了一些空間,存放一些變量與函數;
- 解釋執行:解釋一行,執行一行。
JS 預編譯實例
下面正式進入預編譯的介紹,看例子爲什麼控制檯可以打印出 ‘a’
<script type="text/javascript">
test();
function test() {
console.log('a');
}
</script>
如上邊示例,因爲有預編譯的存在,test() 的調用雖然在聲明之前,函數也可以執行。這是爲什麼呢?引出 預編譯前奏 這個概念:
預編譯前奏:
- imply global 暗示全局變量:即任何變量,如果變量未經聲明就賦值,此變量就爲全局對象所有;
eg: a = 123;
eg: var a = b = 123;
- 一切聲明的全局變量,全是 window 的屬性。
eg: var a = 123;
下圖爲一個實例,變量 a 雖然聲明瞭,但不是全局變量,所以不能在函數體外訪問到,但是變量 b 滿足預編譯前奏的第一點,未經聲明就被賦值,此變量爲全局對象所有,所以可以訪問到。
知道了預編譯前奏的概念,那麼我們的理解就已經深入了一半了,順着脈絡繼續。預編譯前奏生成 GO(Global Object)對象,之後如果有函數就進行預編譯,預編譯四部曲如下:
預編譯四部曲:
- 創建 AO 對象(Activation Object 俗稱執行期上下文);
- 找形參和變量聲明,將變量和形參名作爲 AO 屬性名,值爲 undefined;
- 將實參值和形參統一;
- 在函數體裏面找函數聲明,值賦予函數體。
通過簡單的實例來認識一下 GO 與 AO 的結合:預編譯前奏時,生成 window.b = 10;①預編譯時創建 AO 對象;②將變量聲明 a 放入 AO,值爲 undefined;③傳入參數,使得實參形參相統一,本例中沒有形參;④如果函數體裏還有函數聲明,值賦予函數體,但是本例中沒有。
最終,函數會打印出 undefined 。
再來一個例子,跟着我們前邊的節奏來分析,先寫下 GO 對象,再找 AO 對象。
<script type="text/javascript">
global = 100;
function fn() {
console.log(global);
global = 200;
console.log(global);
var global = 300;
}
fn();
var global;
</script>
大家先來分析一下分別會輸出什麼呢?
我們來分析一下,global 未經聲明就賦值,生成 GO 對象 global = 100;在執行到 fn() 的前一刻進行預編譯,生成 AO 對象,其中只有 global,值爲undefined,之後既沒有實參的傳入,也沒有函數聲明,那麼第15行就打印 undefined了,經過16行之後,undefined 被修改爲200 。所以兩個答案分別是 undefined 和 200,你答對了嗎?
百度2013面試題:
題目一:
因爲在函數 bar 最頂端返回 foo,直接看到 AO 的第四部:返回函數聲明,那麼程序會打印函數 bar()。
題目二:
這題和上一個類似,因爲函數的最後返回 foo,而 foo 在上邊被賦值過,那麼 foo 一定爲該值咯。
看完之後我們發現,其實預編譯也並不難,再梳理一下:
總結
預編譯前奏:生成 GO 對象,有兩個規則;
預編譯四部曲:
1.創建 AO 對象
2. 找形參和變量聲明,將變量和形參名作爲 AO 屬性名,值爲 undefined;
3. 將實參值和形參統一;
4. 在函數體裏面找函數聲明,值賦予函數體。
底層基礎決定上層建築,這是不變的真理,深究原理絕對是沒錯的!
說在最後的話:編寫實屬不易,若喜歡或者對你有幫助記得點贊+關注或者收藏哦~