FinalizationRegistry
Baseline 廣泛可用 *
FinalizationRegistry 物件允許您在值被垃圾回收時請求回撥。
描述
FinalizationRegistry 提供了一種方式,可以在註冊到登錄檔的值被回收(垃圾回收)後的某個時間點,請求呼叫一個清理回撥。(清理回撥有時也稱為終結器。)
注意: 清理回撥不應用於關鍵程式邏輯。有關詳細資訊,請參閱關於清理回撥的注意事項。
透過傳入回撥來建立登錄檔
const registry = new FinalizationRegistry((heldValue) => {
// …
});
然後,透過呼叫 register 方法,傳入值及其持有值,來註冊您希望獲得清理回撥的任何值。
registry.register(target, "some value");
登錄檔不會保留對值的強引用,因為那樣會適得其反(如果登錄檔強引用它,該值將永遠不會被回收)。在 JavaScript 中,物件和未註冊符號是可以被垃圾回收的,因此它們可以作為目標或令牌註冊到 FinalizationRegistry 物件中。
如果 target 被回收,您的清理回撥可能會在某個時間點被呼叫,並傳入您為其提供的持有值(上面的示例中是 "some value")。持有值可以是您喜歡的任何值:原始值或物件,甚至是 undefined。如果持有值是物件,登錄檔會保留對它的強引用(以便稍後將其傳遞給您的清理回撥)。
如果您可能想稍後取消註冊已註冊的目標值,您可以傳入第三個值,即您稍後呼叫登錄檔 unregister 函式以取消註冊該值時將使用的取消註冊令牌。登錄檔僅保留對取消註冊令牌的弱引用。
通常使用目標值本身作為取消註冊令牌,這完全沒問題。
registry.register(target, "some value", target);
// …
// some time later, if you don't care about `target` anymore, unregister it
registry.unregister(target);
然而,它不必是相同的值;可以是不同的值。
registry.register(target, "some value", token);
// …
// some time later
registry.unregister(token);
儘可能避免使用
正確使用 FinalizationRegistry 需要仔細考慮,如果可能,最好避免使用。同樣重要的是要避免依賴規範未保證的任何特定行為。垃圾回收何時、如何以及是否發生,取決於任何給定 JavaScript 引擎的實現。您在一個引擎中觀察到的任何行為,在另一個引擎、同一引擎的另一個版本,甚至在同一版本引擎的稍有不同的情況下,都可能不同。垃圾回收是一個棘手的問題,JavaScript 引擎的實現者正在不斷改進和最佳化他們的解決方案。
以下是引入 FinalizationRegistry 的提案中作者包含的一些具體要點:
垃圾回收器非常複雜。如果應用程式或庫依賴於 GC 來及時、可預測地清理 WeakRef 或呼叫終結器[清理回撥],它很可能會感到失望:清理可能比預期晚得多,或者根本不發生。造成這種不確定性的原因包括:
- 即使兩個物件同時變得不可達,一個物件也可能比另一個物件更早地被垃圾回收,例如,由於分代收集。
- 垃圾回收工作可以使用增量和併發技術來分時完成。
- 可以使用各種執行時啟發式方法來平衡記憶體使用和響應能力。
- JavaScript 引擎可能持有看起來似乎不可達的物件的引用(例如,在閉包或內聯快取中)。
- 不同的 JavaScript 引擎可能以不同的方式執行這些操作,或者同一個引擎在其版本之間可能會更改其演算法。
- 複雜的因素可能導致物件被意外地長時間保留,例如與某些 API 一起使用。
關於清理回撥的注意事項
- 開發者不應依賴清理回撥來執行關鍵程式邏輯。清理回撥可能有助於在程式執行過程中減少記憶體使用,但除此之外可能不太有用。
- 如果您剛剛將一個值註冊到登錄檔,該目標將不會在當前 JavaScript 任務結束前被回收。有關詳細資訊,請參閱關於 WeakRefs 的注意事項。
- 符合標準的 JavaScript 實現,即使是進行垃圾回收的實現,也不要求呼叫清理回撥。何時以及是否呼叫清理回撥完全取決於 JavaScript 引擎的實現。當已註冊物件被回收時,其相應的清理回撥可能會在那時被呼叫,或者在之後某個時間呼叫,或者根本不呼叫。
- 主要的實現很可能會在執行期間的某個時候呼叫清理回撥,但這些呼叫可能比相關物件被回收的時間晚很多。此外,如果一個物件在兩個登錄檔中註冊,不能保證兩個回撥會相鄰呼叫——一個可能被呼叫而另一個從未被呼叫,或者另一個可能晚得多才被呼叫。
- 在某些情況下,即使是通常會呼叫清理回撥的實現,也可能不太可能呼叫它們:
- 當 JavaScript 程式完全關閉時(例如,在瀏覽器中關閉標籤頁)。
- 當
FinalizationRegistry例項本身不再能被 JavaScript 程式碼訪問時。
- 如果
WeakRef的目標也存在於FinalizationRegistry中,則WeakRef的目標會在與登錄檔關聯的任何清理回撥被呼叫之前或同時被清除;如果您的清理回撥嘗試對物件的WeakRef呼叫deref,它將返回undefined。
建構函式
FinalizationRegistry()-
建立一個新的
FinalizationRegistry物件。
例項屬性
這些屬性定義在 FinalizationRegistry.prototype 上,並由所有 FinalizationRegistry 例項共享。
FinalizationRegistry.prototype.constructor-
建立例項物件的建構函式。對於
FinalizationRegistry例項,初始值是FinalizationRegistry建構函式。 FinalizationRegistry.prototype[Symbol.toStringTag]-
[Symbol.toStringTag]屬性的初始值是字串"FinalizationRegistry"。該屬性用於Object.prototype.toString()。
例項方法
FinalizationRegistry.prototype.register()-
將物件註冊到登錄檔,以便在物件被垃圾回收時/如果被垃圾回收時獲得清理回撥。
FinalizationRegistry.prototype.unregister()-
將物件從登錄檔中取消註冊。
示例
建立新登錄檔
透過傳入回撥來建立登錄檔
const registry = new FinalizationRegistry((heldValue) => {
// …
});
註冊物件以進行清理
然後,透過呼叫 register 方法,傳入物件及其持有值,來註冊您希望獲得清理回撥的任何物件。
registry.register(theObject, "some value");
回撥永遠不會同步呼叫
無論您對垃圾回收器施加多大的壓力,清理回撥都不會同步呼叫。物件可能會同步回收,但回撥總會在當前任務完成後某個時間被呼叫。
let counter = 0;
const registry = new FinalizationRegistry(() => {
console.log(`Array gets garbage collected at ${counter}`);
});
registry.register(["foo"]);
(function allocateMemory() {
// Allocate 50000 functions — a lot of memory!
Array.from({ length: 50000 }, () => () => {});
if (counter > 5000) return;
counter++;
allocateMemory();
})();
console.log("Main job ends");
// Logs:
// Main job ends
// Array gets garbage collected at 5001
然而,如果您允許在每次分配之間有短暫的間歇,回撥可能會更早被呼叫。
let arrayCollected = false;
let counter = 0;
const registry = new FinalizationRegistry(() => {
console.log(`Array gets garbage collected at ${counter}`);
arrayCollected = true;
});
registry.register(["foo"]);
(function allocateMemory() {
// Allocate 50000 functions — a lot of memory!
Array.from({ length: 50000 }, () => () => {});
if (counter > 5000 || arrayCollected) return;
counter++;
// Use setTimeout to make each allocateMemory a different job
setTimeout(allocateMemory);
})();
console.log("Main job ends");
不能保證回撥會被更早呼叫,或者是否會被呼叫,但日誌訊息的計數器值可能小於 5000。
規範
| 規範 |
|---|
| ECMAScript® 2026 語言規範 # sec-finalization-registry-objects |
瀏覽器相容性
載入中…