1. 程式人生 > >詳解JS作用域(一)

詳解JS作用域(一)

一、什麼是作用域

儲存和訪問變數,是任何一種程式語言最基本的功能之一,變數存在哪裡?程式需要時如何找到它?這些問題需要一套良好的規則來規範,這套規則,就成為作用域。

二、編譯原理

js通常歸類為解釋語言,但它其實是編譯語言,和傳統編譯語言不同,它不是提前編譯,編譯結果也不能在分散式系統中進行移植。js引擎進行編譯的步驟和傳統的編譯語言非常相似,傳統編譯語言,程式中的一段原始碼在執行之前會經歷三個步驟,統稱為“編譯”。

2.1 分詞/詞法分析

這個過程會講由字元組成的祖父穿分解成有意義的程式碼塊。這些程式碼塊叫詞法單元。例如:var a=2;會分解成以下這些詞法單元:

var、a、=、2、;

空格是否會被當做詞法單元,取決於空格在這門語言中是否具有意義。

2.2 解析/詞法分析

這個過程是將詞法單元流轉成一個由元素逐級巢狀所組成的代表了程式語法結構的樹,這個樹叫做抽象語法樹(AST)

var a=2; 的抽象語法數中可能會有一個叫做VariableDeclaration的頂級節點,接下來是一個叫做Identifier(它的值是a)的節點,以及一個叫做AssignmentExpression的子節點,AssignmentExpression節點有一個叫做NumericLiteral(它的值是2)的子節點。

2.3 程式碼生成

將AST轉換成可執行程式碼的過程叫做程式碼生成,簡單來說就是有某種方法可以講var a=2 ;的AST轉化成一組機器指令,用來建立一個叫做a的變數(包括分配記憶體等),並將一個值存在a中。

比起那些編譯過程只要三個步驟的語言編譯器,js引擎要複雜得多,例如,在語法分析和程式碼生成階段有特定的步驟來對執行效能進行優化,包括對冗餘元素進行優化等。簡單來說,任何js程式碼片段在執行前都要進行編譯(通常就在執行前),因此js編譯器首先會對var a=2;這段程式進行編譯,然後做好執行他的準備,並且通常馬上就會執行它。

三、理解作用域

3.1 參與到var a=2;進行處理的過程中的演員們 

引擎:從頭到尾負責整個js程式的編譯和執行過程

編譯器:引擎的好朋友,負責詞法分析以及程式碼生成等髒活累活。

作用域:引擎的另外一個朋友,負責收集並維護由所有宣告的識別符號(變數)組成的一系列查詢,並實施一套非常嚴格的規則,確定當前執行的程式碼對這些識別符號的訪問許可權。

3.2 對話 

看看var a=2;的執行過程。

編譯器首先會將這段程式碼分解成詞法單元,然後將詞法單元解析成一個樹結構,但是當編譯器進行程式碼生成時,他對這段程式的處理方式和預期的有所不同。可以合理地假設編譯器所產生的程式碼能夠用下面的虛擬碼進行概括:“為一個變數分配記憶體,將其命名為a,然後將值2儲存在這個變數”,然而,這並不完全正確。

事實上編譯器會進行如下處理:

遇到var a,編譯器會詢問作用域是否已經有一個該名稱的變數存在於同一個作用域的集合中,如果是,編譯器會忽略該宣告,繼續進行編譯,否則他會要求作用域在當前作用域的集合中宣告一個新的變數,命名為a。

接下來編譯器會為引擎聲稱執行時所需的程式碼,這些程式碼備用處理a=2這個賦值操作。引擎執行時會首先詢問作用域,在當前的作用域集合中是否存在一個叫做a的變數,如果是,引擎會使用這個變數,否則,引擎會繼續查詢變數。

如果引擎最終找到了a變數,就會將2賦值給它,否則引擎會舉手示意丟擲一個異常。

所以,變數的複製操作會執行兩個動作,首先編譯器會在當前作用域中宣告一個變數(如果之前沒有宣告過),然後再執行時引擎會在作用域中查詢這個變數,如果能夠找到就會對他賦值。

3.3 編譯器有話說

編譯器在編譯過程的第二步中生成了程式碼,引擎執行它時,會通過查詢變數a來判斷是否已宣告過,查詢的過程由作用域協助,但是引擎執行怎麼樣的查詢,會影響最終的查詢結果。

引擎為會a進行LHS查詢,另外一個查詢的型別叫做RHS。

當變量出現在賦值操作的左側時,進行LHS查詢,出現在右側時進行RHS查詢。

console.log(a); //對a的引用是一個RHS引用

a=2;//對a的引用是LHS引用

三、作用域巢狀

當一個塊或者函式巢狀在另外一個塊或者函式中時,就發生了作用域巢狀,在當前作用域中無法找到這個變數時,引擎就會在外層巢狀的作用域中繼續找,直到找到該變數,或抵擋最外層的作用域位置。

function foo(a){
     console.log(a+b);
}
var b=2;
foo(2);

引擎:foo作用域兄弟,你見過b嗎?我需要對它進行RHS引用。

作用域:沒有

引擎:foo的上級作用域兄弟,你見過b沒?我需要對它進行RHS引用。

作用域:當然了,給你!

四、總結

作用域是一套規則,用於確定在何處以及如何查詢變數,如果查詢的目的是對變數賦值,那麼就使用LHS查詢,如果目的是獲取變數的值,就用RHS查詢,