在JavaScript中,作用域和作用域鏈是理解代碼執行和變量訪問的關鍵概念。它們決定了變量和函數在代碼中的可見性和生命周期。
一、作用域(Scope)
(一)什么是作用域?
作用域是在運行時代碼中的某些特定部分中變量、函數和對象的可訪問性。換句話說,作用域決定了代碼區塊中變量和其他資源的可見性。
作用域的主要作用是隔離變量,防止不同作用域下的同名變量發生沖突。例如:
function outFun2() {var inVariable = "內層變量2";
}
outFun2();
console.log(inVariable); // Uncaught ReferenceError: inVariable is not defined
在上面的例子中,變量inVariable
在全局作用域中沒有聲明,因此在全局作用域下訪問它會報錯。
(二)全局作用域和函數作用域
1. 全局作用域
全局作用域是指在代碼中任何地方都能訪問到的對象。以下幾種情形擁有全局作用域:
- 最外層函數和在最外層函數外面定義的變量擁有全局作用域。
- 所有未定義直接賦值的變量自動聲明為擁有全局作用域。
- 所有
window
對象的屬性擁有全局作用域。
var outVariable = "我是最外層變量"; // 最外層變量
function outFun() { // 最外層函數var inVariable = "內層變量";function innerFun() { // 內層函數console.log(inVariable);}innerFun();
}
console.log(outVariable); // 我是最外層變量
outFun(); // 內層變量
console.log(inVariable); // inVariable is not defined
全局作用域的弊端是容易污染全局命名空間,引起命名沖突。因此,通常建議將代碼封裝在函數中,避免全局變量的濫用。
2. 函數作用域
函數作用域是指聲明在函數內部的變量,這些變量只能在函數內部訪問。例如:
function doSomething() {var stuName = "zhangsan";function innerSay() {console.log(stuName);}innerSay();
}
console.log(stuName); // 腳本錯誤
innerSay(); // 腳本錯誤
函數作用域的一個重要特點是內層作用域可以訪問外層作用域的變量,但外層作用域不能訪問內層作用域的變量。
(三)塊級作用域
ES6引入了塊級作用域,通過let
和const
關鍵字聲明的變量具有塊級作用域。塊級作用域在以下情況被創建:
- 在一個函數內部。
- 在一個代碼塊(由一對花括號包裹)內部。
塊級作用域的特點包括:
- 聲明變量不會提升到代碼塊頂部。
- 禁止重復聲明。
- 循環中的綁定塊作用域的妙用。
for (let i = 0; i < 10; i++) {console.log(i); // i 在循環內部有效
}
console.log(i); // ReferenceError: i is not defined
二、作用域鏈
(一)什么是自由變量?
自由變量是指在當前作用域中沒有定義的變量。例如:
var a = 100;
function fn() {var b = 200;console.log(a); // 這里的 a 是一個自由變量console.log(b);
}
fn();
在fn
函數中,a
是一個自由變量,因為它在fn
函數的作用域中沒有定義。
(二)什么是作用域鏈?
作用域鏈是指當訪問一個變量時,編譯器會從當前作用域開始,逐層向上查找,直到找到該變量或到達全局作用域。例如:
var a = 100;
function f1() {var b = 200;function f2() {var c = 300;console.log(a); // 100console.log(b); // 200console.log(c); // 300}f2();
}
f1();
在f2
函數中,a
和b
是自由變量,它們的值通過作用域鏈從外層作用域中獲取。
(三)關于自由變量的取值
自由變量的值是在函數定義時確定的,而不是在函數調用時確定的。例如:
var x = 10;
function fn() {console.log(x);
}
function show(f) {var x = 20;(function () {f(); // 輸出 10,而不是 20})();
}
show(fn);
在fn
函數中,x
的值是在fn
函數定義時確定的,因此輸出的是全局作用域中的x
,而不是show
函數中的x
。
三、作用域與執行上下文
許多開發人員經常混淆作用域和執行上下文的概念。雖然它們都與變量的訪問和函數的執行有關,但它們是不同的概念。
- 作用域:作用域是在函數定義時確定的,它決定了變量的可見性和生命周期。
- 執行上下文:執行上下文是在函數執行時創建的,它包括變量對象、作用域鏈和
this
的指向。
(一)執行上下文的生命周期
執行上下文的生命周期分為兩個階段:
-
創建階段:當代碼執行進入一個環境時,會創建一個執行上下文。在這個階段,執行上下文會進行以下操作:
- 創建變量對象(Variable Object,VO):包括函數的形參、
arguments
對象、函數聲明和變量聲明。 - 確定
this
的指向。 - 確定作用域鏈。
- 創建變量對象(Variable Object,VO):包括函數的形參、
-
執行階段:在執行階段,代碼開始執行,變量被賦值,函數被調用,其他代碼按順序執行。
四、總結
理解作用域和作用域鏈的工作原理和實際應用,可以幫助你更好地理解代碼的執行流程和變量的訪問機制。如果你對本文的內容有任何疑問或補充,歡迎在評論區留言討論。