JavaScript變量的作用域介紹
JavaScript 變量的作用域決定了變量在代碼中的可訪問性。
var 是 JavaScript 中最早用于聲明變量的關鍵字,它函數作用域或全局作用域。
let 關鍵字,具有塊級作用域、全局作用域。
const關鍵字,具有塊級作用域、全局作用域。
var、let 和 const 關鍵字對比表
特性 | var | let | const |
作用域 | 函數作用域或全局作用域 | 塊級作用域、全局作用域 | 塊級作用域、全局作用域 |
變量提升 | 提升到作用域頂部,初始化為?undefined | 聲明前訪問會報錯,因存在“暫時性死區”(Temporal Dead Zone) | 聲明前訪問會報錯,因存在“暫時性死區”(Temporal Dead Zone) |
重復聲明 | 合法,后續聲明覆蓋前一個值 | 不合法,拋出?SyntaxError | 不合法,拋出?SyntaxError |
是否可變 | 可變 | 可變 | 不可變(引用不可變,但內容可變) |
是否成為全局對象屬性 | 是(在全局上下文中) | 否 | 否 |
下面展開介紹
1.?作用域類型
全局作用域(Global Scope)
- 定義:在函數外聲明的變量。
- 特點:
- 任何地方都可訪問,包括函數內部。過度使用全局變量可能導致命名沖突和代碼難以維護。
- 在瀏覽器環境中,全局變量通常與 window 對象關聯。
- 瀏覽器環境中,var?聲明的全局變量會成為?window?對象(在瀏覽器環境中)的屬性,let?和?const?不會。
- 注意
避免隱式全局變量,始終顯式聲明變量。
在非嚴格模式下,未使用 var、let 或 const 關鍵字聲明的變量會被隱式提升為全局變量。
在嚴格模式下,未聲明的變量會導致 ReferenceError,因此不會創建隱式全局變量。
var:在全局上下文中聲明的變量會成為全局對象的屬性,具有全局作用域。
let 和 const:在全局上下文中聲明的變量是全局變量,但不會成為全局對象的屬性;?let 或 const 關鍵字還可以聲明塊級作用域(見后面)。
- 全局變量的創建方式:?
1)隱式全局變量:
myGlobalVar = "Hello, World!"; // 隱式全局變量
console.log(myGlobalVar); // 輸出 "Hello, World!"
console.log(window.myGlobalVar); // 輸出 "Hello, World!",因為它是 window 的屬性
2)顯式全局變量:
使用window對象可以明確地創建全局變量。
window.myExplicitGlobalVar = "Hello, again!"; // 顯式全局變量
console.log(window.myExplicitGlobalVar); // 輸出 "Hello, again!"
console.log(myExplicitGlobalVar); // 輸出 "Hello, again!",直接訪問也可以。
3) 使用 var、let 或 const 關鍵字聲明全局變量
//使用 var 聲明全局變量
var globalVar = "Hello";
console.log(globalVar); // 輸出: Hello
console.log(window.globalVar); // 輸出: Hello,因為它是 window 對象的屬性//使用 let 聲明全局變量
let globalLetVar = "Hello";
console.log(globalLetVar); // 輸出: Hello
console.log(window.globalLetVar); // 輸出: undefined(不是 window 的屬性)//使用 const 聲明全局變量
const globalConstVar = "Hello";
console.log(globalConstVar); // 輸出: Hello
console.log(window.globalConstVar); // 輸出: undefined(不是 window 的屬性)
注意,雖然全局變量在某些情況下是必要的,但為了防止命名沖突和提高代碼質量,現代編程實踐中通常不鼓勵過度依賴全局變量。
函數作用域(Function Scope)
- 定義:在函數內部用var聲明的變量和函數參數,作用范圍為整個函數。
- 特點:
- 在函數內任何位置(包括嵌套代碼塊)可訪問。
- 示例:
function func() {if (true) {var innerVar = '內部變量'; // 屬于函數作用域}console.log(innerVar); // '內部變量'(正常訪問)
}
func();
?此例同時說明,var 聲明的變量沒有塊級作用域,即使在塊(如 if、for、while 等)中聲明,變量仍然屬于包含它的函數或全局作用域。
var 聲明的變量會被提升(hoisting)到當前作用域的頂部,但賦值不會被提升。這意味著變量在聲明之前可以訪問,但值為 undefined。例如:
console.log(hoistedVar); // 輸出: undefined
var hoistedVar = 'I am hoisted';
塊級作用域(ES6+)
- 定義:由?{}?包圍的代碼塊(如?if、for),使用?let?或?const?聲明。
- 特點:
- 變量僅在塊內有效。
- 避免循環變量泄露等問題。
- 示例:
if (true) {let blockVar = '塊內變量';const PI = 3.14;
}
console.log(blockVar); // 報錯:blockVar未定義
此例說明,let 和 const 不會被提升,存在“暫時性死區”(Temporal Dead Zone),即聲明前訪問會報錯。
注意:
??? ?????? let 和 const 聲明的變量僅在 聲明它們的代碼塊內有效。
??? ?????? 如果在函數體的最外層(不嵌套在任何代碼塊中)聲明,則作用域為 整個函數體(因為函數體本身是一個塊級作用域)。
??? ?????? 如果在函數內的嵌套代碼塊中聲明(如 if 內部),則作用域僅限該代碼塊。
var、let和const的區別
- var:
- 具有函數作用域(在函數內部聲明的變量只在函數內部有效)。
- 如果在全局作用域中聲明,則具有全局作用域。
- 存在變量提升(hoisting),但未初始化的變量會返回undefined。
- 可以重復聲明同一個變量。
- let和const:
- 具有塊級作用域(在代碼塊內部聲明的變量只在代碼塊內部有效)。
- 不會被提升到塊的頂部。
- 不允許重復聲明同一個變量。
- let聲明的變量可以重新賦值,而const聲明的變量不能重新賦值(具有只讀性,且必須在聲明時立即賦值,否則報錯))。
2.作用域鏈與閉包
- 作用域鏈(Scope Chain):函數在定義時確定作用域鏈,逐級向上查找變量。
- 閉包(Closures):函數保留對外部作用域的引用,即使外部函數已執行完畢。
- 內部函數可以訪問外部函數的變量。
- 外部函數執行完畢后,其作用域不會被銷毀,而是被內部函數引用。
作用域鏈示例:
let globalVar = "global";function outerFunction() {let outerVar = "outer";function innerFunction() {let innerVar = "inner";console.log(innerVar); // 輸出: innerconsole.log(outerVar); // 輸出: outerconsole.log(globalVar); // 輸出: global}innerFunction();
}outerFunction();
當訪問一個變量時,JavaScript會按照作用域鏈的順序查找變量。作用域鏈是從當前作用域開始,逐級向上查找,直到全局作用域。
閉包示例:
function outerFunction() {let outerVar = "I am outer";function innerFunction() {console.log(outerVar); // 訪問外部變量}return innerFunction;
}let myClosure = outerFunction();
myClosure(); // 輸出: I am outer
閉包是指函數可以訪問其外部作用域的變量,即使外部函數已經執行完畢。
附1、嚴格模式(使用'use strict')
- 在嚴格模式下,未聲明的變量會導致ReferenceError,從而避免全局變量污染。
- 嚴格模式是ES5引入的一種特殊的運行模式,使代碼在更嚴格的條件下運行。
- 通過在代碼開頭添加 "use strict" 聲明來啟用。
- 可以應用于整個腳本或單個函數。
啟用方式
// 整個腳本使用嚴格模式
"use strict";
// 后續代碼...
// 或在函數內使用
function myFunction() {
? ? "use strict";
? ? // 函數代碼...
}
?
示例:非嚴格模式和嚴格模式下未聲明變量直接賦值的行為差異。
假設我們有以下代碼片段:
function myFunction() {x = 10; // 未聲明的變量 xconsole.log(x); // 輸出 x 的值
}myFunction();
console.log(x); // 在全局作用域中訪問 x
1)非嚴格模式下的行為
在非嚴格模式下,如果直接給未聲明的變量賦值,JavaScript 會自動將該變量提升為全局變量。
function myFunction() {
? ? x = 10; // 未聲明的變量 x
? ? console.log(x); // 輸出 10
}
myFunction();
console.log(x); // 輸出 10
解釋:
在myFunction函數中,變量x沒有被聲明(沒有使用var、let或const),但直接賦值為10。
非嚴格模式下,JavaScript 會自動將x提升為全局變量。因此,x在函數內部和全局作用域中都可以被訪問。
這種行為可能導致意外的全局變量污染。
2)嚴格模式下的行為
在嚴格模式下,未聲明的變量直接賦值會拋出ReferenceError。
'use strict'; // 啟用嚴格模式
function myFunction() {
? ? x = 10; // 未聲明的變量 x
? ? console.log(x); // 拋出 ReferenceError : x is not defined
}
myFunction();
console.log(x); // 這行代碼不會被執行
解釋:
在嚴格模式下,JavaScript 不允許直接給未聲明的變量賦值。
如果嘗試給未聲明的變量賦值,JavaScript 會拋出ReferenceError,提示變量未定義。
這種行為可以防止意外創建全局變量,減少潛在的錯誤和命名沖突。
?
關于 JavaScript 嚴格模式的官方文檔可見https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Strict_mode
附2、可變(Mutable)和不可變(Immutable)
“可變”(Mutable)和“不可變”(Immutable)是描述變量或數據是否可以被修改的術語。
1)可變(Mutable)
如果一個變量或數據結構的內容可以被修改,那么它就是可變的。這意味著你可以直接改變變量的值或數據結構中的某些部分,而不會創建新的對象。
示例
// 可變的變量
let number = 10;
number = 20; // 修改變量的值,number 現在是 20
// 可變的對象
let obj = { name: "Alice" };
obj.name = "Bob"; // 修改對象的屬性,obj 現在是 { name: "Bob" }
obj.age = 25; // 添加新的屬性,obj 現在是 { name: "Bob", age: 25 }
在上面的例子中:
number 是一個可變的變量,因為它的值可以從 10 改為 20。
obj 是一個可變的對象,因為它的屬性可以被修改或添加。
2)不可變(Immutable)
如果一個變量或數據結構的內容不能被修改,那么它就是不可變的。這意味著一旦創建,它的值或內容就無法被改變。如果需要“修改”,通常會創建一個新的對象或變量。
示例
// 不可變的變量(使用 const 聲明)
const PI = 3.14;
PI = 3.14159; // 拋出 TypeError: Assignment to constant variable.
在上面的例子中:
PI 是一個不可變的變量,因為它被 const 聲明,不能被重新賦值。
不可變對象
雖然 JavaScript 中沒有原生的不可變對象,但可以通過一些技術手段(如 Object.freeze())使對象的結構和屬性不可變。
JavaScript復制
const obj = Object.freeze({ name: "Alice" });
obj.name = "Bob"; // 不會報錯,但不會生效,obj 仍然是 { name: "Alice" }
obj.age = 25; // 不會報錯,但不會生效,obj 仍然是 { name: "Alice" }
在上面的例子中:
- obj 是一個不可變的對象,因為通過 Object.freeze() 方法,它的屬性無法被修改或添加。
3. 可變與不可變的區別
- 可變數據:
- 可以直接修改。
- 修改操作通常會影響原始數據。
- 在某些情況下可能導致意外的副作用(如在函數中修改傳入的對象)。
- 不可變數據:
- 不能直接修改。
- 修改操作會創建一個新的對象或數據結構。
- 更安全,避免了意外修改導致的錯誤。
- 常用于函數式編程,因為函數式編程強調“無副作用”的函數。
小結
- 可變(Mutable):可以被修改。
- 不可變(Immutable):不能被修改。
- 在 JavaScript 中,let 聲明的變量是可變的,而 const 聲明的變量是不可變的(但對象或數組的內容可以被修改)。
- 不可變性有助于提高代碼的安全性和可維護性,尤其是在復雜的應用中。