• 咱們在書寫js代碼的時候,會發現兩點和c/c++語言不一樣的地方。第一個是當咱們在後面定義了一個函數以後,咱們在定義函數以前使用這個函數也是能夠的。第二個是咱們在後面聲明的一個變量,可是在前面調用這個變量的時候並不會報錯而是undefiend。c++
這兩點不一樣在js中被稱爲函數聲明提高和變量聲明提高,函數聲明提高是一種總體提高,它會把函數聲明和函數體一塊兒提高到前面。變量聲明提高則是一種局部提高,它僅僅將變量的聲明提早了,可是並無將賦值也一塊兒提早。函數
那麼爲何會出現這種提高的現象呢?this
這是由於js運行的時候有一個階段叫作預編譯階段,而咱們的聲明提高現象都是發生在預編譯的時候喲~es5
預編譯
• js運行三部曲spa
1.語法分析3d
2.預編譯對象
3.解釋執行blog
語法分析:js引擎在解析js代碼以前,會先通篇掃描一下,找出低級的語法錯誤,好比寫錯大括號之類的。io
編譯執行:咱們前面提到js是一種解釋型語言,編譯一行執行一行,當語法分析沒有問題,而且已經完成預編譯階段以後,就開始解釋執行代碼。console
這裏咱們着重介紹預編譯。
預編譯前奏
在介紹預編譯以前,咱們有兩個重要概念須要掌握。
1.imply global 暗示全局變量。
若是任何變量未經聲明就賦值使用,此變量就會爲全局對象window全部,而且成爲window對象的一個屬性。
或者
2.一切聲明的全局變量,都是window的屬性。
• 這樣看不論全局變量有沒有聲明,彷佛都會成爲全局對象上的屬性,那麼二者之間有什麼區別呢?
區別在於:通過聲明的全局變量不能經過delete操做來刪除,可是未經聲明的全局變量能夠被刪除。
正是這一種特性,致使es5有一種弊端,咱們總會在無形中聲明一些全局變量。
這段代碼的原意是:在函數體中聲明兩個變量a、b,而後初始化a、b都是0。可是咱們這麼寫以後,a通過了聲明,可是b卻沒有聲明,這時候b就會成爲一個全局變量。
瞭解這兩點以後,咱們正式介紹一下預編譯的過程。
預編譯的過程我總結爲如下四步:
1.建立AO對象。
2.尋找形參和變量聲明,將變量和形參做爲AO對象的屬性名添加到對象中,值爲undefined。值得注意的是,函數聲明不叫變量。
3.將實參值和形參值相統一。
4.在函數體裏面尋找函數聲明,將函數名做爲屬性名,值爲這個函數的函數體。
函數在執行的前一刻會產生一個上下文,這個上下文就是Activeaction Object對象,簡稱AO對象。
AO = {}
這個對象是空的,可是裏面有一些咱們看不到的卻存在的隱式屬性,好比this: window屬性和arguments: [];屬性
這個對象用來存放一些屬性和方法,這些屬性和方法就按照前面的四步來產生。
這裏咱們用這一個樣例代碼來簡單介紹一下預編譯的過程。
首先第一步,建立一個AO對象。
var AO = {};
第二步,尋找形參值和變量聲明,而且將值賦爲undefined。
3.將實參值和形參值相統一。這裏由於屬性名都已經存在了,因此直接賦值就能夠了。
4.尋找函數聲明,將函數體賦值給屬性。
這樣在編譯執行以前,咱們預編譯階段建立的AO對象就是這個樣子了,這個時候咱們再看看分別打印的值是什麼。
第一個console.log a –> function () {}
第二個console.log a –> 222 由於執行了a = 222這一行代碼,因此從新賦值了。
第三個console.log b –> function () {}
• var b = function () {}這種不叫作函數聲明,這個函數是賦值給b變量的,b變量是聲明。
這裏的var b = function () {}只是聲明瞭b變量,在第四步尋找函數聲明裏面並不會把b賦值成function () {},由於後面的函數並非聲明,當代碼開始解釋執行以後,執行到這一行以後才把b賦值成這個函數。
• 尋找變量聲明的時候,不會管裏面的代碼到底會不會執行,執行是後面的事,這裏只負責尋找全部變量。
打印第一個a的時候並不會報錯而是undefined,當a沒有聲明的時候纔會報錯,所以這裏a是有聲明的,只是沒有賦值而已,它根本不看有沒有if,if的條件是否是真對尋找變量聲明都沒有關係。
第二步尋找形參和變量聲明時候的AO對象:
以上就是我總結的預編譯的知識點,但願對你們有幫助喲!~