第一部分:作用域是什麼?
- 編譯原理
- 理解作用域
- 作用域嵌套
- 異常
- 小結
編譯原理
編譯過程:
- 分詞/詞法分析
- 解析/語法分析
- 代碼生成
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