在第一次接觸 JS 時,每個人大概都會先認識變數宣告,再來會知道宣告後要怎麼呼叫,寫在呼叫前後會是長什麼樣子等等……,就像當年的映入眼簾到想要掀開那層神秘面紗,如今使用變數,發覺自己對它的背後原理不甚瞭解,縱使現在列隊待消化的知識量已經看不到盡頭,在經過一番整理後,還是決定就從變數開始寫了!
了解變數前,先知道語法作用域(Lexical scope)
JavaScript 屬於直譯式語言,意指是用直譯器把原始碼變成代碼,最後可在電腦運行,而電腦在解析的時候就會確定語法的作用範圍,稱為「靜態作用域」。作用範圍可大可小,決定於宣告時的關鍵字以及是否為函式,若是函式,因為函式必須要在呼叫時才會執行,那時才會決定作用域範圍,所以為「動態作用域」。
函式的範圍鏈(Scope Chain)
當函式自己的內容沒有宣告變數,那在執行當下它就會向外查找。若是有兩個函式,它們都沒有被宣告的變數的話,兩個函式都會往各自的外層查找,與它們的執行範圍和呼叫順序無關。如果遇到函式 1 包著函式 2 的狀況,若函式 2 自己沒有宣告,則會先往函式 1 尋找。
當外層函式和內層函式都沒有宣告變數時,就會各自往外查找。外層函式會找到 John,內層函式在找了兩層後,最終也會找到 John。
宣告變數
1 | var |
像是一個無拘無束的傭兵,可以應付各種戰況也可以造成各種混亂,如果不是在函數內被宣告,就會在全域物件中留下痕跡,成為全域物件下的一個屬性,所以稱為全域變數,嚴格來說,這個變數會是全域物件下的屬性,一旦函式內找不到變數,便會在全域物件裡抓到這個屬性,而這時並非會是我們想要的結果,最慘的就是遭到污染和衝突,而且變數是無法被刪除的,造成修復程式碼的困難。
1 | 沒有經過宣告的變數也會變成全域物件裡的屬性。 |
1 | let |
像是有加入公會守規矩的戰士,除了像 var 可以靈活應付戰況外,也知道不該參與的時候就不該露一手,所以被 let 宣告的變數,不會進到全域物件裡,也會安分守己地待在應該處理的地方,Good boy!
1 | const |
同 let,也是有加入公會的一個守規矩的賢者,因為被 const 宣告的不是變數而是常數,意指一旦被 const 宣告,它的資料就不能被重新賦予,通常會用來宣告不會被變動的資料型別或數學公式,但若是宣告的資料型別為陣列或物件,裡面的內容是可以被修改的。
三巨頭的作用域
1 | var |
被 var 宣告的變數為全域變數,但函式能關住 var ,只要是函式內被 var 宣告的變數,只能被該函式內使用,就不會變成全域物件的屬性,函式外部也無法正確取得該值。因為 var 宣告的變數只能活在函式內,所以它的作用域被稱為函式作用域(function scope)。
1 | // 因為只有函式可以關住 var,所以若是使用 for 迴圈, |
let & const:而被這兩位宣告的變數和常數,所有的 { } 都可以將它們關住,例如:
1 | 函式:function fn() {…} |
因為 let 和 const 宣告的變數和常數只能活在 { } 內,所以它們的作用域被稱為區塊作用域(block scope)。
函式的作用域
先定義一個函數
1 | function myName() { |
呼叫函式
1 | function myName() { |
透過呼叫函式的名字,就可以把該函式內的最終內容呈現出來,但如果 log 在函式外部:
1 | function myName() { |
如此,則會呼叫失敗,並且會顯示 ReferenceError: variName is not defined,因為函式裡 let 並不會活到外部讓 console.log 讀取到,var 和 const 也是。
提升(Hoisting)
提升通常用來解釋 JavaScript 變數在記憶體中的運作形式,在瀏覽器執行時,會區分為兩個階段:
1 | // 創造階段(Creation):宣告變數的那一瞬間記憶體空間就被創造了,無值 |
1 | // 執行階段(execution):當這個變數被賦予值,就可以進這個記憶體空間了 |
函式陳述式會在創造階段就會優先載入,函式內容會在創造階段都先準備好了,所以函式陳述式的呼叫不管放哪都可以。
1 | function myName() { |
1 | var myName = function () { |
若是函式陳述式和函式表達式來比拼,順序會是以下:
函式陳述式會優先創造,但如果執行順序在函式表達式前,就會被函式表達式覆蓋過去了
那 let 和 const 有沒有提升呢?
答案是有的,let 和 const 在宣告時還是會進入創造階段,但從創造到執行的時候會出現暫時性死區(TDZ),而這個區域是無法呼叫變數的。像 var 在這個區域時會出現 undefined,而 let 和 const 則會直接出現不能取得未初始化變數的錯誤。
小結
初次撰寫類技術文章,就獻給了 JavaScript 入門級知識點 — 變數,如果再深入研究變數和它延伸的知識點,也會是非常威武的規模。如今只是將自己的流水帳筆記展開成一篇分享筆記,也希望自己能夠將敘述文字再用圖片甚至動畫來鞏固自己的觀念!