在寫這篇博客時這本書我已經是看過一遍了,為了加深印象和深入學習於是打算做這系列的前端經典書籍導讀博文,大家如果覺得這本書講的好可以自己買來看看,我是比較喜歡看紙質版書的,因為這樣才有讀書的那種感覺。 本期我給大家講述的是 前端經典js書籍 <<你不知道的javaScript(上捲)>> 第一章內容的 ...
在寫這篇博客時這本書我已經是看過一遍了,為了加深印象和深入學習於是打算做這系列的前端經典書籍導讀博文,大家如果覺得這本書講的好可以自己買來看看,我是比較喜歡看紙質版書的,因為這樣才有讀書的那種感覺。
本期我給大家講述的是 前端經典js書籍 <<你不知道的javaScript(上捲)>> 第一章內容的知識點總結和講解。
1.1 編譯原理
儘管通常將js歸類為“動態”或“解釋執行”語言,但事實上它是一門編譯語言。但與傳統的編譯語言不同,他不是提前編譯的,編譯結果也不能在分散式系統中進行移植。在傳統編譯語言的流程中,程式中的一段源代碼在執行之前會經歷三個步驟,統稱為“編譯”。
1>分詞/詞法分析
這個過程會將由字元組成的字元串分解成(對編程語言來說)有意義的代碼塊,這些代碼塊被稱為詞法單元。例如,考慮程式 var a=2;這段程式通常會被分解成為下麵這些詞法單元 : var 、a、=、2、;。空格是否會被當做詞法單元,取決於空格是否在這門語言中具有意義。
2>解析/語法分析
這個過程是將詞法單元流(數組)轉換成一個由元素逐級嵌套所組成的代表了程式語法結構的樹。這個樹稱為“抽象語法樹”(AST)。
3>代碼生成
將AST轉換為可執行代碼的過程被稱為代碼生成。這個過程與語言、目標平臺等息息相關。拋開具體細節,簡單的來說就是有某種方法可以將 var a=2 ;的AST轉化為一組機器指令,用來創建一個叫做a的變數(包括分配記憶體等),並將一個值存儲在a中。
1.2理解作用域
為了進一步理解,我們需要多介紹一點編譯器的術語。在我們的例子中,引擎會為變數a進行LHS查詢。另外一個查找的類型叫做RHS查詢。我打賭你一定能猜到“L”和“R”的涵義,它們分別代表左側和右側。什麼東西的左側和右側?是一個賦值操作的左側和右側。
換句話說,當變數出現在賦值操作的左側時進行LHS查詢,出現在右側時進行RHS查詢。講得更準確一點,RHS查詢與簡單地查找某個變數的值別無二致,而LHS查詢則是試圖找到變數的容器本身,從而可以對其賦值。從這個角度來說,RHS並不是真正意義上的“賦值操作的右側”,更準確的說是“非左側”。
考慮以下代碼:
1 console.log(a); 其中對a 的引用是一個RHS引用,因為這裡a並沒有賦予任何值。相應地,需要查找並取得a的值,這樣才能將值傳遞給console.log(...)。
相比之下,例如:
1 a=2; 這裡對a的引用則是一個LHS引用,因為實際上我們並不關心當前的值是什麼,只是想要為=2這個值賦值操作找到一個目標。
1.3作用域嵌套
當一個塊或函數嵌套在另一個塊或函數中,就發生了作用域的嵌套。因此,在當前作用域中無法找到某個變數時,引擎就會在外層嵌套的作用域中繼續查找,直到找到該變數,或抵達最外層的作用域(也就是全局作用域為止)。
考慮以下代碼:
1 function foo(a){
2 console.log( a+b );
3 }
4 var b=2;
5 foo( 2 ); //4
對b進行的RHS引用無法在函數foo內部完成,但可以在上一級作用域(在這個例子中就是全局作用域)中完成。
把作用域鏈比喻成一個建築:第一層樓代表當前的執行作用域,也就是你所處的位置。建築的頂層代表全局作用域。LHS和RHS引用都會在當前樓層進行查找,如果沒有找到,就會乘坐電梯前往上一層樓,如果還沒找到就繼續向上,以此類推。一旦抵達頂層(全局作用域),可能找到了你所需的變數,也可能沒找到,但無論如何查找過程都將停止。
1.4異常
為什麼區分LHS和RHS是一件重要的事情? 因為在變數還沒有聲明(在任何作用域中都無法找到該變數)的情況下,這兩種查詢的行為是不一樣的。
考慮如下代碼:
1 function foo(a){
2 console.log( a+b );
3 b=a;
4 }
5 foo( 2 );
第一次對b進行RHS查詢時是無法找到該變數的。也就是說,這是一個“未聲明”的變數,因為在任何相關的作用域中都無法找到它。
如果RHS查詢在所有嵌套的作用域中遍尋不到所需的變數,引擎就會拋出 ReferenceError異常。相較之下,當引擎執行LHS查詢時,如果在頂層(全局作用域)中也無法找到目標變數,全局作用域中就會創建一個具有該名稱的變數,並將其返還給引擎,前提是程式運行在非嚴格模式下。
嚴格模式下在行為上有很多不同。其中一個行為就是禁止自動或隱式地創建全局變數。因此,在嚴格模式下LHS查詢失敗時,並不會創建並返回一個全局變數,引擎會拋出同RHS查詢失敗時類似的 ReferenceError異常。
小結:作用域是一套規則,用於確定在何處以及如何查找變數(標識符)。如果查找的目的是對變數進行賦值,那麼就會使用 LHS查詢;如果目的是獲取變數的值,就會使用 RHS查詢。賦值操作符會導致 LHS查詢。=操作符或調用函數時傳入參數的操作都會導致關聯作用域的賦值操作。js引擎首先會在代碼執行前對其進行編譯,在這個過程中,像 var a=2 ;這樣的聲明會被分解成兩個獨立的步驟:
1.首先,var a 在其作用域中聲明新變數。這會在最開始的階段,也就是代碼執行前進行。
2.接下來,a=2 會查詢(LHS查詢) 變數a並對其進行賦值。
LHS和RHS查詢都會在當前執行作用域開始,如果有需要(也就是說它們沒有找到所需的標識符),就會向上級作用域繼續查找目標標識符,這樣每次上升一級作用域(一層樓),最後抵達全局作用域(頂層),無論找到還是沒找到都將停止。
不成功的RHS引用會導致拋出 ReferenceError異常,不成功的LHS引用會導致自動隱式地創建一個全局變數(非嚴格模式下),該變數使用LHS引用的目標作為標識符,或者拋出 ReferenceError 異常(嚴格模式下)。