在這篇博客文章中喇聊,我們將研究JavaScript的全局變量是如何工作的恍风。一些有趣的現(xiàn)象是: 腳本的作用域、所謂的全局對象 等等誓篱。
作用域
變量的詞法作用域(lexical scope)(簡稱:作用域(scope))是程序中可以訪問它的區(qū)域朋贬。JavaScript的作用域是靜態(tài)的(它們在運行時不會改變)并且它們可以嵌套 - 例如:
function func() { // (A)
const foo = 1;
if (true) { // (B)
const bar = 2;
}
}
if
語句(B行)引入的作用域嵌套在函數(shù)func()
(A行)的作用域內(nèi)。
在這里例子里窜骄,func
就是if
的外部作用域锦募。
詞法環(huán)境
在JavaScript的語言規(guī)范中,作用域是通過詞法環(huán)境來"實現(xiàn)"的邻遏。它們由兩部分組成:
- 將變量名映射到變量值的環(huán)境記錄(聯(lián)想字典)糠亩。這是JavaScript存儲變量的地方。環(huán)境記錄的鍵值對入口被叫做綁定(binding)准验。
- 對外部環(huán)境的引用——表示當前環(huán)境所代表作用域的外部作用域的環(huán)境赎线。
因此,嵌套作用域樹由一個嵌套環(huán)境樹表示糊饱,該樹由外部引用鏈接垂寥。
全局對象
全局對象是一個對象,他的屬性是全局變量。等下就會介紹他是怎么去和環(huán)境樹相適應滞项。它們有幾個不同的名稱:
- 任意地方(提案功能):
globalThis
- 全局對象的其他名稱取決于平臺和語言結構:
- window:是引用全局對象的經(jīng)典方式狭归。但它只在普通瀏覽器代碼中工作;不存在Node.js中,也不存在Web Workers中(與普通瀏覽器代碼并發(fā)運行的進程)文判。
- self:瀏覽器的任何地方都是可用的唉铜,包括Web Workers。但是Node.js中不支持律杠。
- global:只在Node.js中被支持潭流。
全局對象包含所有內(nèi)置的全局變量。
全局環(huán)境
全局作用域是“最外層的”作用域——它沒有外部作用域柜去。它的環(huán)境是全局環(huán)境灰嫉。每個環(huán)境都通過由外部引用鏈接的環(huán)境鏈與全局環(huán)境聯(lián)系。全局環(huán)境的外部引用是null
嗓奢。
全局環(huán)境結合了兩個環(huán)境記錄:
- 一個對象環(huán)境記錄 的作用就像一個普通的環(huán)境記錄讼撒,但會保持他的綁定和對象同步。在這種情況下股耽,對象是全局對象根盒。
- 一個普通的(聲明性的)環(huán)境記錄。
下圖顯示了這些數(shù)據(jù)結構物蝙。過一會就會解釋腳本作用域和模塊環(huán)境炎滞。
接下來的兩個小節(jié)將解釋對象記錄和聲明性記錄是如何組合的。
創(chuàng)建變量
為了創(chuàng)建一個真正意義上的全局變量诬乞,你必須在全局作用域中 -- 只有一種情況册赛,那就是在腳本的頂級。
- 頂級
const
震嫉,let
和class
在聲明性記錄中創(chuàng)建綁定森瘪。 - 頂級
var
和function
聲明在對象記錄中創(chuàng)建綁定。
<script>
const one = 1;
var two = 2;
</script>
<script>
// 所有腳本共享相同的頂級作用域:
console.log(one); // 1
console.log(two); // 2
// 并非所有聲明都創(chuàng)建全局對象的屬性:
console.log(window.one); // undefined
console.log(window.two); // 2
</script>
此外票堵,全局對象包含所有內(nèi)置的全局變量扼睬,并通過對象記錄將它們貢獻給全局環(huán)境。
獲取或設置變量
當我們獲取或者設置一個變量悴势,并且兩個的環(huán)境記錄(對象記錄和聲明記錄)都有對變量的綁定窗宇,然后聲明性記錄會具有優(yōu)勢:
<script>
let foo = 1; // 聲明性的環(huán)境記錄
globalThis.foo = 2; // 對象環(huán)境記錄
console.log(foo); // 1 (聲明性的環(huán)境記錄具有優(yōu)勢)
console.log(globalThis.foo); // 2
</script>
模塊環(huán)境
每個模塊有屬于他自己的環(huán)境。他存儲所有的頂級聲明瞳浦,也包含導入(import)担映。模塊環(huán)境的外部環(huán)境是全局環(huán)境。
為什么JavaScript既有正常的全局變量又有全局對象叫潦?
全局對象通常會被認為是一個錯誤蝇完。因此,較新的構造(如const、let和class)創(chuàng)建正常的全局變量(在腳本作用域中)短蜕。
值得慶幸的是氢架,大多數(shù)用現(xiàn)代JavaScript編寫的代碼都存在于ECMAScript模塊和CommonJS模塊中。每個模塊都有自己的作用域朋魔,這就是為什么控制全局變量的規(guī)則很少對基于模塊的代碼有影響岖研。
進一步閱讀
- ECMAScript規(guī)范中的“詞法環(huán)境”提供了對環(huán)境的總體概述。
- “全局環(huán)境記錄”涵蓋了全局環(huán)境警检。
總結
譯者個人總結
環(huán)境記錄包含了對象環(huán)境記錄(object record)和聲明性環(huán)境記錄(declarative record)孙援。
let
,const
, class
的這些聲明都是在聲明性記錄(declarative record)中創(chuàng)建的綁定,
var
,function
這種聲明都是在對象記錄(object record)中創(chuàng)建的綁定。
全局對象之所以可以訪問var
這些聲明扇雕,是因為他會通過對象記錄來獲取拓售。
對于js來說,全局對象通常被認為是一個錯誤镶奉,所以新的聲明方法都是存儲在聲明性記錄里础淤。