你不知道的JavaScript--作用域(一)
阿新 • • 發佈:2019-02-01
第一部分:作用域是什麼?
- 編譯原理
- 理解作用域
- 作用域巢狀
- 異常
- 小結
編譯原理
編譯過程:
- 分詞/詞法分析
- 解析/語法分析
- 程式碼生成
1)分詞/詞法分析:
- 這個過程會將字元組成的字串分解成有意義的 程式碼塊 ,這些程式碼塊被稱為 詞法單元。
- eg:var a = 2; 這段程式通常會被分解成 var 、a 、= 、2 、;
- 空格是否會被當作詞法單元,取決於空格在這門語言中是否有意義。
- 分詞和詞法分析之間的主要差異在於 詞法單元的識別是通過 有狀態還是無狀態的 方式進行的。
- 詞法分析:詞法單元生成器在判斷a是一個獨立的詞法單元還是其他詞法單元的一部分時,呼叫的是有狀態的解析規則,這個過程就被稱為詞法分析。
2)解析/語法分析
- 將詞法單元流(陣列)轉換成一個 “抽象語法樹”。AST
- 這棵樹是有元素逐級巢狀所組成的 代表程式語法結構的樹。
3)程式碼生成
- 將 AST 轉換成可執行程式碼的過程
- 建立並分配記憶體,並完成賦值操作
除了以上過程,js在語法分析和程式碼生成階段有特定的步驟來對執行效能進行優化,包括對冗餘元素進行優化。
js程式碼在執行前都要進行編譯。js編譯器首先會對程式碼片段進行編譯,然後做好執行它的準備,並且馬上就會執行它。
理解作用域
- 作用域是根據名稱找變數的一套規則
- 引擎會對變數進行LHS、RHS查詢。分別是賦值操作的左側和右側。查詢過程通過作用域進行協助。
- LHS(左側):試圖找到變數的容器本身
- RHS(右側):查詢某個變數的值
- 當變量出現在賦值操作的左側時進行LHS查詢(a=2),出現在右側是進行RHS查詢(console.log(a);)。
a=2; //為=2這個操作找到一個目標
console.log(a); //關心的是這個值
function foo(a){
console.log(a);
}
foo(2);
// 2次RHS 1次LHS
找到LHS、RHS查詢的次數?。
function foo(a){
var b = a;
return a+b;
}
var c = foo(2);
作用域巢狀
- 當一個塊函式巢狀在另一個塊函式中時,就發生了作用域的巢狀。
- 遍歷巢狀作用域鏈的規則很簡單: 引擎從當前的執行作用域開始查詢變數, 如果找不到,就向上一級繼續查詢。 當抵達最外層的全域性作用域時, 無論找到還是沒找到, 查詢過程都會停止。(當前作用域->上級作用域->…->全域性作用域)
異常
- RHS查詢在所有巢狀的作用域中遍尋不到所需的變數,引擎就會丟擲ReferenceError的異常。
- LHS
- 非嚴格模式:在全域性作用域中也無法找到目標變數,就會建立一個,並將它返回給引擎
- 嚴格模式:禁止自動或隱式地建立全域性變數,引擎丟擲ReferenceError的異常。
- ReferenceError:同作用域判別失敗相關
- TypeError:作用域判別成功,但是對結果操作是非法和不合理的。(eg:對一個非函式型別的值進行函式呼叫,或者引用null或undefined型別的值中的屬性)
//RHS
console.log(a) //ReferenceError
var a;
a();
console.log(a)//TypeError
//LHS
"use strict";
function foo(a) {
// console.log(a+b);
b=a;
}
foo(2);//ReferenceError
function foo(a) {
// console.log(a+b);
b=a;
}
foo(2);
小結
1.作用域是一套規則,用於確定在何處以及如何查詢變數(識別符號)
- 如果查詢的目的是對變數進行賦值,那麼會使用LHS查詢
- 如果查詢的目的是獲取變數的值,就會使用RHS查詢
- 賦值操作符會導致LHS查詢
2.在同一作用域中,對於同一個變數的多個宣告,編譯器只會編譯第一個宣告,其他的都會忽略
3.在編譯階段,編譯器會完成宣告和賦值兩個操作。
// 答案:4次RHS 3次LHS