JavaScript全域變數與區域變數的差別

前言

JavaScript 中真的滿多坑可以撲倒新手的,一個不小心就踩到地雷,所以這一篇來講講全域變數及區域變數的差異吧~

全域變數與區域變數的差異

以往我們在建立變數的時候都會 var 一個物件。

1
var item = '全域媽媽';

這時候輸出 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);

這時候你會發現 itemnot 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 所導致,所以 functionitem 才因此不影響到全域,我知道這一段很繞口令,多寫幾次就會更清楚原理哩。

接下來挑戰試著進階題目

這些題目是來自六角社群中的一個同學所發表的。

題目一

試著單看程式碼講出各自 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++) {
//var bananaTotal = 0;
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 }啦~

0%