深入學習Java Scipt之作用域和閉包

引擎與作用域及編譯器

在傳統的編譯語言的流程中,程序的一段源代碼主要分成三步,統稱爲“編譯”

  •   分詞/詞法分析

    它的主要作用是將字符組成的字符串分解成有意義的代碼塊,例如:var a=2;者會被分解成“var”,“a”,“=”,“2”。

空格是否被分解主要是看空格在該編程語言中有沒有意義。

  •  解析/語法分析

  這個過程是將詞法單元流(數組)轉換成由元素逐級嵌套所組成的程序語法結構的樹。這棵樹被稱爲“抽象語法樹”

  • 代碼生成

  將AST轉換成可執行代碼的過程。如果不考慮具體細節的話,那麼簡單來說此過程便是將var a=2這一組AST轉換成機器指令,用來創建一個叫做a的變量(分配內存),將2存儲在a中。

  Java Script引擎要比這個過程複雜得多,但是它沒有那麼多時間來進行優化或者對代碼的其他處理,因爲Java Script代碼通常在代碼執行前的幾微秒內進行編譯和處理,看到這,也許你會有疑惑,爲什麼Java Script引擎能這麼快呢?這是因爲在我們討論的作用域背後Java Script用盡了各種辦法,將性能達到最優化。

作用域

  要想了解作用域是什麼?起到怎麼樣的作用,必須還得了解以下的概念。

  • 引擎

  從頭到尾負責代碼的編譯與運行

  • 編譯器

  負責語法分析與代碼生成

  • 作用域

  負責收集和維護所有聲明的標識符組成的一系列查詢,並實施一套非常嚴格的規則,判斷當前執行的代碼對變量訪問的權限。通俗點來講就像是倉庫的管理員,它負責物品(聲明的標識符)的安全(維護),以及的上架(收集)。對來訪者(引擎或者編譯器)取物品進行檢查判斷(判斷權限)。

若你還是覺得很難理解,那沒有關係,我們舉個例子
 **例Java Script執行代碼“ var a=2”。當引擎見到此聲明時,它不僅會自己處理,還會將此聲明拋給編譯器進行處理。**

  **編譯器的處理**:將“var a=2”分解爲詞法單元,由這些詞法單元生成抽象語法樹。然後接着生成代碼,那麼由僞代碼描述“var a=2”,它的執行結果是這樣的:“爲一個變量分配內存,並命名爲a,然後將2這個值保存在變量裏面”。儘管看上去很完美,但是由於作用域的存在,編譯器不得不進行另一種操作。

   遇到“var a=2”,編譯器會先向作用域詢問是否有一個該名稱的變量存在於作用域中,是的話它就會忽略這次聲明,繼續編譯。否的話,它就會要求作用域在當前作用域集合中創建一個名爲"a"的變量,然後再編譯。我們來按照之前的倉庫理解,編譯器是對商品(代碼)加工的工人,當它遇到“var a=2”的物品清單(聲明),它會將這個物品羅列出來(詞法分析),然後看看物品之間的需求需要(抽象語法樹),接着去倉庫找物品(生成代碼),在倉庫的門口遇到了倉庫管理員(當前的作用域集合),提出了物品要求,倉庫管理員進庫檢索物品(查看是否創建了名爲"a"的變量),若有,那麼將物品遞給工人(忽略此次聲明);沒有的話,則向總部提出此庫("當前的作用域結合")物品進貨(創建"a"變量)
   接下來編譯器將爲引擎運生成運行的代碼,這些代碼用來處理a=2這個賦值操作。引擎運行時會首先詢問作用域,當前作用域中是否存在"a"這個變量,是的話,繼續操作,不是的話,將在作用域中急促查找。
引擎最終找到了"a"這個變量,便會將2賦值給它,不然的話,它就會拋出一個異常。


  總結:在對"var a=2"的執行過程中,編譯器首先對代碼進行詞法分析、語法分析,然後創建變量(如果作用域中不存在的話),運行時,引擎再一次在作用域中查詢變量,如果找到的話就給它賦值。(經歷了兩次變量查詢,此處的作用域指的是當前的作用域集合)

 

引擎查詢變量的方式


  在編譯器將聲明生成可運行代碼時,我們的引擎將會進行變量查找,引擎進行怎樣的查找將會影響到查找的結果。
   在Java Script中引擎查找變量涉及到的查找方式主要有兩種,LHS查詢以及RHS查詢,**LHS是在賦值操作左側查找**,**RHS是賦值操作右側查找**,在我們的例子中,“var a=2”涉及到的是LHS查詢。
   例:
      console.log(a);
      在這裏面就涉及到了RHS查詢,怎麼理解呢?在這代碼裏面"a"沒有值,相應的它需要查找得到"a"的值,這樣的話才能把值傳給console.log();

我們把兩個例子進行比較

      a=2

  在這裏面我們並不需要查找"a"的值,而是要爲"=2"這一個操作找到一個目標。

爲了更加熟悉"LHS"以及"RHS",我們再舉個例子

-------------------------------------------

  function foo(a){

console.log(a);

};

