JavaScript做用域及預編譯

幾乎全部的編程語言均可以存儲,訪問,修改變量,那在JavaScript中這些變量放在那裏?程序如何找到他們?編程

js被歸類於解釋執行語言,但事實上他也是一門編譯語言,由於他也要編譯,但於傳統的編譯語言不一樣,他不是提早編譯,編譯結果也不能在分佈式系統中進行移植。但js引擎編譯的步驟和傳統的編譯語言很是類似。編程語言

傳統的編譯會經歷3個步驟:分佈式

  1. 分詞:將組成的字符串分解成有意義的代碼塊(詞法單元)for instance:var a = 2;被分解成var,a,=,2,;。
  2. 語法分析:將詞法單元轉換成有元素逐級嵌套所組成的表明了程序語法結構的樹,這個樹叫抽象語法樹。
  3. 代碼生成:將抽象語法樹轉換成可執行的代碼的過程。簡單來講就是將抽象語法樹轉化爲一組機器指令,用來建立一個叫做a的變量(包括分配內存等),並將值存儲到a中。

對於傳統的編譯過程,js引擎要複雜的多,js編譯發生在執行前的幾微秒,而後作好執行他的準備,而且一般立刻就會執行他。函數

說道這仍是沒有說這些變量放在那裏?下面介紹3位大佬:spa

  1. 引擎:從頭至尾負責整個js程序的變以及執行過程。
  2. 編譯器:負責語法分析和代碼生成等。
  3. 做用域:負責收集並維護由全部聲明的標識符(變量和函數)組成的一系列查詢,並實施一套很是嚴格的規則,肯定當前執行的代碼對這些變量的訪問權限。

這時候答案就浮出水面。啊,是這夥計--做用域.prototype

做用域?大哥你哪來的啊?上面提到做用域像一個容器同樣收集並維護全部標識符(變量和函數)事實上他是一個對象,收集的東西掛在它的屬性上。做用域的出生是由於函數的產生而產生的。當函數執行的前一刻的時候,會建立一個做用域,這個做用域定義了這個函數執行時的環境,函數每次執行時的做用域都是獨一無二的,由於他是對象啊,就像出生的孩子長得都不太同樣。這個做用域小名特別多,什麼執行期上下文,AO(Activation Object)對象,活動對象。做用域這幫夥計們有個頭叫--全局做用域。裏面的做用域能看到外面的,哈哈,你在我面前就是個小透明,但外的做用域可看不到裏面的。指針

 

在js高程裏解釋道做用域。code

 做用域是因函數產生而產生的,每一個對象都有屬性和方法,函數(function)也是一種特殊的對象,函數能夠有test.name test.prototype ...這些是能夠訪問的,還有一些屬性是不能夠訪問的隱式屬性僅供JavaScript引擎處理。 好比[[scope]]:指的是做用域鏈,其中存儲了執行期上下文的集合。這個集合呈現鏈式鏈接,咱們把這種鏈接叫作做用域鏈。做用域鏈本質上是一個指向變量對象的指針列表,他只是引用,但不包含實際變量對象。.[[scope]]這裏面存的就是做用域。系統會根據內部的原理去按期調用scope。當函數執行的前一刻的時候,會建立一個稱爲執行期上下文的內部對象(AO activation object)。一個執行期上下文定義了一個函數執行時的環境。函數每次執行時對應的上下文都是獨一無二的,即便執行同樣的函數可是執行期上下文並不相同,因此屢次調用一個函數會致使建立多個執行上下文,當函數執行完畢,他所產生的執行上下文會銷燬。對象

 

上面還提到了編譯,說道js編譯發生在執行前的幾微秒。也講到變量和函數會放到做用域的問題,事實上,是這樣的,在程序執行前不是要進行編譯的嗎,在引擎和編譯器兩位大哥的幫助下 ,在編譯時(函數執行前)會處理聲明的變量和函數,把他掛到做用域這個對象的屬性上。這個過程叫 -- 見下行。blog

預編譯

當你看見var a = 2;這段程序時,極可能認爲這是一句聲明,事實上咱們引擎這哥們認爲有兩個徹底不一樣的聲明,一個由編譯器在編譯時處理,另外一個在引擎運行時處理。

代碼執行前會對其進行編譯,首先編譯器會分詞,而後解析成語法樹,最後進行代碼生成,別忘了代碼生成就是將語法樹轉化爲一組機器指令。

  1. 生成代碼前編譯器會詢問做用域是否有該名稱的變量,若是有,忽略該聲明,若是沒有,會要求做用域在當前做用域的集合中聲明一個新變量,並命名爲a。
  2. 接下來爲引擎生成運行時所須要的代碼,這些代碼用來處理a = 2這個賦值操做,運行時引擎首先會問做用域,在當前做用域集合中是否有一個叫做a的變量,若是找到就會對他賦值,不然就會拋出異常。

也就是說變量和函數在內的全部聲明都會在任何代碼被執行前首先被處理。--先編譯,在執行。

console.log(a);  //undefinde
var a = 2; 
console.log(a);  //2

這也解釋了爲何第一行沒有報錯的緣由。

首先代碼執行前一刻進行編譯,var a; console.log(a); a = 2; console.log(a);相似這樣的執行順序(就好像變量和函數從他們的代碼中出現的位置被移動到了最上面)。 編譯器看到var a會查看當前做用域是否有變量a,沒有聲明一個變量a,開始執行代碼(js是順序執行)。

  1. console.log(a)查看做用域是否有變量a,有,但沒有值,a爲undefined。
  2. var a = 2;查看做用域是否有變量a,有,對a賦值a = 2。
  3. console.log(a)查看做用域是否有變量a,有,值爲2。

    當函數和變量同名時,函數會覆蓋變量

  由此預編譯過程能夠總結成一下過程

  預編譯四部曲:

  • 1.建立AO對象/活動對象(activation object)(執行期上下文)
  • 2.找形參和變量聲明,將變量和形參名做爲AO屬性名,值爲undefined
  • 3.將實參值和形參統一
  • 4.在函數體裏面找到函數聲明,值賦予函數體
相關文章
相關標籤/搜索