全域和本機變數範圍

本文將說明範圍及其在 JavaScript 中的運作方式。

範圍是 JavaScript 和其他程式設計語言中的基本概念,用於定義存取和使用變數的結構定義。隨著您持續學習 JavaScript 及運用變數,您的程式碼會變得更加實用,且適用於程式碼。

範圍可協助您:

  • 更有效率地使用記憶體:範圍可讓您只在需要時載入變數。如果變數超出範圍,則不需要提供給目前執行的程式碼使用。
  • 更輕鬆地找出及修正錯誤:使用本機範圍隔離變數,可讓您更輕鬆地排解程式碼中的錯誤;因為與全域變數不同,您可以信任外部範圍的程式碼無法操控本機範圍變數。
  • 建立可重複使用的程式碼小區塊:例如,您可以編寫不必依賴外部範圍的純函式。只需稍微調整,即可輕鬆將這類函式移至其他位置。

什麼是範圍?

變數的範圍會決定您可以在程式碼中使用變數的哪個位置。

JavaScript 會定義全域或本機範圍的變數:

  • 設有全域範圍的變數可從 JavaScript 程式碼中的其他範圍中使用。
  • 設有本機範圍的變數僅適用於特定當地環境,且可透過關鍵字建立,例如 varletconst。如果您使用 varletconst 關鍵字在函式中建立變數,則該變數會具有本機範圍。

本文後續章節將探討區塊和詞法範圍:

  • 「區塊範圍」變數可在本機供本機使用,具體取決於大括號內定義區塊陳述式的位置。只有以 letconst 關鍵字宣告的變數具有區塊範圍。
  • Lexical 範圍會使用原始碼中宣告變數的位置,判斷該變數的適用位置。您可以使用閉包來授權封閉式函式存取外部範圍 (稱為詞法環境) 中參照的變數。

如果在範圍內存取變數,JavaScript 會傳回其指定值或產生錯誤。

宣告變數的方法如下:

  • 使用 varconstlet 關鍵字來宣告本機或全域範圍變數。
  • 使用 constlet 關鍵字宣告區塊範圍變數。

當您在函式中宣告 var 變數時,宣告會將變數提供給最近的封閉函式。您無法使用 var 關鍵字宣告封鎖範圍的變數。

範圍範例

這個範例說明瞭全域範圍,因為 greeting 變數是在函式或區塊外宣告,所以其值可供目前文件中的所有程式碼使用:

const greeting = 'hello';
console.log(greeting); // 'hello'

在全域範圍範例中,greeting 變數獲派 hello 值。

本範例示範本機範圍,因為在函式中使用 let 關鍵字宣告 greeting 變數。greeting 變數是本地範圍的變數,無法在函式外部使用。

function greet() {
  let greeting = 'Hello World!';
  console.log(greeting);
}

以下範例說明區塊範圍,原因是此範例會在區塊中宣告 greeting 變數,讓該變數只能在大括號中存取:

if (true) {
   const greeting = 'hello';
}

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

請注意,當 console.log 函式嘗試輸出 greeting 變數的值時,JavaScript 會傳回 ReferenceError 錯誤訊息,而不是預期的 hello 訊息。原因何在?

greeting 變數具有區塊範圍,且最近的區塊是 if 條件陳述式的一部分,因此系統會傳回錯誤。您無法從區塊外存取區塊中宣告的 letconst 變數。因此,您只能在指定區塊範圍的大括號中存取 greeting 變數。

此範例會將 console.log(message) 方法移至大括號中,因此修正了錯誤。更新的程式碼會將 console.log(message) 方法重新置於區塊中。

if (true) {
   const greeting = 'hello';
   console.log(greeting);
}

範圍類型

全域範圍

您可以在程式中的任何位置存取全域範圍的變數。

假設使用一個 HTML 檔案匯入兩個 JavaScript 檔案:file-1.jsfile-2.js

<script src="file-1.js"></script>
<script src="file-2.js"></script>

在這個範例中,globalMessage 變數具有全域範圍,而且是在函式外編寫。在執行和執行期間,您可以從 JavaScript 程式的任何位置存取 globalMessage 變數的值。

您可以在這個程式碼片段中查看 file-1.jsfile-2.js 檔案的內容。請注意,這兩個檔案中都有 globalMessage 變數。

// file-1.js
function hello() {
    var localMessage = 'Hello!';
}

var globalMessage = 'Hey there!';

// file-2.js
console.log(localMessage); // localMessage is not defined
console.log(globalMessage); // Hey there!

還有其他類型的範圍不在本文的討論範圍內。如果您在 JavaScript 模組內建立變數,但不在函式或區塊之外,則變數沒有全域範圍,而是模組範圍。具有模組範圍的變數可在目前模組中的任何位置存取,但無法透過其他檔案或模組存取。如要將模組範圍變數提供給其他檔案使用,您必須從建立該變數的模組匯出該變數,然後從需要存取變數的模組中import

本機範圍和函式範圍

