Skip to content

2026-06-13

/ 13 分鐘閱讀

/ Deep JavaScript Foundations v3

詞彙範疇的邊界案例:遮蔽缺失與自動全域變數

本文延續執行階段的討論。上一篇走過了所有查找都成功的「順利路徑」,這篇要看兩個更微妙的情況:當內層範疇沒有宣告變數時會發生什麼,以及查找失敗時 JavaScript 的預設行為。

示範程式碼

javascript
var teacher = "Kyle";

function otherClass() {
    teacher = "Suzy";       // 注意:沒有 var
    topic = "React";        // 注意:沒有 var,也不在任何範疇中宣告
    console.log("Welcome!");
}

otherClass();
console.log(teacher);   // Suzy
console.log(topic);     // React

編譯階段:otherClass 內部沒有任何宣告

走過編譯階段,全域範疇會建立兩顆紅色彈珠:teacherotherClass

進入 otherClass 的藍色桶子時,整個函式內部沒有任何 varletconst 宣告。藍色桶子完全是空的,沒有任何藍色彈珠。這個細節在執行期會產生重要影響。

執行階段:沒有遮蔽 (shadowing) 時,賦值找到外層彈珠

第 4 行:teacher = "Suzy"(目標位置)

引擎:「藍色桶子,目標參考,teacher,你認識嗎?」 範疇管理器:「不認識,藍色桶子裡沒有這顆彈珠。」

往外一層:

引擎:「全域範疇,目標參考,teacher,你認識嗎?」 範疇管理器:「認識,給你紅色彈珠。」

引擎拿到的是紅色彈珠,不是藍色彈珠。"Suzy" 被賦值到全域的 teacher,覆蓋了原本的 "Kyle"

這與宣告了 var teacher 的版本不同,那個版本在函式內部建立了獨立的藍色彈珠,形成遮蔽(shadowing)。沒有宣告就沒有遮蔽,賦值會沿著範疇鏈往上找,直到找到已宣告的識別字。

執行階段:完全未宣告的變數與自動全域

第 5 行:topic = "React"(目標位置)

引擎:「藍色桶子,目標參考,topic,你認識嗎?」 範疇管理器:「不認識。」

往外一層:

引擎:「全域範疇,目標參考,topic,你認識嗎?」 範疇管理器:「不認識。」

這裡是關鍵分歧點。理性上,我們期望的答案是「不認識,拋出錯誤」。但在**非嚴格模式(non-strict mode/sloppy mode)**下,JavaScript 的歷史行為是:

全域範疇在執行期動態創建一個新的全域變數 topic,並把紅色彈珠交給引擎。

javascript
// 執行後,全域範疇多了一個沒有被宣告的變數
console.log(topic);   // "React"

這就是自動全域(auto global),在執行期動態創建於全域範疇,不是編譯期的正式宣告。Kyle Simpson 認為這是 JavaScript 早期為了「盡量寬容」而做出的糟糕設計,至今成為許多難以追蹤的 bug 來源。

嚴格模式:關閉自動全域,得到 ReferenceError

在程式頂端加上 "use strict" 後,行為會改變:

javascript
"use strict";

function otherClass() {
    topic = "React";   // ReferenceError: topic is not defined
}

在嚴格模式下,當查找抵達全域範疇仍找不到識別字時,不再默默創建變數,而是直接拋出 ReferenceError

這裡有個值得釐清的術語區別:

  • ReferenceError:找不到這個變數,無法給你使用
  • TypeError:找到了變數,但它持有的值不允許你做你想做的事(例如對 null 存取屬性、對非函式的值使用呼叫語法)

兩種錯誤的根本原因不同,看到錯誤訊息時要能區分。

關於嚴格模式的幾個補充

嚴格模式不是預設開啟的。 JavaScript 維持向後相容,不能讓 20 年前寫的程式突然壞掉,所以必須主動加上 "use strict" 才會啟用。

但有些環境預設就是嚴格模式。 ES6 的 class 語法和 ES6 模組(import/export)內部,不需要手動宣告,預設就在嚴格模式下執行。如果你使用 Babel 等轉譯工具,輸出的程式碼通常也會自動加上嚴格模式。

嚴格模式是語言的未來方向。 愈來愈多新特性只在嚴格模式下以正確的方式運作,Kyle Simpson 建議直接採用——它已經存在將近十年,現在切換過去是值得的。

嚴格模式除了自動全域之外還有其他影響。 例如在 ES5 時代,對唯讀屬性賦值在非嚴格模式下會靜默失敗(什麼都不發生);開啟嚴格模式後就會拋出錯誤。把原本靜默吞掉的問題變成可見的錯誤,是嚴格模式整體設計的核心精神。

核心結論

這個練習揭示了兩個關鍵行為:

賦值給未遮蔽的外層變數:當函式內部沒有宣告同名變數時,賦值會沿著範疇鏈往上找,修改到外層(甚至全域)的變數。這不一定是 bug,但需要清楚意識到這件事。

賦值給完全未宣告的變數:在非嚴格模式下,自動創建全域變數。這幾乎永遠是 bug。解法是永遠明確宣告你需要使用的變數,並打開嚴格模式。

實踐建議: 永遠在適當的範疇中使用 varletconst 明確宣告變數,並啟用嚴格模式。

複習

在非嚴格模式的 JavaScript 中,對未宣告的變數賦值時,會發生什麼?

會在全域範疇自動創建一個全域變數,這可能導致非預期的行為,被視為不良實踐。

JavaScript 中自動全域(auto global)的問題是什麼?

當對未宣告的變數賦值時,會自動創建全域變數,可能造成非預期的副作用,並使程式碼更難除錯。

嚴格模式如何改變未宣告變數的賦值行為?

在嚴格模式下,嘗試對未宣告的變數賦值會拋出錯誤,而不是創建自動全域變數。

當一個變數在全域範疇被重新賦值時,會發生什麼?

原本的值會被覆蓋,變數指向新的值,不會創建獨立的新變數。

JavaScript 中變數宣告的建議做法是什麼?

始終在適當的範疇中使用 varletconst 明確宣告變數,避免創建自動全域變數。

小測驗

在非嚴格模式的 JavaScript 中,對未宣告的變數賦值時,會發生什麼? 創建一個自動全域變數
在全域範疇查找變數時,使用的是哪種參考類型? 來源參考(Source reference)
在嚴格模式下執行 JavaScript 時,哪個行為會改變? 防止自動全域變數的創建
當 JavaScript 中的變數被賦予新值時,會發生什麼? 覆蓋現有變數的值
在非嚴格模式下,全域範疇查找到未宣告的變數時,會如何回應? 創建並回傳一個新的全域變數

此文章是 FrontendMasters 上的 Deep JavaScript Foundations, v3 課程筆記

最後更新時間:

Buy Me A Coffee

系列章節 第 37 篇 / 共 51 篇

0 %
MIT Licensed | Copyright © 2025-present Wen-Hsiu's Blog
Photo by Federica Galli on Unsplash