Day15:變數的地盤—作用域(scoop)與提升(Hoisting)
作用域(scoop)簡單來說,就是變數的地盤,在地盤內,變數都有作用,出了地盤,變數就undefined了。
舉個不倫不類的例子,丐幫的幫主洪七公武功再強也沒有辦法命令桃花島黃老邪的弟子梅超風去烤一隻土窯雞來吃。因為根本就不同門不同派。
那要怎麼區分變數的範圍呢?
在ES6之前,切分變數最小的範圍是function為單位,函式裡面的變數只能存活在函式裡面。
在ES6之後,可以使用let、const來定義變數,這種狀況下切分變數最小範圍則為{}大括號區塊。
好險!歐陽克娶不到黃蓉!
因為使用var定義變數man,前面提到作用域最小的範圍是function,所以marriage()會去捉「var man = ‘郭靖’ 」來用,而不會去抓外層的「 var man = ‘歐陽克’」來用。
糟糕!歐陽克娶了黃蓉…郭靖哭哭!
但是如果在marriage()裡面找不到「var man = ‘郭靖’」,就會往外層去找,此時「var man = ‘歐陽克’」就會被捉來用了!郭靖真的會哭!
在外層console.log(man)一直都是歐陽克是因為,切分變數最小的範圍是function,所以「var man = ‘郭靖’ 」的作用域只在marriage()函式裡面,如果marriage()裡面找不到,才會往外層找,一直找到作外層的全域變數,所以console.log(man)才會一直都是’歐陽克’。如果都找不到就會報錯:ReferenceError:man is not defined
情人眼裡出西施!但重點是你要先進入情人的眼裡(作用域)!不管她的眼界是「全域」還是「區域」。
所以我們要記得:
- ES6之前,切分變數最小範圍是function。
- function可以捉外層變數還使用,但是從外層捉不到function內的變數。
提升(Hoisting)
繼續來看看郭靖有沒有辦法跟黃蓉有情人終成眷屬!
我們在「var man = ‘郭靖’」前面加了一個console.log(man),雖然「var man = ‘郭靖’」宣告在後面,但是console.log(man)並沒有去外層抓「var man = ‘歐陽克’」來用。
那是因為console.log(man)和「var man = ‘郭靖’」在同一個作用域,只要確認後面 man這個變數有宣告,那就會宣告變數這件事提到上面來,然後先給它一個undefined的值。
如果是使用 let來宣告「let man = ‘郭靖’」,在還沒宣告前,使用console.log(man)來查詢,就會報錯:Uncaught ReferenceError: Cannot access ‘man’ before initialization。因為let和const不允許在宣告卻沒有初始化的狀況下使用,從宣告到初始化這中間的時間差稱為「暫時死區」(Temporal Dead Zone)(TDZ)。
所以在這裡提一下重點:
- let/const 是使用區塊作用域,以一對大括號{}為單位;var 是使用函式(function)作用域
- 在 let/const 宣告之前就存取對應的變數與常數,會拋出
ReferenceError
錯誤;但在 var 宣告之前就存取對應的變數,則會得到undefined
綜上所述,最好在作用域(scope)的一開始就宣告好所有的變數再使用,避免發生慘案。
函式的提升
前面提到的是用var設參數的提升,還有一種是「函式的提升」。
而透過「函式宣告」的方式建立的函式,也可以在前面叫用,後面再宣告函式。
而「函式表達式」就沒這樣的待遇了,提前叫用只會出現:TypeError。
變數的提升只有宣告被提升,但是沒有初始化。但是函式的提升則是函式的內容整個被提升。