【共讀】你不知道的js 上 (1)做用域是什麼?

前言

本文會用思惟導圖的形式列出本書該部分的知識點(剔除案例),構建知識脈絡。因爲是導讀,正文部分只會列舉部分的內容。本文適合未讀過此書的同窗參考,另外讀過此書的同窗,若是能純熟得答出文初的問題,那麼相信您對於這部分的內容能夠說是記憶深入了。編程

建議在閱讀前瞭解做者的平生,背景,核心貢獻及思想。相信會對理解本書以及後續的選書讀書會有所幫助。bash

豆瓣讀書編程語言

問題

  1. 談談你對做用域的理解。
  2. 引擎,編譯器,做用域分別是什麼?它們如何共同協做?
  3. 介紹一下 ReferenceError 異常類型 和 TypeError 異常類型。
  4. 談談你對做用域鏈的理解。

做用域是什麼?

談談你對做用域的理解。(我的理解,求拍磚)函數

做用域收集而且維護由全部聲明的標識符組成的查詢,有本身很是嚴格的規則肯定當前執行代碼對標識符的訪問權限。性能

JavaScript 是一門編譯語言,在執行代碼前的編譯中,編譯器須要和做用域溝通是否存在某個變量來決定建立仍是忽略。優化

接着引擎須要爲變量賦值,它會經過 LHS查詢 或者 RHS查詢 查找變量,在當前做用域找不到時還要沿着做用域鏈一直往上往上找,若是在最外層的全局做用域也找不到,那麼拋出叫作 ReferenceError 的異常spa

這裏若是是使用 LHS查詢 當全局做用域也不存在查找的變量時會自動建立並返還給引擎。code

1、簡單介紹編譯原理

JavaScript 是一門編譯語言,可是它不像傳統語言那樣僅僅只經歷編譯的三個步驟,分詞/詞法分析,解析/語法分析,代碼生成。cdn

咱們的 JavaScript 引擎要複雜的多,JavaScript 會用盡各辦法(好比用JIT)來保證性能最佳。而且咱們要記住的是任何 JavaScript 代碼片斷在執行前都要進行編譯,大部分狀況下編譯發生在代碼執行前的幾微秒(甚至更短)。對象

  • 分詞/詞法分析:這個過程會將字符串分解成對編程語言來講有意義的詞法單元(代碼塊)

    var a = zhengyang;
    複製代碼

    以上代碼會被分解成 var、a、=、; 空格是否會被當成語法單元取決於它是否在此處具備意義。

    分詞和詞法分析實際上是一件事,詞在這裏指的是帶有某種歸類的字符串,詞經過詞法來劃分,分詞是目的,詞法分析是手段。

  • 解析/語法分析:這個過程會將詞法單元流轉化成一個由元素逐級嵌套所組成的表明程序語法結構的樹,這個樹叫作抽象語法樹(Abstract Syntax Tree,AST)。

    var a = zhengyang;
    複製代碼

    通過分詞/詞法分析,咱們把劃分好的代碼塊組成抽象語法樹,它有一個 VariableDeclaration(變量聲明) 的頂級節點,下面是一個 Identitier (值爲a)的子節點和一個叫作 AssignmentExpression(賦值表達式)的子節點,AssignmentExpression 有一個叫作 NumericLiteral(數值文字)的值爲 2 的子節點。

  • 代碼生成:將 AST 轉化爲課執行代碼的過程被稱爲代碼生成。

    簡單來講就是將 var a = 2AST 轉化爲一組機器指令來創造一個叫作 a 的變量,並將一個值存儲在 a 中。

2、引擎,編譯器,做用域分別是什麼?它們如何共同協做?

  1. 引擎:從頭至尾負責整個 JavaScript 的編譯及執行過程。

  2. 編譯器:引擎的同事負責語法分析及代碼生成等髒活累活。

  3. 做用域:引擎的另外一位同事,負責收集而且維護全部聲明的標識符組成的一些列諮詢。它由一套很是嚴格個規則,肯定當前執行的代碼對這些標識符的訪問權限。

  4. 三位一體工做流:

var a = zhengyang
複製代碼
  • 遇到 var a,編譯器會諮詢做用域是否已經存在該名稱的變量存在於同一個做用域集合中。是,就忽略 var a 繼續編譯;不然就會要求在當前做用域集合中生命一個新的變量,命名爲 a
  • 編譯器會爲引擎生成運行時所需的代碼,用來處理 a = 2 這個賦值操做。引擎在運行時會先諮詢做用域,當前的做用域集合中是否存在一個叫作a的變量。是,就會使用這個變量;否,引擎就會繼續查找該變量。
  • 若是最後引擎找到了 a 變量會將 2 賦值給它;否,引擎會拋出一個異常。
  1. 編譯器在第二步中生成了代碼,引擎執行它的過程當中會查找 a 判斷是否聲明過,這個查找方式會影響最終的查找結果
  2. LHS查詢RHS查詢:簡單來講 LHS 查詢就是當變量出如今賦值操做的左側時進行的查詢, RHS查詢 就是變量出如今賦值操做的右側時進行的查詢。要注意,查找只會在當前做用域進行。
