跳至主要內容
Skip to content

JS 實戰 (3) —— 記憶體管理的隱形冠軍:WeakMap 與響應式緩存

在開發大型單頁應用 (SPA) 時,除了效能,最讓工程師頭痛的就是 「記憶體洩漏 (Memory Leak)」

你可能已經知道 Vue 3 使用了全新的響應式引擎,但你可能不知道,這個引擎的底層有一位「隱形冠軍」在默默支撐著全站的記憶體安危——它就是 WeakMap


一、 回顧:垃圾回收 (GC) 的基本法則

JavaScript 是一門具備自動垃圾回收機制的語言。最核心的法則只有一條:「只要這個物件沒人要了(不可達),我就把它回收掉」。

但什麼叫「有人要」?這就引出了 強引用 (Strong Reference) 的概念。

1. 強引用的陷阱

一般的 MapSet 都是強引用。

javascript
let user = { name: "Linyi" };
const cache = new Map();

cache.set(user, "Some User Data");

user = null; // 我們以為這會讓物件被回收
// ❌ 錯了!cache 內部依然持有對該物件的強引用。
// 只要 cache 存在,原本那個物件就永遠不會被回收。

這就是記憶體洩漏的典型場景:隨著時間推移,你的緩存 (Cache) 裡堆滿了死掉的物件,直到瀏覽器崩潰。


二、 弱引用 (Weak Reference):優雅的放手

WeakMapWeakSet 的出現就是為了打破僵局。

1. 什麼是 WeakMap?

你可以把它想像成一個「單向存取」的地圖:

  • Key 必須是物件
  • 弱引用的力量:WeakMap 對 Key 的引用不計入 GC 的回收判斷程式。
javascript
let node = { id: 1 };
const wm = new WeakMap();

wm.set(node, "Related Metadata");

node = null; // 當 node 在外部沒有其他引用時...
// ✅ GC 會在下次執行時自動把該物件回收,即便 wm 裡還有它。

三、 深度解析:為什麼 Vue 3 非它不可?

在第一篇中我們提到,Vue 3 的響應式核心是一個全域的 targetMap。它記錄了「哪個物件」的「哪個屬性」被「哪個 Effect」監聽著。

1. 龐大的依賴地圖

想像你的 App 裡有幾千個元件,每個元件都有幾百個資料點。

javascript
const targetMap = new WeakMap(); // (Object) -> (Map) -> (Set)

如果 targetMap 使用普通的 Map

  1. 當你銷毀 (Destroy) 一個元件時,該元件內部的數據物件(Target)依然被 targetMap 死死抓著。
  2. 即使該元件已經在画面上消失,它的響應式資料、關聯的 Effects 通通都不會被釋放。
  3. 元件切換幾次後,記憶體就會瞬間爆表。

因為使用了 WeakMap,當元件銷毀、資料物件外部引用消失時,Vue 3 核心數據地圖也會自動「瘦身」,無需手動清理,這讓框架變得既強大又安全。


四、 效能建議:緩存 (Cache) 的正確姿勢

如果你正在開發一個具備「資料快取」功能的 Service,請遵循以下原則:

  1. 關聯元數據:如果資料是跟著某個 DOM 元素或物件走的,請用 WeakMap
  2. 避免死緩存:不要讓全域變數 (Global scope) 長期持有一個普通的 Map,除非你有明確的清理 (Pruning) 機制。

五、 關鍵區別總結

特性MapWeakMap
Key 型別任意型別必須是物件
可遍歷性可遍歷 (keys, values, entries)不可遍歷
GC 行為持有強引用,阻礙回收持有弱引用,不影響回收
應用場景通用的鍵值對存儲關聯元數據、依賴收集、私有屬性模擬

️ 進階挑戰

  1. 實作挑戰:試著利用 WeakMap 為一個現有的類別實作「真・私有屬性」。外層無論如何都存取不到,且當實例銷毀時,私有屬性資料也會自動回收。
  2. 深度思考:為什麼 WeakMap 是不可遍歷的 (No .size, No .keys())?這與垃圾回收機制有什麼底層關聯?

延伸閱讀與資源

返回專題首頁