咱們都知道javascript是解釋型語言,執行的特色呢是編譯一行,執行一行。按照這個思路有時候咱們在運行代碼時會有一些使人費解的現象出現。下面咱們一塊兒來執行下面三段代碼。javascript
<script> var a = 123; console.log(a); </script>
<script> console.log(a); </script>
<script> console.log(a); var a = 123; </script>
運行上面三段代碼能夠得出結果分別爲: 123. 和 a is not defined. 和 undefined.html
按理說第三個代碼應該也輸出:a is not defined.可是並無,這正是因爲javascript在執行代碼前的預編譯產生的。java
前面說的是變量的例子,下面咱們看看函數執行時的效果。請看下面兩段代碼。函數
<script> function test() { document.write("a"); } test(); </script>
<script> test(); function test() { document.write("a"); } </script>
運行上面兩段代碼執行結果都爲:a。 這也是預編譯所產生的結果。spa
這又是爲何呢。下面咱們帶着疑問來了解一下預編譯究竟是什麼。code
全局變量、暗示全局變量和局部變量的概念
在這以前咱們先來了解一下什麼是:暗示全局變量。咱們都知道變量有兩種類型:全局變量和局部變量。在函數外部定義的就叫全局變量,在函數內部定義的就叫局部變量。htm
先看一段代碼對象
<script> var a = 1; b = 2; function test() { var c = d = 3; //變量的賦值是從右到左的,至關於:d = 3; var c = d; console.log(c); console.log(d); } test(); console.log(b); console.log(d); </script>
執行結果爲:3 3 2 3. blog
奇怪,d 不是局部變量嗎,爲何能夠在全局進行訪問? 爲何 b = 2 這樣也能夠聲明變量 ?ip
那麼就只有一個可能,d 不是局部變量,而是暗示全局變量。
接下來咱們熟悉一下兩個重要的概念。
- imply global暗示全局變量:即任何變量,若是變量未聲明就賦值,此變量爲全局對象(window)全部。window就是全局的域。
- 任何聲明的全局變量,皆爲window的屬性。因此訪問一個全局變量能夠console.log(a),也能夠console.log(window.a).
因此出現上面的結果也就不難解釋了,a 是全局變量。b d兩者是暗示全局變量(即未聲明就賦值的變量)。c 是局部變量。因此變量d能夠在外部進行訪問。之後咱們只要記得暗示全局變量也是全局變量便可。
JS運行三部曲
下面,咱們正式進入JS運行三部曲部分。
一、語法分析
二、預編譯
三、解釋執行
在執行代碼前還有兩個步驟,即語法分析和預編譯。
在預編譯以前,系統會對整個代碼所有掃描一遍,看看有沒有低級的語法錯誤,如少了括號或引號等等。解釋執行就是從上到下依次執行函數代碼。
下面咱們重點來說一下預編譯。講完預編譯後相信你們就會知道以前的代碼執行結果爲何是那樣的啦。
咱們分函數體內的預編譯和全局預編譯兩個部分來說解預編譯。
函數體內的預編譯
函數體內預編譯四部曲 //發生在函數執行的前一刻。
- 建立AO對象。Activation object。即做用域,或叫執行期上下文。
- 找形參和變量聲明,將變量和形參名做爲AO的屬性名,初始值爲undefined.
- 將形參和實參相統一。
- 在函數體裏找函數聲明,值(函數聲明)賦予AO對象。
注意:預編譯過程只涉及變量或函數的聲明,賦值語句在解釋執行的階段才進行。
咱們來看一下這段代碼具體解釋一下函數體內預編譯的詳細過程,也能夠本身先執行一遍,看一下結果與本身預期的是否一致。
<script> function fn(a){ //定義一個函數fn() console.log(a); var a = 123; console.log(a); function a() {} //函數的聲明在預編譯時已經被識別,因此在調用fn(1)時,忽略這條語句 console.log(a); var b =function () {} console.log(b); function d() {} } fn(1); </script>
一、建立一個AO對象。 AO { }。
二、找形參和變量聲明,將變量和形參名做爲AO的屬性名,初始值爲undefined。變量名和形參名一致的話只寫一個就行。
AO { a : undefined
b : undefined
}
三、將實參和形參統一。
AO {a : 1
b : undefined
}
四、在函數體裏找函數聲明,值(函數聲明)賦予AO對象。
AO {a : function a() {}
b : undefined
d : function d() {}
}
預編譯結束後,才執行fn(1)函數。(執行過程爲從上到下依次執行)
執行結果爲:function a() {} 、12三、12三、function () {}.
全局預編譯
全局預編譯(整個script代碼塊執行前)
一、建立一個GO對象。GO對象即window對象。
二、找變量聲明。初始值爲undefined。注:全局預編譯沒有形參和實參
三、找‘全局裏的函數聲明,函數聲明賦值到全局裏的GO。
咱們來看一下下面這段代碼來了解一下全局預編譯。
<script> function b(a) { console.log(a); } b(1); console.log(a); var a = 2; console.log(a); </script>
一、建立一個GO對象。 GO { }
二、找變量聲明 GO {a : undefined}
三、找函數聲明 GO {a : undefined
b : function b(a){...} }
從上到下執行整個代碼。
執行到 b (1)時,先進行函數體的預編譯,而後再執行 b (1)函數。
一、建立一個AO對象 AO { }
二、找形參和變量聲明 AO {a : undefined}
三、將實參和形參統一 AO {a : 1}
四、在函數體裏找函數聲明,值(函數聲明)賦予AO對象。沒有函數體,則這一步忽略。
預編譯完成後執行 b(1)函數。
最後顯示的結果爲:1 undefined 2.
總結:JS運行過程
全局預編譯(腳本代碼塊script執行前)
- 查找全局變量聲明(包括隱式全局變量聲明,省略var聲明),變量名做全局對象的屬性,值爲undefined 查找函數聲明,函數名做爲全局對象的屬性,值爲函數引用
- 查找函數聲明,函數名做爲全局對象的屬性,值爲函數引用。
從上往下依次執行代碼,遇到函數體時,進行函數體預編譯。
函數體預編譯(函數執行前)
- 建立AO對象(Active Object)
- 查找函數形參及函數內變量聲明,形參名及變量名做爲AO對象的屬性,值爲undefined
- 實參形參相統一,實參值賦給形參
- 查找函數聲明,函數名做爲AO對象的屬性,值爲函數引用
函數體預編譯後,繼續往下執行代碼。