console.log(a) 
複製代碼

以上代碼就是 RHS查詢 ,咱們能夠看到變量 a 出如今右側。

a = 2
複製代碼

以上代碼就是 LHS查詢, 變量 a 出如今左側。 6. 看一個具體例子

function foo(a) {
    console.log(a);//2
}
foo (2)
複製代碼

以上代碼首先進行的聲明 foo 函數,變量在右因此使 RHS查詢

而後是隱式的 a = 2 這裏採用 RHS查詢 ,變量在左因此使用 LHS查詢

console.log console是內置對象,找 log 變量在右使用 RHS查詢

console.log(a) 同上變量在右使用 RHS查詢

3、做用域鏈是什麼?

做用域是根據名稱查找變臉的一套規則,當一個塊或函數嵌套在另外一個塊或函數中時就發生了做用域的嵌套。在當前做用域沒法找到某個變量時,引擎就會在外層嵌套的做用域中繼續查找,直到找到該變量爲止。

function foo(a){
    console.log(a + b)
}
var b = 2
foo(2); //4
複製代碼

上面的代碼中 console.log(a + b) 咱們在函數做用域中找不到 b 只能在上層的全局做用域中找

遍歷嵌套做用域做用域鏈的規則:引擎從當前的執行做用於開始查找變量,若是找不到就去上級繼續查找。當抵達最外層的全局做用域時,若是尚未找到,那麼查找就會中止。

如上圖的一條做用域鏈,咱們在當前做用域要找到 a 、b、c 當前做用域沒有就去外層做用域找在,找到了 b ,c沒找到繼續往外找,而後在全局做用域找到了 c 若是到此時尚未找到,那麼查找就會中止。

4、ReferenceError 異常類型 和 TypeError 異常類型

  1. ReferenceError 異常類型
function foo(a) {
    console.log( a + b);
    b = a;
}
複製代碼

以上代碼會報 ReferenceError的異常,由於咱們經過 RHS 查詢 在全部的嵌套做用域中都找不到 b。

相比之下若是是用 LHS查詢 非嚴格模式下,若是在全局做用於中也找不到就會幫你建立一個具備該名稱的變量,而且返還給引擎。

  1. 嚴格模式

嚴格模式禁止自動或隱式地建立全局變量。所以在嚴格模式中 LHS查詢 失敗時並不會建立並返回一個全局變量,而是會拋出 ReferenceError 異常。

  1. TypeError

若是經過 RHS查詢 找到了一個變量,可是你嘗試對這個變量的值進行不合理的操做,好比對一個非函數類型進行函數調用那麼就會拋出 TypeError 異常。

5、小結

  1. JavaScript 是一門編譯語言,它的編譯過程不只僅是傳統的三步,分詞/詞法分析,解析/語法分析,代碼生成。還須要通過大量JIT這樣的優化過程來保證性能。
  2. JavaScipt 引擎沒有大量時間用來優化,他的編譯過程不是發生在構建以前的而是在代碼執行前的幾微妙。
  3. 做用域是根據名稱查找變量的一套規則,在 JavaScript 中若是在當前做用域找不到某個變量時,就會到外層嵌套的做用域中繼續查找,若是在最外層的全局做用域當中也找不到那麼查找就會中止。做用域鏈就是這一層層往外找的一條路徑。
  4. 引擎從頭至尾負責整個 JavaScrip t的編譯及執行過程。編譯器負責語法分析及代碼生成。做用域負責收集而且維護由全部生命的標識符(變量)組成的一系列查詢,並實施一套很是嚴格的規則肯定當前執行的代碼對這些標識符的訪問權限。
  5. 引擎,編譯器,做用域的合做過程。
var a = 'zhengyang'
複製代碼
  • 執行前的編譯中,第一步,編譯器會看變量是否在做用域中已經存在。是,忽略;不是,建立並命名爲a。
  • 第二步,爲引擎生成運行時所需的代碼來處理 a = 'zhengyang' 這個賦值操做。
  • 銜接第二步,看變量在賦值操做的左邊仍是右邊引擎會使用 LHS查詢 或者 RHS查詢 在當前做用域查找變量。若是找到了引擎就會使用這個變量將 'zhengyang' 這個值賦給它。
  • 若是在當前做用域找不到就會經過做用域鏈向外找。若是最外層的全局做用域也找不到就會報 ReferenceError 異常。
  • 注意非嚴格模式下若是使用 LHS查詢 ,當最外層的全局做用域也不存在要查找的變量時會自動建立而且返回該變量給引擎,嚴格模式下則不可,由於嚴格模式禁止自動或隱式建立全局變量。
相關文章
相關標籤/搜索