JavaScript 作用域
-
全域變數
- 全域變數在整個程式中都可以被存取與修改
var name = "elaine"; function showName() { console.log(name); // elaine } showName(); console.log(name); // elaine
-
區域變數
- 每次執行函式時,就會建立區域變數再予以摧毀,而且函式之外的所有程式碼都不能存取這個變數
function showName() { var name = "elaine"; console.log(name); // elaine } showName(); console.log(name); // ReferenceError: name is not defined
-
function 由內向外找
var name = "elaine"; function showName() { var name = "amy"; console.log(name); // amy } showName(); console.log(name); // elaine
宣告提升 Hosting
-
var
有宣告提升console.log(a); // undefined var a = 123;
相當於
var a; console.log(a); // undefined a = 123;
-
let/const
其實也有宣告提升,但沒有初始化為 undefined,而且在賦值之前試圖取值會發生錯誤 (Temporal Dead Zone)-
let
console.log(a); // ReferenceError: Cannot access 'a' before initialization let a = 123;
-
const
console.log(a); // ReferenceError: Cannot access 'a' before initialization const a = 123;
-
函式作用域
-
var
可用範圍以function
為界,function
外讀不到值var a = 10; function hi() { var b = 20; } console.log(a); // 10 console.log(b); // ReferenceError: b is not defined
-
如果使用區塊語句像
if、else、for、while
等等區塊語句時,宣告的區域變數仍然可在整段程式碼做存取var a = 10; if (true) { var b = 20; // 全域宣告 } console.log(a); // 10 console.log(b); // 20
-
-
let/const
可用範圍以block
為界,block
外讀不到值-
let
var a = 10; if (true) { let b = 20; } console.log(a); // 10 console.log(b); // ReferenceError: b is not defined
-
const
var a = 10; if (true) { const b = 20; } console.log(a); // 10 console.log(b); // ReferenceError: b is not defined
-
-
最大的差別
-
使用
var
setTimeout 在存取 i 的時候,都存取到相同作用域的 i (此時
i = 3
)for (var i = 0; i < 3; i++) { console.log(i); // 0 1 2 setTimeout(function() { console.log("這執行第" + i + "次"); // 這執行第3次 這執行第3次 這執行第3次 }, 10); }
-
使用
let
因為 let 會產生新的作用域,所以 setTimeout 在存取 i 的時候,是三個不同的作用域 (第一個作用域
i = 0
、第二個作用域i = 1
,第三個作用域i = 2
)for (let i = 0; i < 3; i++) { console.log(i); // 0 1 2 setTimeout(function() { console.log("這執行第" + i + "次"); // 這執行第0次 這執行第1次 這執行第2次 }, 10); }
-
非得要用
var
來完成的話,只好包成一個IIFE
(立即呼叫的函式),來產生新的作用域for (var i = 0; i < 3; i++) { console.log(i); // 0 1 2 (function(j) { setTimeout(function() { console.log("這執行第" + j + "次"); // 這執行第0次 這執行第1次 這執行第2次 }, 10); })(i); }
-
重複宣告
-
var
允許重複宣告var a = 123; var a = 456; console.log(a); // 456
-
let/const
不允許重複宣告let a = 123; let a = 456; // SyntaxError: Identifier 'a' has already been declared console.log(a);
const a = 123; const a = 456; // SyntaxError: Identifier 'a' has already been declared console.log(a);
const
-
const
在宣告的時候一定要賦予值const a; // SyntaxError: Missing initializer in const declaration
-
const
一旦被賦予值,就不可以再更改const a = 1; a = 2; // TypeError: Assignment to constant variable
-
但是物件和陣列,記憶體位置不改變,值可以變動
const a = ["第一個 value"]; a[0] = "啦啦啦"; console.log(a); // ["啦啦啦"] a = 123; // TypeError: Assignment to constant variable
-
結論
-
不要再用
var
來宣告變數,改用let
與const
,而且優先使用const
,除非需要再指定值才用let
- Google 5.1.1, Airbnb 2.1/2.2
-
不要使用逗號
,
在同一行來宣告多個變數或常數,例如let a = 1, b = 2
是不必要的,應該是一行一個宣告- Google 5.1.2, Airbnb 13.2
-
並不是在區塊中或函式中區域的最上面來宣告變數/常數,而是在合理的位置,在變數/常數首次被使用時的上面一行來宣告變數
- Google 5.1.3, Airbnb 13.4