什麼是 JavaScript 的作用域

這個過程將由字符組成的代碼分解成對程序有意義的代碼塊,這些代碼塊被稱爲**詞法單元**。

例如 `var foo = 'bar'` 通常會被分解爲這些詞法單元: `var``foo``=``'bar'`

JavaScript 引擎比只有三個步驟的語言的編譯器要複雜的多。例如在語法分析和代碼生成階段有特定的步驟來對運行性能進行優化,包括對冗餘元素進行優化等。

對於 JavaScript 來說,大部分情況下編譯發生在代碼執行的前幾微秒,任何代碼片段在執行前都要進行編譯。因此 JavaScript 編譯器首先對 var foo = 'bar' 進行編譯,然後做好執行它的準備,並且通常馬上就會執行它。

引擎、編譯器、作用域在賦值操作中的配合

對於 var foo = 'bar' 這段代碼,大家很有可能認爲是一句簡單的聲明。而事實上 JavaScript 執行時會將它分成兩個完全不同的聲明。

  1. 編譯器首先將這段代碼分解成詞法單元,然後解析爲樹結構。(在下一步代碼生成時,處理這段代碼的方式會跟預期有所不同)
  2. 遇到 var foo ,編譯器會檢查作用域是否已有同名變量存在。如果有的話編譯器會忽略聲明,繼續編譯。否則它會生成代碼在當前作用域的變量集合中聲明一個新的變量,命名爲 foo
  3. 接下來編譯器會爲引擎生成運行時所需代碼,用來處理 foo = 'bar' 這個賦值操作。
  4. 引擎運行時會首先查詢當前作用域是否存在叫做 foo 的變量。如果有引擎則會使用這個變量,否則會一直向上層作用域查找。
  5. 如果最終找到了 foo 這個變量,就會將 'bar' 賦給它,否則拋出異常。

總結:變量的賦值會執行兩個動作:首先是編譯器在當前作用域中聲明變量(如果變量未被聲明過);接着運行時引擎在作用域查找該變量,能找到就會對它賦值。

LHS 查詢 vs RHS 查詢

引擎執行編譯器生成的代碼時,會通過查找 foo 來判斷是否已經聲明過。查找的過程由作用域來協助。在我們的例子中,引擎爲變量 foo 進行的時 LHS 查詢,還有另一個查找類型叫 RHS 查詢。顧名思義,它們的意思是 Left hand side 和 Right hand side

// 考慮下邊的代碼
console.log(foo)

此例中 foo 的引用就是 RHS 查詢,這裏沒有賦予 foo 任何值,相反的,我們需要查找 foo 的值,才能傳遞給 log 方法。

// 相比之下
foo = 'bar'

這裏對 foo 的查詢則是 LHS 查詢,我們並不關心 foo 當前的值是什麼, 只是想爲這個賦值操作找到目標。

// 再分析下邊的代碼
function foo(a) {
  console.log(a)
}

foo('bar')

這段代碼裏既有 LHS 查詢又有 RHS 查詢

  1. 最後一行 foo(...) 函數的調用需要對 foo 進行 RHS 查詢 → 找到 foo 的值
  2. 入參時存在隱式的 a = 'bar' ,需要對 a 進行 LHS 查詢
  3. console.log(a)a 進行 RHS 查詢
  4. console.log(...) 本身也需要對 console 對象進行 RHS 查詢

作用域的嵌套

我們在文章開始時說過,作用域是根據名稱查找變量的一套規則。實際情況中需要同時顧及幾個作用域。

當一個塊或函數嵌套在另一個塊或函數中時,就發生了作用域的嵌套。因此在當前作用域中沒有查找到目標變量時,會逐層向上查找直到全局作用域。

// 考慮以下代碼
function foo(a) {
  console.log(a + b)
}

var b = 258;

foo(369)

b 進行的 RHS 查詢無法在 foo 內部完成,但可以在上一級的作用域中完成(在此例中是全局作用域)。

LHS,RHS 查詢都會在作用域內逐層查找,直到找到爲止(或到達全局作用域)。

ReferenceError

上一節提到了 LHS,RHS 都會在作用域內逐層查找變量,但如果到達全局作用域仍然沒有找到變量怎麼辦呢?

這時區分 LHS 和 RHS 查詢的意義就體現出來了。

如果 RHS 查詢在所有嵌套的作用域中都沒有找到所需變量,引擎就會拋出 ReferenceError

如果 LHS 查詢在所有嵌套的作用域中都沒有找到所需變量,引擎就會在全局作用域中創建一個具有該名稱的變量,並將其返回給引擎。

注意:ES5 中引入了嚴格模式,與普通模式相比,嚴格模式其中一個不同就是進制自動或隱式的創建全局變量。因此在嚴格模式下 LHS 查詢失敗時不會創建並返回全局變量,引擎同樣會拋出 ReferenceError

總結

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://juejin.cn/post/6924704438800089102