前言
JavaScript 中真的滿多坑可以撲倒新手的,一個不小心就踩到地雷,所以這一篇來講講全域變數及區域變數的差異吧~
全域變數與區域變數的差異
以往我們在建立變數的時候都會 var 一個物件。
這時候輸出 item 是沒有什麼問題就是會出現全域媽媽無誤。
那這時候如果改寫這樣子呢?在 function 中建立 item,然後在外層呼叫 item 呢?
1 2 3 4
| function testFu(){ var item = '媽媽' }; console.log(item);
|
這時候可以發現它會說 item is not defined

這兩個小範例基本上淺顯易懂知道問題出在哪裡,也是最基本的全域與區域變數,在 function 下建立的變數通稱為”區域變數”,而 function 之外就是全域變數。
有一個基本認知後來試著挑戰看看這個下面這個題目吧。
1 2 3 4 5
| var item = '全域媽媽'; function name(){ item = '區域兒子'; }; console.log(item);
|
這題答案相信也不難,是全域媽媽,因為 function 並沒有被執行,那如果這樣子呢?
1 2 3 4 5 6
| var item = '全域媽媽'; function name(){ item = '區域兒子'; }; name(); console.log(item);
|
你會發現 item 變成區域兒子了,而這種狀況就是所謂的變數汙染(如果你本來預期結果就是要變成區域兒子的話那就另當別論)。
另外如果你試著打 window 你可以從這底下找到一個叫 item 的變數,而且通常我們在撰寫 JavaScript 時都會盡可能不要去汙染全域 (window),一但累積太多全域變數不但會對效能有影響(每一個全域變數都需要占用記憶體來儲存),甚至有可能影響其他變數(衝名),所以通常有些變數會盡可能建立在 function 中,舉例像這樣子。
1 2 3 4 5 6
| function name(){ var item = '區域媽媽'; item = '區域兒子'; }; name(); console.log(item);
|
這時候你會發現 item 是 not defined。

