1. 程式人生 > >你不知道的JavaScript--作用域(一)

你不知道的JavaScript--作用域(一)

第一部分:作用域是什麼?

  • 編譯原理
  • 理解作用域
  • 作用域巢狀
  • 異常
  • 小結
編譯原理

編譯過程:

  • 分詞/詞法分析
  • 解析/語法分析
  • 程式碼生成

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