在js的學習中,我們已經不滿足於僅了解js的基礎知識,而是開始追求更深層次的學習。因為你會發現,僅了解js的基礎知識,在開發項目的過程中是遠遠不夠的。今天就來介紹一下在js執行過程中的一些相關內容了。
JavaScript運行環境
JavaScript的運行不是像C++,Java等編譯語言編譯后直接在操作系統上運行,因為它是腳本語言,運行時必須要借助引擎來運行,所以它可以在封裝了引擎的環境下運行。而當js運行時,它會有不同的運行環境。
- Global Code -- JavaScript代碼開始的默認運行環境
- Function Code -- 代碼執行在JavaScript函數中
- Eval Code -- 使用eval()執行代碼
JavaScript運行過程
Js的執行過程可分為兩個重要的時期預編譯期(預解析期)和執行期。
- 預編譯期
- 瀏覽器的JavaScript引擎“解析”js代碼
- 建立arguments對象,函數,參數,變量
- 建立作用域鏈
- 確定this的指向
- 執行期
按照從上到下的順序執行代碼。
執行上下文
概念
如何區分不同的運行環境,需要引出的一個概念就是執行上下文(Execution Context)。它是一個對象,由js的執行引擎創建,具有三個屬性:變量對象(variable object),作用域鏈(scope chain),this指針。
上下文棧
js在執行過程中會有一個上下文棧,上下文棧中存放的就是不同的上下文對象(你可以理解為不同的js運行環境)。比如當js開始執行一個函數,那此時它的運行環境從原來的Global Code變為Function Code,js引擎會創建一個context對象,並將其壓如棧中。當這個函數執行完后,這個對象將會彈出。故而,當前執行代碼的context對象總是在棧頂。
var a = 1; function plus(a, b) { var c; c = a + b; return c; }; plus(1, 2); function minus() { var d = 3; function get() { var e = 4; return e; } return d - get(); }; minus();
變量對象
變量對象是context對象中的一個重要屬性,其創建過程如下:
- 創建arguments對象,其中保存有多個屬性,屬性的key值是'0','1','2'......,value值就是傳入的參數的實際值。
- 找到這個作用域內的所有var和function的聲明,作為屬性存儲在變量對象中,如果是function,那屬性名就是函數名,屬性值是函數的引用。如果是var,那屬性名就是變量名,屬性值是undefined.
理解了變量對象創建的過程,你就可以理解為什么會有變量提升這個特性了。
console.log(a); // undefined var a = 1;
以上代碼預解析后的實際過程可理解為:
var a; console.log(a); a =1
function聲明的函數也是一樣的原理:
f(); // 1 function f() { var a = 1; console.log(a); }
以上代碼預解析后的實際過程可理解為:
function f() { var a = 1; console.log(a); } f(); // 1
既然var聲明和function聲明都具有變量提升的特性,那var和function哪一個的聲明在前呢?其實從上面的變量對象的創建過程中我們就已經知道了,為了看的清楚,我們用函數表達式的方式來聲明一個函數檢測一下。
f(); var f = function (){ var a = 1; console.log(a); }; function f(){ var b = 2; console.log(b); }; f();
控制台打印效果
顯然是function的聲明在前。
看了變量對象的創建過程,是不是覺得它和js執行過程中預編譯期的第2步非常相似。沒錯,其實在js的預編譯時期所做的工作實際上就是創建Global Execution Context的過程。它的第2步,就是context對象中創建變量對象的過程。
看到這里,是不是又有一個新的疑惑,為什么在最初的時候我們是在代碼還沒有開始執行的時候就已經創建了Global Execution Context對象,而之后是在要執行函數之前,才創建context對象呢?需要理解的一點就是函數體的預解析發生在函數被調用之時,被調用時先進行函數體的預編譯,然后按順序進行執行。
如果這段js代碼是運行在瀏覽器端的,那么你猜到此時的Gobal Execution Context中的變量對象是什么了嗎?沒錯,它就是window對象。但是當它是運行在服務器端的時候,全局上下問的變量對象卻不是它的全局對象global。為什么呢?各位可愛的讀者可以自己來探索一下哦。