當您在 JavaScript 函式中使用 varletconst 關鍵字建立變數時,這些變數是函式本機的,因此您只能從函式中存取這些變數。本機變數會在函式啟動時建立,並在函式執行完畢後有效刪除。

這個範例會在 addNumbers() 函式中宣告 total 變數。您只能存取 addNumbers() 函式中的 ab,total 變數。

function addNumbers(a, b) {
    const total = a + b;
}

addNumbers(3, 4);

您可以使用 letconst 關鍵字為變數命名。使用 let 關鍵字時,JavaScript 可以更新變數。然而,有了 const 關鍵字,變數會保持不變。

var variable1 = 'Declared with var';
var variable1 = 'Redeclared with var';
variable1; // Redeclared with var

let variable2 = 'Declared with let. Cannot be redeclared.';
variable2 = 'let cannot be redeclared, but can be updated';
variable2; // let cannot be redeclared, but can be updated

const variable3 = 'Declared with const. Cannot be redeclared or updated';
variable3; // Declared with const. Cannot be redeclared or updated

封鎖範圍

封鎖條件可用來將單一陳述式或一組陳述式分組。您可以使用 constlet 關鍵字來宣告區塊範圍的本機變數。請注意,您無法使用 var 關鍵字宣告區塊範圍的變數。

舉例來說,在這個區塊中,name 變數的範圍及其 "Elizabeth" 值包含在大括號中。區塊範圍內的變數無法在區塊外使用。

{
    const name = "Elizabeth";
}

您可以在 ifforwhile 陳述式中使用區塊範圍變數。

請記下這個程式碼片段中的兩個 for 迴圈。一個 for 迴圈會使用 var 關鍵字宣告初始化器變數,而變數會透過 012 遞增。其他 for 迴圈會使用 let 關鍵字宣告初始化器變數。

for (var i = 0; i < 2; i++) {
    // ...
}

console.log(i); // 2

for (let j = 0; j < 2; j++) {
    // ...
}

console.log(j); // The j variable isn't defined.

在先前的程式碼範例中,您可能會發現第一個 for 迴圈中的 i 變數外洩至 for 迴圈,但仍會保留 2 值,因為 var 關鍵字未使用區塊範圍。這項問題在第二個 for 迴圈中已修正,其中使用 let 關鍵字宣告的 j 變數的範圍限定在 for 迴圈的區塊內,且在 for 迴圈完成後不存在。

重複使用其他範圍內的變數名稱

範圍可以區隔函式中的變數,即使您在不同範圍的其他位置重複使用相同的變數名稱也一樣。

本例說明如何使用範圍,在不同函式中重複使用相同的變數名稱:

function listOne() {
    let listItems = 10;
    console.log(listItems); // 10
}

function listTwo() {
   let listItems = 20;
   console.log(listItems); // 20
}

listOne();
listTwo();

listOne()listTwo() 函式中的 listItems 變數會獲派預期值,因此請勿彼此衝突。

閉包和詞法範圍

閉包是指封閉式函式,內部函式可存取外部函式範圍,也稱為詞法環境。因此,在 JavaScript 中,您可以使用閉包來讓函式參照外部詞法環境,這樣一來,程式碼就能在函式外宣告的函式參照變數內編寫程式碼。事實上,您可以將指向外部詞法環境的參照鏈結編寫程式碼,讓函式由另一個函式呼叫函式。

在本範例中,程式碼會形成一個閉包環境,而該環境會在叫用 outer() 函式時建立,並透過 hello 變數關閉。因此,hello 變數會在 setTimeout 回呼函式中使用。

function outer() {
    const hello = 'world';

    setTimeout(function () {
        console.log('Within the closure!', hello)
    }, 100);
}

outer();

使用詞法範圍時,範圍是在原始碼編譯期間決定,而非在執行階段。如要進一步瞭解詞法環境,請參閱詞形範圍和閉包

單元

JavaScript 模組有助於整理 JavaScript 程式碼。如果使用得當,它們可以為程式碼集提供有效的結構,並協助重複使用程式碼。JavaScript 模組提供匯出import變數的技巧,而不是使用全域變數在不同檔案之間共用變數。

// hello.js file
function hello() {
  return 'Hello world!';
}

export { hello };

// app.js file
import { hello } from './hello.js';

console.log(hello()); // Hello world!

範圍視覺化工具示範

範圍是所有 JavaScript 開發人員都應瞭解的基本概念。如要進一步瞭解範圍系統,您可以嘗試使用 JS 範圍 Visualizer 自行編寫程式碼。示範程式碼會在程式碼中使用顏色,協助您以視覺化方式呈現 JavaScript 範圍。

結語

本文將介紹不同的範圍類型。JavaScript 範圍是網頁開發中的進階概念之一,因此請務必詳閱內容,並花一些時間瞭解這個主題。

範圍並非面向使用者的功能。這只會影響編寫程式碼的網頁程式開發人員,但瞭解範圍的運作方式,可協助您修正錯誤。