原因是 item 這個變數只存活於 function 中,只要離開 function 就會被釋放掉,所以如果你把console.log 移入 function 就可以發現區域兒子會跑出來。
1 2 3 4 5 6
| function name(){ var item = '區域媽媽'; item = '區域兒子'; console.log(item); }; name();
|
此時再去打 window 就無法找到一個叫 item 的變數,這個觀念就是區域變數,它只會儲存在特定的環境下只要一到全域 (window),或是其它 function 就會出現 not defined。
另一種全域變數與區域變數狀況
這時候再試試看這個題目,這次輸出的 item 會是誰呢?
1 2 3 4 5 6
| var item = '全域媽媽'; function name(){ var item = '區域兒子'; }; name(); console.log(item);
|
答案是全域媽媽,有些人會開始疑惑為什麼不是區域兒子而是全域媽媽,這跟剛剛這個題目不是一樣嗎?
1 2 3 4 5 6
| var item = '全域媽媽'; function name(){ item = '區域兒子'; }; name(); console.log(item);
|
原因是出在執行 name() 時,裡面的 item,是重新建立一個 item 而非直接取用全域媽媽。
接下來如果改成傳值進去 function 呢?這個答案又會是甚麼
1 2 3 4 5 6
| var item = '全域媽媽'; function name(item){ item = '區域爸爸'; }; name('區域兒子'); console.log(item);
|
答案還是全域媽媽,主要原因是 function 中的 item 被當成了傳值進來的 item 所導致,所以 function 中 item 才因此不影響到全域,我知道這一段很繞口令,多寫幾次就會更清楚原理哩。
接下來挑戰試著進階題目
這些題目是來自六角社群中的一個同學所發表的。
題目一
試著單看程式碼講出各自 console 的答案吧~
(提示: 答案與程式執行順序、hoisting 、全域及區域變數觀念有關)
1 2 3 4 5 6 7 8 9
| function f (x){ console.log('inside f:x='+ x ); x = 5; console.log('inside f:x='+ x + '(after assignment)'); } var x = 3; console.log('before calling f: x=' + x); f(x); console.log('after calling f: x='+ x);
|
題目二
這是學員私底下問我的題目。
第一種狀況
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| var farms = [ { farmer: '卡斯伯', banana: 5000 }, { farmer: '查理', banana: 1000 }, { farmer: '約翰', banana: 3215 } ];
var farmsTotal = farms.length; var bananaTotal = 0;
for (var i = 0;i < farmsTotal;i++) { bananaTotal += farms[i].banana; };
console.log('村子今年的香蕉採收數量:' + bananaTotal);
|
與第二種狀況
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| var farms = [ { farmer: '卡斯伯', banana: 5000 }, { farmer: '查理', banana: 1000 }, { farmer: '約翰', banana: 3215 } ];
var farmsTotal = farms.length; var bananaTotal = 0;
for (var i = 1;i < farmsTotal;i++) { var bananaTotal = 0; bananaTotal += farms[i].banana; };
console.log('村子今年的香蕉採收數量:' + bananaTotal);
|
請問為什麼第一種的香蕉採收數量與第二種會不一樣呢?
(第一種採收數量為 9215,第二種數量為 3215)
進階題目解析
第一題
1 2 3 4 5 6 7 8 9
| function f (x){ console.log('inside f:x='+ x ); x = 5; console.log('inside f:x='+ x + '(after assignment)'); } var x = 3; console.log('before calling f: x=' + x); f(x); console.log('after calling f: x='+ x);
|
首先由於變數及函式有 hoisting 特性,所以變數與函式會變成這樣。
1 2 3 4 5 6 7 8 9 10 11
| var x; function f(x){ console.log('inside f:x='+ x ); x = 5; console.log('inside f:x='+ x + '(after assignment)'); } x = 3; console.log('before calling f: x=' + x); f(x); console.log('after calling f: x='+ x);
|
而 function 還沒有被執行只是被提升,所以可以直接忽略程式會往下跑直到出現 x = 3,此時 X就會被賦予3,下一行立刻就出現 console,所以第一個console是 before calling f: x=3。
接下來會遇到 f(x),第一次載入遇到將會出現 console,而此時的 X 為3,所以第二行 console 是 inside f:x=3。
接下來因為 x 被重新賦值為5,所以 console 答案為 inside f:x= 5 (after assignment)。
最後這一行是重點了,前面有講到 function 中的傳入的值若與全域名子相同則會引 function 的值為重,而 function 中的值並不會影響全域,所以答案是 after calling f: x=3。
1 2 3 4
| before calling f: x=3 inside f:x=3 inside f:x=5(after assignment) after calling f: x=3
|
第二題
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| var farms = [ { farmer: '卡斯伯', banana: 5000 }, { farmer: '查理', banana: 1000 }, { farmer: '約翰', banana: 3215 } ];
var farmsTotal = farms.length; var bananaTotal = 0;
for (var i = 0;i < farmsTotal;i++) { bananaTotal += farms[i].banana; };
console.log('村子今年的香蕉採收數量:' + bananaTotal);
|
第一種的答案是因為全域累加,這個比較沒有甚麼問題,比較有問題是在於第二種類型。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| var farms = [ { farmer: '卡斯伯', banana: 5000 }, { farmer: '查理', banana: 1000 }, { farmer: '約翰', banana: 3215 } ];
var farmsTotal = farms.length; var bananaTotal = 0;
for (var i = 1;i < farmsTotal;i++) { var bananaTotal = 0; bananaTotal += farms[i].banana; };
console.log('村子今年的香蕉採收數量:' + bananaTotal);
|
這題答案之所以是 3215 是因為,迴圈回由上跑至下,直到條件滿足,所以若用圖片來看…

所以每執行一次迴圈就會重新建立一個新的變數叫做 bananaTotal,而迴圈會跑三次,因為 farms.length,所以跑到最後一次就是 { farmer: '約翰', banana: 3215 }啦~
整理這些技術筆記真的很花時間,如果你願意 關閉 Adblock 支持我,我會把這份感謝轉換成更多「踩坑轉避坑」的內容給你!ヽ(・∀・)ノ
Advertisement