FinalizationRegistry是和WeakMap、WeakSet、WeakRef在ES12一同進入到語言規範裡的兩個API。其實後面幾個更容易使用到,但我今天偏偏就是要來聊聊前者--FinalizationRegistry
。
在說說為什麼這個API有點雞肋可能沒什麼人知道之前,還是先介紹介紹這個API的用法。這個API的作用是在變數物件在被記憶體回收以前,可以註冊一些清理動作。比如可以建立物件:
var registry = new FinalizationRegistry((heldValue) => {
console.log(`${heldValue} is cleaned`);
})
var obj1 = { toString() { return "<Object obj1>"} };
registry
的callback function將快被記憶體回收的訊息打印出來。如果我們希望了解obj1
和obj2
何時被回收,可以用.register()
註冊:
registry.register(obj1, obj1.toString());
那麼當obj1
或obj2
不再可以被存取的時候,就有可能被記憶體回收,進而列印出訊息出來:
obj1 = null;
至於為什麼
obj1
不能使用delete
,可以參考「你可能都不瞭解的JS變數祕密 - 一文了解無宣告、var、let、const變數細節」
(突然發現這也是你可能不知道系列呢XD)
JavaScript和許多高級程式語言一樣,實現了自動的垃圾回收機制,在變數資料不再有辦法被存取後,一段時間會將其從記憶體釋放。但也與其他語言一樣,並不知道釋放的時間。
在我認識的中高級程式語言裡,只有C、C++、Rust比較能夠預測釋放記憶體的時間。
不過通常來說,自動記憶體回收機制若恰當,執行起來結果並不會太壞。
但如果是會佔用到IO等程式語言外部資源的,像是資料庫連線,仍不建議依賴自動記憶體釋放。
也因此實際上無法作用在任何前端應用上。不過透過瀏覽器工具,還是可以手動觸發記憶體回收機制的運作。
值得注意的是,本篇標題雖然還是帶有一個括號的Web,但其實這個是進到語言的設計規範裡面的。換句話說,Node.js、deno等等可以作為後端的語言實現,一樣可以使用這個特性。
只是不巧前陣子硬碟壞了,私人電腦並沒有安裝Node.js嘗試。但如果它還是基於V8引擎的話,最新版本應該已經可以使用。
FinalizationRegistry
會有這樣的設計出現,想必是有需要的情況。不過以我個人的看法,可以作為最後保險手段,但不要依賴。像是Java 8以前物件有finalize
可以使用,但是在Java 9廢棄的finalize?,換成Cleaner,並且在《Effective Java》第三版條款八中,依舊建議避免使用。
我並沒有閱讀過《Effective Java》。上面算是引用的
在C#,雖然物件有解構子,但如果有引用到外部資源,仍會建議實現IDisposable
介面。(以下僅作為概念範例,並不是可以運行的C#程式)
using System;
using System.ComponentModel;
public class DatabaseManager: IDisposable {
private Database db;
private bool disposed = false;
public DatabaseManager()
{
db = Database.open();
}
public void Dispose()
{
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
public void Dispose(bool disposing)
{
if(!this.disposed)
{
db.close();
disposed = true;
}
}
~DatabaseManager()
{
Dispose(disposing: false);
}
}
雖然C#在釋放記憶體前,會執行解構式。不過最好還是使用Dispose()
方法來明確釋放非受控資源。實現了IDisposable
可以使用using
語法:
using(DatabaseManger dm = new DatabaseManager)
{ }
這有點像是Python的:
with DatabaseManager() as dm:
...
最後在解構式在呼叫一次Dispose()
,算是做一層保險,避免使用該類型的人忘記呼叫Dispose()
。但為了避免被多次呼叫Dispose()
而造成的重複釋放記憶體的問題,還需要一個guard變數disposed
檢查。
注意:可以多次呼叫
Dispose()
,不代表在多執行緒下使用是安全的。
在了解以後,同理可以在JavaScript設計類似的類別(同樣這不是個可以執行的JavaScript):
const registry = new FinalizationRegistry((obj) => {
obj.Dispose();
})
class DatabaseManager {
#disposed = false;
db = null;
constructor() {
this.db = Database.open();
registry.register(obj1, obj1);
}
Dispose() {
if(this.#disposed) {
db.close();
this.#disposed = true;
}
}
}
就算這個API對我沒啥用,不過知道瀏覽器怎麼手動釋放記憶體還蠻有意思的~
本文同時發表於我的隨筆