初學者指南:深入了解 JavaScript 的函式作用域與區塊作用域

更新日期: 2024 年 10 月 17 日

在學習 JavaScript 的過程中,理解「作用域」(Scope)的概念是至關重要的。

作用域決定了變數和函式在程式中的可見性和生命週期。

JavaScript 的作用域主要分為兩種:函式作用域(Function Scope)和 區塊作用域(Block Scope)。

本文將為新手詳細介紹這兩種作用域的概念、差異,以及如何在實際開發中正確使用它們。

閱讀本文前,建議先具備相關概念:初學者指南:深入了解 JavaScript 的建立期與執行期


什麼是作用域?

作用域是指變數或函式在程式中可被訪問的範圍。理解作用域有助於:

  • 避免變數命名衝突:確保不同作用域中的變數不會互相影響。
  • 管理變數的生命週期:控制變數何時被創建和銷毀。
  • 提高程式碼的可讀性和維護性:清晰地組織程式結構。

函式作用域(Function Scope)

定義

函式作用域是指在「函式」內部宣告的變數,只有在該函式內部可見和可訪問。

傳統上,JavaScript 使用 var 關鍵字宣告的變數就是具有函式作用域。

特點

  • 作用範圍限制在函式內部:變數在函式內部宣告後,無法在函式外部訪問。
  • 變數提升(Hoisting):使用 var 宣告的變數會被提升到函式作用域的頂部,初始化為 undefined

範例

範例 1:函式內部的變數

function greet() {
  var message = "Hello, World!";
  console.log(message); // 輸出:Hello, World!
}

greet();
console.log(message); // ReferenceError: message is not defined

解釋:

  • 變數 message 在函式 greet 內宣告,只有在該函式內部可訪問。
  • 在函式外部訪問 message 會拋出錯誤,因為它不在全域作用域內。

變數提升

function test() {
  console.log(a); // 輸出:undefined
  var a = 10;
  console.log(a); // 輸出:10
}

test();

解釋:

  • var a = 10; 中的 a 被提升到函式的頂部,但未賦值,因此第一次輸出為 undefined
  • 之後 a 被賦值為 10,第二次輸出為 10

變數提升帶來的問題

  • 意外的 undefined:因為變數提升,可能在變數宣告之前就訪問到該變數,導致輸出 undefined
  • 難以調試:變數提升可能讓程式碼的執行順序變得不直觀,增加調試的難度。

區塊作用域(Block Scope)

定義

區塊作用域是指在一對花括號 {} 所包圍的區塊內宣告的變數,其作用範圍僅限於該區塊內。

ES6 引入了 letconst 關鍵字,使得 JavaScript 支援區塊作用域。

特點

  • 作用範圍限制在區塊內部:變數在區塊內宣告後,無法在區塊外部訪問。
  • 不會提升或在提升時不會初始化letconst 宣告的變數在建立期被創建但不初始化,在宣告之前訪問會拋出錯誤。
  • 暫時性死區(Temporal Dead Zone,TDZ):從區塊的開始到變數宣告為止的區域,無法訪問該變數。

範例

範例 1:區塊內部的變數

{
  let x = 10;
  const y = 20;
  console.log(x); // 輸出:10
  console.log(y); // 輸出:20
}

console.log(x); // ReferenceError: x is not defined
console.log(y); // ReferenceError: y is not defined

解釋:

  • xy 在區塊內宣告,僅在該區塊內部可訪問。
  • 在區塊外部訪問 xy 會拋出錯誤。

範例 2:暫時性死區

{
  console.log(z); // ReferenceError: Cannot access 'z' before initialization
  let z = 5;
}

解釋:

  • 在宣告變數 z 之前訪問它,會拋出 ReferenceError,因為它處於暫時性死區。

範例 3:在循環中的區塊作用域

for (let i = 0; i < 3; i++) {
  console.log(i); // 輸出:0, 1, 2
}

console.log(i); // ReferenceError: i is not defined

解釋:

  • let i = 0; 宣告的變數 i,作用域僅限於 for 迴圈的區塊內。
  • 在迴圈外部訪問 i 會拋出錯誤。

函式作用域與區塊作用域的差異

作用範圍

  • 函式作用域:變數的作用範圍限制在函式內部,對於 var 宣告的變數適用。
  • 區塊作用域:變數的作用範圍限制在區塊 {} 內部,對於 letconst 宣告的變數適用。

變數提升

  • 函式作用域(var
    • 變數提升到作用域頂部,初始化為 undefined
    • 可以在變數宣告之前訪問變數,但值為 undefined
  • 區塊作用域(letconst
    • 變數被提升但不初始化,處於暫時性死區。
    • 在變數宣告之前訪問變數會拋出 ReferenceError

變數的重複宣告

  • var:允許在同一作用域內重複宣告變數,後面的宣告會覆蓋前面的。
  • letconst:不允許在同一作用域內重複宣告變數,否則會拋出 SyntaxError

範例:

function example() {
  var a = 1;
  var a = 2; // 允許,a 的值被更新為 2
  console.log(a); // 輸出:2

  let b = 1;
  let b = 2; // SyntaxError: Identifier 'b' has already been declared
}

example();

最佳實踐

優先使用 letconst

  • 提升代碼安全性:避免變數提升和暫時性死區帶來的問題。
  • 提高可讀性:使變數的作用範圍更加清晰。

使用 const 儲存常量

  • 不可變性const 宣告的變數不可重新賦值,適用於不會改變的值。
  • 預防意外修改:防止變數被重新賦值導致的錯誤。

避免使用 var

  • 防止作用域污染var 沒有區塊作用域,可能導致變數在不應該的地方被修改。
  • 避免變數提升var 的提升可能導致難以預測的行為。

明確劃分作用域

  • 利用區塊作用域:將相關的代碼組織在一起,限制變數的可見性。
  • 減少全域變數的使用:盡量將變數限制在需要的最小作用域內。

結語

理解 JavaScript 的函式作用域和區塊作用域,對於撰寫高品質的程式碼至關重要。

透過正確地使用 letconstvar,你可以:

  • 控制變數的生命週期:確保變數在需要的時候可用,避免不必要的內存佔用。
  • 提高代碼的可維護性:清晰的作用域結構讓程式碼更易於理解和修改。
  • 避免常見錯誤:如變數提升、作用域污染等問題。

希望本文能夠幫助你深入了解這兩種作用域的概念,並在未來的開發中靈活運用這些知識,寫出更健壯和可靠的 JavaScript 程式碼。


參考資料

Similar Posts