全域和本機變數範圍

本文將說明範圍以及如何在 JavaScript 中運作。

範圍是 JavaScript 和其他程式設計語言中的基本概念,可定義存取及使用變數的背景資訊。隨著您持續瞭解 JavaScript 並運用變數,這個 API 會更加實用且適用於程式碼。

範圍可協助您:

  • 提高記憶體使用效率:範圍可讓您只在需要時載入變數。如果變數超出範圍,您不需要讓目前正在執行的程式碼使用變數。
  • 更輕鬆地找出及修正錯誤:使用本機範圍隔離變數可更輕鬆地排解程式碼錯誤,因為與全域變數不同,您可以信任外部範圍內的程式碼無法在本機操控本機範圍內的變數。
  • 建立可重複使用的程式碼區塊:例如,您可以編寫不使用外部範圍的純函式。您可以輕鬆將這類函式移至他處,且盡量減少變動。

什麼是範圍?

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

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

  • 設有全域範圍的變數可從 JavaScript 程式碼內的所有其他範圍存取。
  • 具有本機範圍的變數只能在特定本機環境中使用,而且以關鍵字建立,例如 varletconst。如果您使用 varletconst 關鍵字在函式中建立變數,則該變數具有本地範圍。

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

  • 區塊範圍變數可在區塊中依據大括號 (定義區塊陳述式的位置) 提供,因此適用於區塊。只有使用 letconst 關鍵字宣告的變數才會具有封鎖範圍。
  • 詞法範圍會根據原始碼中宣告變數的位置,判斷該變數的可用位置。您可以使用閉包,讓封閉函式存取外部範圍內參照的變數 (稱為詞法環境)。

如果在範圍內存取變數,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該變數。

本機範圍和函式範圍

使用 varletconst 關鍵字在 JavaScript 函式中建立變數時,變數會位於函式本機,因此您只能透過函式存取變數。系統會在函式啟動時建立本機變數,並在函式執行完畢後立即刪除。

這個範例會在 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 變數。因此,系統會在 setTimeout 回呼函式中使用 hello 變數。

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 Scope Visualizer 編寫自己的程式碼。這項示範會使用程式碼中的顏色,協助您透過圖表呈現 JavaScript 範圍。

結論

本文將介紹不同類型的範圍。JavaScript 範圍是網頁開發中較進階的概念之一,因此非常適合您已閱讀本文內容並花時間瞭解這個主題。

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