foo(2);

---------------------------------------------

  首先我們先看看foo()函數,console.log(a);中涉及到了"RHS"查詢。

在foo(2)中我們也涉及到了"RHS"查詢(查找foo()),但是,別忽略了,在此函數中將"2"的值傳給形參"a"即"a=2"此處涉及到了"LHS"查詢。

  這裏面涉及到了幾次"RHS"以及"LHS"查詢呢?

foo(2)------一次"RHS'查詢,查詢foo()函數,

console.log(a)--------三次"RHS"查詢,一次查詢console,一次.log,一次"a"

在傳遞形參時-------一次"LHS"查詢

總結:"LHS"是找到操作的容器,"RHS"是查找變量的值

那麼爲什麼要區分LHS和RHS,這對我們調試Java Script是有好處的

  • 當作用域進行RHS查詢時,查詢不到結果時,它便會拋出ReferncError錯誤,而在查詢到變量,但是對變量的引用不正確的情況下,會拋出TypeError錯誤。
  • 當作用域進行LHS查詢時,在非ES5的嚴格模式下,它查詢不到變量時,便會主動創建一個變量。而在ES5的嚴格模式下,它則會彈出ReferenceError錯誤。

作用域嵌套

  在我們舉得例子中,如編譯器對變量的查詢,引擎對變量的查詢都是在當前作用域集合中操作的,通常不僅只有這麼一個作用域,而是多個。

  例如,當一個塊或者函數在另一個塊或者函數中時,便會發生作用域的嵌套,首先它們會在當前作用域下查詢變量,沒有查詢到則返回外層作用域(直到最外層全局作用域)--------------這就是作用域的嵌套。

舉個例子:

        function foo(a){

    console.log(a+b);

}        

var b=2;

foo(2);

在foo函數中我們需要對b進行RHS引用(查詢b),但是在foo()函數這一層的作用域中,並沒有查詢到b,於是它便回到外層作用域查詢b,在"var b=2"中查詢到了b。

運用我們之前講過的倉庫例子,引擎是一名採購商人,它需要b這一個商品(RHS引用),於是去問了當前商店所在的倉庫管理員(當前作用域),“這裏有b這個商品嗎?”,“沒有,但是您可以去更高級別的倉庫(外層作用域)找找”倉庫管理員回答,於是採購商便去更高級別的倉庫找到了b。

嵌套規則:從當前的作用域開始查找,向更高級別的作用域查找,直到最高級別的作用域,查找不到則停止拋出異常(根據RHS以及LHS不同拋出的異常不同)

 

異常

  爲什麼要知道LHS與RHS查詢呢??這對我們調試Java Script代碼是有好處的。

例如:   function(a){

console.log(a+b);

b=a;

}

foo(b);

  我們在foo()中對b進行RHS查詢,但是在當前的作用域中查詢不到b,根據作用域嵌套規則,我們返回最外一層,發現也沒有b。此時我們拋出ReferenceError異常-------即RHS查詢不到相關變量則拋出ReferenceError異常。

  而LHS則不同,如果在最外層的作用域中查詢不到變量的話,它就會自作主張的創建一個變量,此時不會拋出異常

  但是在ES5的情況下,LHS表現則不同,在ES5(嚴格模式)下禁止自動或者隱式創建變量,這就意味着進行LHS查詢不能夠自動創建變量了!!!-------此時拋出ReferenceError異常

   RHS查詢還有一個細節,就是當你查詢到變量,但對變量的操作不正確時,也會拋出異常,例如在對一個數組執行大於數組下標的操作時--------拋出TypeError異常。

總結:

  • RHS查詢不到變量時拋出ReferenceError異常,查詢到變量對變量操作不正確時拋出Type異常

  • LHS在非ES5模式下,查詢不到變量時,自動創建變量。在ES5模式下,查詢不到變量時,拋出Reference異常

 

本章小結

  作用域實際上是一套用於確定變量的位置以及查詢變量的規則。代碼的編譯分爲三部分,詞法分析--語法分析--代碼生成。前兩步是編譯器在執行,直到生成可執行代碼時,引擎纔開始從作用域中查詢變量。

  像Var a=2可分爲兩步

  1、聲明變量:var a將會在當前的作用域內創建a變量,分配內存

  2、賦值操作:a=2會查詢(LHS)變量a,然後進行賦值操作

  RHS查詢是找到變量的值

  LHS查詢是爲變量進行賦值操作(包括實參形參的傳遞)

  RHS以及LHS查詢都從當前作用域開始,一層一層往更高級作用域查詢,直到全局作用域停止(找到變量立即停止)。

  當RHS沒查詢到變量時,拋出ReferenceError異常,查詢到變量但變量操作不正確時,發生TypeError異常。

  不在嚴格模式(ES5)下,當LHS沒查詢到變量時,它自動隱式創建變量。在嚴格模式(ES5)下,LHS不允許自動隱式創建變量,拋出ReferenceError異常。

 -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------本文爲作者自行編寫,轉載需聯繫本人

 

 

 

 

 

 

 

 

 

  

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章