[JS] 變數(Variables)的宣告與作用域(Scope)
tags: Javascript, variable
JavaScript 的變數可以分為全域與區域變數。簡單來說可以用是否在函式內宣告來區分:
- 函式以內宣告的為「區域」
- 函式以外宣告的為「全域」
變數 (variable) 與屬性 (property)
全域環境和全域物件 (window 物件)
執行 JavaScript 程式碼時,JaveScript Engine 會產生基礎的執行環境 (Base execution context) 或稱為 「全域執行環境(Global Execution Context)」 。
全域執行環境在一開始的創建階段 (Creation Phase) 就會替我們創造兩個東西:
- 全域物件 (Global Object)
this特殊變數
JavaScript 在不同的執行環境下會有不同的全域物件:
- 在瀏覽器中的全域物件就是 window 物件
- 在 Node.js 環境中的全域物件就是 global 物件
在瀏覽器執行環境中,全域等級的全域物件 window 就等同
this。
(圖片來源)
全域 (Global)
- 全域 (Global) 代表我們可以在執行環境內的所有地方取用它
- 當你的程式碼或變數不是在函數裡創造的,就會隸屬 (attached) 於全域物件
var a = 10;
console.log(window.a); // 10 --> 全域物件 window 底下有 a
console.log(this.a); // 10 --> this 等同全域物件 window
沒有宣告
- 用
var宣告:會是一個變數,同時也是 window 物件的一個屬性; - 沒有宣告:只會是 window 物件的一個「全域屬性」(即使寫在函式內也是):
function printName() {
name = 'Jay'; // 即使在函式內,沒有宣告的變數會變成「全 域屬性」
console.log(name);
}
printName(); // Jay
console.log(name); // Jay --> 函式外也取用得到
setTimeout(() => {
data = []; // 沒有宣告變數
updateData();
console.log(data); // 最後印出 data 的值為 `[1]
}, 0);
function updateData() {
data.push(1);
}
// [1]
setTimeout(() => {
var data = []; // 使用 var 宣告變數
updateData();
console.log(data); // 得到錯誤
}, 0);
function updateData() {
data.push(1);
}
// Uncaught ReferenceError: data is not defined
屬性可以被刪除,變數不行
var a = 'Jay'; // --> 全域變數
d = 'Tom'; // --> 全域屬性
delete window.a; // false --> 變數不能被刪除
delete window.d; // true --> 屬性可以被刪除
console.log(a); // Jay
console.log(d); // Uncaught ReferenceError: d is not defined
-
使用
var宣告,記憶體會先準備一個空間給它並賦予預設值undefined,因此在賦值變數前取用它並不會出錯。 -
但如果是沒有宣告的變數就會跳錯誤:
console.log(a); // undefined
var a = 'Jay';console.log(a); // Uncaught ReferenceError: a is not defined
a = 'Jay';
important
建議無論如何都一定要宣告變數。
宣告變數 var, let, const 與 window 的關係
- 使用
var宣告的變數會出現在 window 屬性下 - 使用
let、const的不會:var a = 'Jay';
let b = 'Tom';
const c = 'Bob';
console.log(window.a); // Jay
console.log(window.b); // undefined
console.log(window.c); // undefined
總結各個差異
var a = 'Jay'; // 是變數,同時也是 window 物件的一個屬性
let b = 'Tom'; // 是變數,但不是 window 物件的一個屬性
const c = 'Bob'; // 是變數,但不是 window 物件的一個屬性
d = 'Amy'; // 不是變數,但是是 window 物件的一個屬性
// 在全域物件 window 查看
console.log(window.a); // Jay
console.log(window.b); // undefined
console.log(window.c); // undefined
console.log(window.d); // Amy
// 變數 vs 屬性
delete window.a; // false --> 變數不能被刪除
delete window.d; // true --> 屬性可以被刪除
console.log(window.a); // Jay
console.log(window.d); // undefined
console.log(d); // Uncaught ReferenceError: d is not defined
變數的作用域(Scope)
除了沒有宣告的變數會被視為是全域屬性之外,其他無論是使用 var、let、const 宣告的變數差異主要在於變數的「生存範圍」也就是變數的「作用域」。
全域變數與區域變數
內層可以存取外層,外層不能存取內層
var a = 'Tom'; // 全域變數
function fn1() {
var b = 'Jack'; // 區域變數
console.log(a, b);
function fn2() {
console.log(a, b); // 區域環境,可以存取外層作用域的變數
debugger;
}
fn2();
}
fn1();
上述例子我們可以看到:
- 全域作用域中有 a 變數
var a = 'Tom' - fn1() 作用域有 b 變數
var b = 'Jack',沒有 a 變數因此向上層 global 查找到 a 變數 - fn2() 作用域中沒有 a 跟 b 變數,因此會往上一層 fn() 作用域查找到 b 變數、再往上一層 global 作用域查找到 a 變數
使用 debugger 與法可以在 console 查看到不同 scope 的變數:

每個函示
var a = 'Tom';
function fn1() {
var b = 'Jack';
}
function fn2() {
var c = 'Amy';
console.log(b);
}
fn1();
fn2();
var a = 'Tom';
function fn1() {
b = 'Jack';
}
function fn2() {
var c = 'Amy';
console.log(b);
}
fn1();
fn2();
var, let, const 作用域的差異
var是屬於函式作用域let、const是屬於區塊作用域 (script, block)- 實際開發上不推薦使用全域變數,會有覆蓋問題(
var可以重複宣告)
範例一:let 與 var 結果相同
for (let index = 0; index < 10; index++) {
console.log(index);
}
for (var index = 0; index < 10; index++) {
console.log(index);
}
// Output: 依序印出 0~10
範例二:let 與 var 結果不同
for (let index = 0; index < 10; index++) {}
console.log(index); // Output: index is not defined; index 是區域變數
for (var index = 0; index < 10; index++) {}
console.log(index); // Output: 10; index 是全域變數
範例三:let 與 var 結果不同
for (var index = 0; index < 10; index++) {
setTimeout(() => {
console.log(index);
}, 10);
}
// Output: 10
for (let index = 0; index < 10; index++) {
setTimeout(() => {
console.log(index);
}, 10);
}
// Output: 依序印出 0~10