作用域是什麼
1. 編譯原理
開發者大多把JavaScript歸為“動態”或者“解釋執行”語言,但是實際上JavaScript也是編譯語言,只是並不是提前編譯的。JavaScript的編譯發生在程式碼執行前的幾微秒。
程式碼的編譯分為以下步驟:
1. 分詞/詞法分析 以JavaScript為例, var a = 2; 這段程式會被分解成以下詞法單元: var、a、=、2、;。空格是否會當做詞法單元取決於空格在這門語言中是否有意義
2. 解析/語法分析 將分詞得到的詞法單元轉換成“抽象語法樹”(AST)
3. 程式碼生成 將AST轉換為可執行程式碼的過程 然而,比起上述的編譯過程只有三個步驟的語言的編譯器,JavaScript的引擎要複雜得多。JavaScript引擎在語法分析和程式碼生成階段會對執行效能進行優化,包括對冗餘元素的優化。
2.理解作用域
在作用域中參與工作的有以下幾個部分: 引擎、編譯器、作用域。 當執行var a = 2;時 編譯器先將程式碼分解成詞法單元,然後解析為“抽象語法樹”。當開始進行程式碼生成時,對這段程式碼的處理方式會和預期的有所不同。
編譯器的處理如下:
1. 遇到var a,會先去詢問該作用域是否已經存在a變數。若存在,則忽略var 宣告;否則要求作用域在該作用域生成一個變數a。
2. 接下來編譯器生成引擎執行時所需的程式碼,這些程式碼唄用來處理a = 2這個賦值操作。引擎執行時先詢問作用域當前作用域是否存在一個叫a的變數,若是,則使用這個變數,否則繼續查詢該變數直到找到,或者到全域性作用域(不管找沒找到)而停止。
總結:變數賦值的的過程概括來說就是 1. 編譯器宣告變數。2. 引擎查詢變數,找到賦值。
3.LHS和RHS
區分LHS和RHS是一件非常重要的事情!!!!
1. LHS:查詢變數賦值操作的目標
2. RHS:取源值,非LHS即不是賦值操作的目標都為RHS
以下為例子:
引擎和作用域: 引擎想作用域去討要引用,引擎執行程式碼操作
4.作用域巢狀
在當前作用域無法找到某個變數時,引擎會在外層作用域繼續查詢,知道找到該變數,或者到全域性作用域(不管找沒找到)而停止。
5.異常
現在我們來講講為什麼區分LHS和RHS是一件重要的事情。
在變數還沒有宣告(在任何作用域中都無法找到該變數)的情況下,這兩種查詢行為不同。
下列程式碼的真實操作為右邊的程式碼塊
var b
function foo(a) { function foo(a) {
console.log(a + b) ==> console.log(a + b) // 這裡對b是RHS查詢的,因為無法找到,所以丟擲ReferenceError錯誤
b = a b = a //這裡對b進行LHS查詢,直到全域性作用域都沒有查到,又因不是strict模式,則在全域性作用域自動建立了b
} }
foo(2)複製程式碼
RHS查詢
: 在所有巢狀的作用域中都無法找到時,會丟擲ReferenceError
LHS查詢
:無法找到時分為兩種情況
1. 非strict模式下: 會熱心的建立一個變數,當然是在全域性作用域中建立。
2. 在strict模式下: 同樣會丟擲ReferenceError
另外RHS找到了這個變數,
但是你對這個變數進行不合理的操作,如:非函式型別值的函式呼叫、引用null/undefined型別的值的屬性。則會丟擲TypeError。 ReferenceError錯誤和作用域判別失敗相關,而TypeError則是作用域判別成功,但是對結果操作是非法或不合理的。
注: LSH和RSH都是從當前作用域開始向頂層查詢的。