原文: https://2ality.com/2019/07/global-scope.html翻譯: 劉小夕
在這篇博文中,我們將研究 JAVAScript 的全局變量是如何工作的。如: scripts的范圍,所謂的全局對象等等。
1.作用域
變量的詞法作用域(簡稱:作用域)是可以訪問它的程序的區域。 JavaScript 的作用域是靜態的(它們在運行時不會改變)并且它們可以嵌套 - 例如:
function func () { // (A) const foo = 1 ; if ( true ) { // (B) const bar = 2 ; } }
if 語句引入的作用域(行B)嵌套在函數 func()(行A)的作用域內。
在示例中, func 是 if 的外層作用域。
2.詞法作用域
在 JavaScript 語言規范中,作用域是通過詞法作用域“實現”的。它們由兩部分組成:
- 將變量名映射到變量值的環境記錄(可以想象成是字典)。這是 JavaScript 存儲變量的地方。環境記錄中的一個 key-value 條目稱為綁定。
- 對外部環境的引用 - 表示當前環境所代表的作用域的外部作用域的環境。
因此,嵌套作用域樹可以由嵌套環境樹表示。
3.全局對象
全局對象是一個對象,其屬性是全局變量。
- 無處不在: globalThis
- 全局對象的其他名稱取決于平臺和語言構造:
- window:是引用全局對象的經典方式,但它只適用于普通瀏覽器環境; 不在 Node.js 和 WebWorkers 中。
- self:在瀏覽器中隨處可用,包括 WebWorkers。但是 Node.js 不支持它。
- global:僅在 Node.js 中可用。
全局對象包含所有內置全局變量。
4.全局環境
全局作用域是“最外層”作用域 - 它沒有外部作用域。它的環境是全局環境。每個環境都通過由外部引用鏈接的一系列環境與全局環境相關聯。全局環境的外部引用為 null。
全局環境結合了兩個環境記錄:
- 對象式環境記錄,其作用類似于普通環境記錄,但保持其綁定與對象同步。在這種情況下,對象是全局對象。
- 聲明式環境記錄。
下圖顯示了這些數據結構。
接下來的兩個小節將解釋如何組合對象記錄和聲明式記錄。
4.1創建變量
為了創建一個真正全局的變量,你必須處于全局作用域內 - 必須要在 scripts 的頂層:
- 頂級 const, let 和 class 在聲明式環境記錄中創建綁定。
- 頂級 var 和函數聲明在對象式環境記錄中創建綁定。
< script > const one = 1 ; var two = 2 ; </ script > < script > // All scripts share the same top-level scope: console . log ( one ); // 1 console . log ( two ); // 2 // Not all declarations create properties of the global object: console . log ( window . one ); // undefined console . log ( window . two ); // 2 </ script >
此外,全局對象包含所有內置全局變量,并通過對象式記錄將它們給全局環境。
4.2讀取/設置變量
當我們獲取或設置變量并且兩個環境記錄都具有該變量的綁定時,聲明式環境記錄將獲勝:
< script > let foo = 1 ; // 聲明式環境記錄 globalThis . foo = 2 ; // 對象式環境記錄 console . log ( foo ); // 1 (聲明式記錄獲勝) console . log ( globalThis . foo ); // 2 </ script >
5.模塊環境
每個模塊都有自己的環境,它存儲所有頂級聲明 - 包括導入。模塊環境的外部環境是全局環境。
結論:為什么JavaScript既有全局變量又有全局對象?
通常認為將全局變量掛載到全局對象上是錯誤的。因此,較新的構造(如 const, let 和 classes)會創建正常的全局變量,不會成為全局對象的屬性。(在 script作用域內時)。
值得慶幸的是,大多數用現代 JavaScript 編寫的代碼都存在于 ECMAScript 模塊和 CommonJS模塊中,每個模塊都有自己的作用域。