主要介紹關鍵字var/let/function在聲明變數和函數的時候,生命周期和作用域的細微區別,以及和window對象的關係 ...
為什麼要將這些內容放在一起,因為他們都跟初始化有關係,我們慢慢說吧。
我們在代碼中,都會聲明變數、函數和對象,然後由瀏覽器解釋器(下麵簡稱瀏覽器)執行;
我們還說過,變數和對象的記憶體結構;
那麼,是什麼時候,我們聲明的變數和對象,被瀏覽器分配記憶體了呢?
我們使用不同的聲明方式,瀏覽器分配記憶體的順序是一樣的嗎?
window對象
我們先來看一個對象window
,這個對象代表什麼呢?
顧名思義,它代表一個視窗,一個瀏覽器視窗;
當我們用瀏覽器打開一個頁面的時候,這個window
對象就被初始化創建了;
這個window
對象,出生就自帶很多方法,包括JS內置函數和瀏覽器視窗函數;
所以下麵的代碼才能夠在什麼都沒有的情況下,輸出window
對象:
可以看到,alert()
也是window
對象的一個方法,其實console.log()
也是;
也就是說,上面的代碼,其實是window.console.log()
,只是省略了window
而已;
window
對象是一個非常值得細緻研究的對象,但是這裡就不贅述了,我們還有很多內容要說;
總之記住一點,window
對象代表的是整個瀏覽器視窗,它的範圍是最大的,它是最早被創建的對象;
變數的作用域
變數的作用域,其實就是指它的可見性,分為全局作用域和局部作用域:
-
局部作用域
先來說明一下代碼塊的概念;
凡是用大括弧
{}
括起來的都是代碼塊,包括函數體、迴圈體,以及大括弧本身等等;在代碼塊中聲明的變數,它就只在代碼塊中可見,代碼塊外面是無法訪問到的;
- 因為代碼塊只會在執行時用到,所以代碼塊中的變數的生命周期,也就是執行代碼塊的時候才會創建,而執行結束以後就會被垃圾回收;比如調用函數時,變數被創建,調用結束,變數就會被垃圾回收;
- 因為大括弧可以嵌套,所以外層代碼塊的變數,內層代碼塊可以訪問到;但是內層代碼塊的變數,外層代碼塊訪問不到;
- 因為內外層代碼塊的可見性不一致,內層代碼塊可能創建和外層代碼塊同名的變數,此時內層代碼塊使用變數時,採用就近原則,也即使用離內層代碼塊最近的變數;
具體看下麵的代碼吧:
如上圖所示,每個代碼塊中的變數a的作用域,都用框表示出來了;
值得一提的是,因為函數的特殊性,函數的局部性比其他代碼塊更強,這在關鍵字
var
那裡體現的最明顯。 -
全局作用域
那麼全局作用域的概念就很清晰了,不在任何
{}
代碼塊中的,直接在script標簽中聲明的變數,都具有全局作用域;那麼它的生命周期,也就是打開一個瀏覽器視窗的時候被創建,關閉一個瀏覽器視窗時才會被垃圾回收;
關鍵字var和let
還記得我們聲明變數的三種方式嗎(因為const和let的行為一致,這裡就不討論const了)?
我們先討論全局作用域的變數聲明的區別:
-
不使用關鍵字
可以看到,這種聲明方式,變數會成為
window
對象的屬性,也就是隨著window
對象一起初始化了;但是圖二告訴我們,它似乎不能被訪問,即此時記憶體並沒有變數a,所以輸出了
a is not defined
,這是為什麼?我也不知道;所以非常不推薦這種聲明方式;
-
使用關鍵字
var
可以看到,這種聲明方式,變數會成為
window
對象的屬性,也就是隨著window
對象一起初始化,開闢了一塊記憶體空間用來存儲變數a;但是此時變數a還沒有被賦值,所以輸出了
undefined
,並不會報錯;這也是為什麼不推薦使用這種方式來聲明變數,因為它的全局作用域太大,甚至在被賦值前,就可以訪問而不報錯;
-
使用關鍵字
let
可以看到,這種聲明方式,變數不會成為
window
對象的屬性;但是此時變數a,實際上已經開闢了記憶體區域了,但是還沒有初始化,所以才會報錯
Cannot access 'a' before initialization
;這是一種符號大多數編程語言的關於變數聲明的處理方式,也是ES6新推出的,用來避免
var
的聲明方式,也是被推薦的聲明方式;
再來說一下局部作用域的一些特殊情況,主要是關鍵字var
:
先看下麵代碼的輸出結果:
可以看到,哪怕是在代碼塊中,使用var
和無關鍵字的聲明方式,依然還是會成為window
對象的屬性,具有全局作用域;而let
聲明的變數,就具有正常的局部作用域。
再看下麵代碼的輸出結果:
可以看到,在函數體的代碼塊中,三種聲明方式具有相同的局部作用域,函數體外部都訪問不到;
這說明,var
雖然在其他代碼塊中不具有局部作用域,但是在函數體中卻具有局部作用域,可以稱之為函數作用域;
總結:
- 除函數體外,在任何地方使用
var
或者無關鍵字的聲明方式,變數都是作為window
對象的屬性,具有全局作用域; - 在函數體中,無論何種聲明方式,都是局部變數,具有局部作用域;
let
以及const
的作用域表現,是最正常的,符合大多數編程語言對變數作用域的定義;- 推薦使用
let
以及const
來聲明變數,酌情考慮使用var
來聲明變數,最好不要使用無關鍵字的方式來聲明變數;
關鍵字function
聲明一個函數的時候,我們可以使用關鍵字function
進行聲明;
我們先看下麵代碼的輸出結果:
按照代碼從上至下的順序執行,我們可以看到,儘管函數aaa
的聲明代碼,是在列印和調用aaa
的代碼之後,依然列印和調用成功了;
說明,當瀏覽器執行代碼的時候,函數聲明是先執行的,並且它成為了window
對象的一個方法;
註意:
- 只有使用
function
關鍵字才會這樣,使用函數表達式和箭頭函數去聲明函數的時候,因為有個賦值給變數的操作,所以它是一個執行語句,瀏覽器會按照順序去執行; - 只有直接在script標簽中聲明才會這樣,在其他任何代碼塊即在
{}
中進行聲明,因為代碼塊也是執行語句,瀏覽器會按照順序去執行;