【JS】做用域-1

讀《你不知道的JS》的筆記,有問題請指出。html

簡述做用域

每一個編程語言的一個最基本的功能,就是能夠聲明變量,在變量裏儲存值,更改值,訪問值。
隨之一系列問題產生,這些變量存儲在哪裏,未來須要使用他們的時候如何獲取他們?
這表明咱們須要有一套設計良好的規則知道如何存儲這些變量,並如何獲取到他們,而這套規則,被稱爲做用域(Scope)。編程

編譯和執行流程

不只是在執行時會用到做用域,編譯時也會用到做用域編程語言

編譯

咱們用 JS 寫的代碼稱爲源代碼,是一種人類能看懂的語言,因爲計算機只能讀懂 0 和 1(二進制/機器語言),因此若是咱們要在計算機上執行咱們的代碼,在執行代碼以前,有個編譯過程,目的是將源碼編譯成機器能夠理解的機器碼。函數

通常來講,編譯分爲 3 個步驟設計

  1. 分詞/詞法分析(Tokenizing/Lexing)
    將咱們寫的代碼(字符串),分解成單獨的、有意義的代碼塊,這種代碼塊也稱爲詞法單元(token),只抽取有意義的部分


    注:分詞≠詞法分析,他們是有細微區別的,區分他們的一個最直接的方式就是,好比 var a = 2這行代碼,當詞法單元生成器(tokenizer)在判斷 var 是單獨的一個詞法單元仍是屬於其餘詞法單元的一部分時,若是調用的是有狀態(stateful)的解析規則,那麼字符串被轉換成詞法單元的這個過程就被稱爲詞法分析,不然,就是分詞。
    待解決:這裏面說的有狀態是什麼是什麼意思?
  2. 解析/語法分析
    將上面已經轉換好的詞法單元轉換成抽象語法樹(AST)
  3. 代碼生成
    將 AST 轉換成機器能夠識別的指令,讓機器執行 var a = 2 這一系列任務。

編譯完以後,就是執行代碼了,對於 JS 來講,即便是 var a = 2 這行很簡單的代碼,在通過編譯和執行這兩個步驟時,也會涉及到做用域。code

JS 的編譯過程和其餘語言的編譯過程有點不一樣,在編譯的第三步,也就是將抽象語法樹(AST)轉換成機器指令時,當編譯器碰到聲明操做時,如 var a = 2,編譯器會詢問做用域,在當前的做用域內,是否聲明過 a,若是沒有,就會讓做用域在當前做用域內聲明一個 a,不然,忽略該聲明。htm

從上面能夠看出,在 JS 引擎真正執行代碼以前,編譯過程當中,編譯器就會將變量先在做用域內聲明好。token

待解決:上面用的是 var,那麼我用 let 聲明變量的話,他也會幫我提早在做用域內聲明好嗎?作用域

執行

代碼編譯完成後,就是執行步驟了,執行是由 JS 引擎執行,在執行 var a = 2 這行代碼時,也會涉及到做用域:字符串

便於理解,接下來咱們把它們擬人化,每一個人工做上都有本身的工做職責,他們也是同樣,對於做用域來講,他的職責就是管理他這塊區域的變量,這個區域裏,存在哪些變量,變量分別存儲的值是多少他都知道,也是他應該知道的,因此在以前的編譯過程當中,編譯器在聲明 a 以前,首先跑去問了做用域確認這塊區域裏尚未變量 a 後,才讓做用域在這塊區域內聲明瞭變量 a。

而編譯完成後,也就是代碼執行過程,JS 引擎這時看到 var a = 2 後,也會先去問做用域,在當前做用域下,是否已經存在變量 a 了呢?

做用域檢查了下當前的他這塊區域內的變量,回答說,「嗯嗯,已經有了,是剛剛編譯的時候編譯器聲明的」。

JS 引擎:「好嘞,既然有了,那我就不用重複聲明瞭,我就賦個值就好了。」

LHS && RHS
JS 引擎在向做用域詢問變量的時候,查詢的方式還能夠細分爲 LHS 和 RHS,也就是讓做用域查詢這個變量是否存在,仍是讓做用域查詢這個變量的值。

var a = 2 
console.log(a)

代碼如上,仍是以對話的形式

JS 引擎:做用域大哥,幫我看看你那裏有沒有變量 a 啊,我得給他賦個值。(查看變量容器自己是否存在,屬於 LHS 查詢方式)

做用域:找到了!這傢伙在我這

JS 引擎: 謝謝了,再幫我看看 console 變量的值呢,我找找他裏面有沒有 log 這個方法(查詢 console 變量的值,屬於 RHS 查詢方式)

做用域:有的,console 的值給你了,你看看吧

JS 引擎:好嘞,謝謝,我看一下。。有 log 這個方法!做用域,再幫我看下 a 變量的值呢,雖然我剛剛給他賦值了一個 2,可是我仍是得確認一下(查詢變量 a 的值,屬於 RHS 查詢方式)

做用域:嗯嗯,我看了下,他的值仍是2

JS 引擎:好的,謝謝!

做用域鏈

有的時候,做用域是嵌套的

// 最外層爲全局做用域
var a = 2 

function test() {
// 對於 JS 來講,一個函數會生成一個做用域(先不提 let 生成的塊級做用域)
    console.log(a)
}

test()

對於 test 函數來講,他本身內部就有一個做用域 A,最外層有一個全局做用域。

JS 引擎在調用 test 函數的時候,因爲須要打印變量 a,所以向 test 函數內部的做用域 A 求助,問他有沒有看到變量 a,做用域 A 說沒有看到,因而 JS 引擎向做用域 A 外層的做用域(全局做用域)求助,最終在全局做用域裏找到了變量 a。

有時候並不必定當前做用域的外層做用域就是全局做用域,可能還嵌套有其餘做用域,可是查詢方式都是同樣的,當前做用域找不到,就沿着嵌套的做用域往外找,直到找到全局做用域。

若是在全局做用域內也找不到這個變量呢?

那就可能會報錯了,可是具體的報錯信息仍是有區別的:

  1. 查詢變量自己(LHS 查詢方式)

    • 在非嚴格模式下,找到全局做用域都沒有找到這個變量,全局做用域就會直接幫咱們在全局做用域內聲明這個變量,也就是全局變量;
    • 在嚴格模式下,會拋出 referenceError 的錯誤,表明咱們想引用的變量不存在。
  2. 查詢變量的值(RHS 查詢方式)
    若是咱們對變量的值使用方式有錯,好比 console.log 是一個函數,咱們卻想從裏面獲取一個不存在的屬性,如 console.log.test.sss,從一個 undefined 的數據類型上獲取 sss 屬性,就會拋出 typeError 錯誤
相關文章
相關標籤/搜索