Weakmap詳解

喆星高照發表於2022-06-17

先看一個例子

let obj = { name: 'toto' }

// { name: 'toto' }這個物件能夠被讀取到,因為obj這個變數名有對它的引用

// 將引用覆蓋掉
obj = null

// 這個物件將會被從記憶體中移除,因為我們已經失去了對它所有的引用
再來看另外一個例子
let obj = { name: 'toto' }
let arr = [ obj ]

obj = null
在這個例子中,物件{name:'toto'}不會被從記憶體中移除,因為陣列arr儲存了對它的引用

強引用和弱引用之間有什麼區別呢?

事實上,javascript中的大多數變數都儲存著對一個物件的強引用。比如上面這個陣列儲存著對物件{name:'toto'}的強引用

如果一個變數儲存著對一個物件的強引用,那麼這個物件將不會被垃圾回收,但是如果一個變數只儲存著對這個物件的弱引用,那麼這個物件將會被垃圾回收

一些變數型別在物件上有一個弱引用,這就是Weakmap的情況

Weakmap

weakmap是一個額外的資料儲存,它可以讓我們從外部(第三方庫)擴充套件或者封裝一個物件,而不需要進行垃圾回收的推斷,或者能夠智慧的建立一個快取函式。

不用擔心看不明白,在比較mapweakmap之前我將解釋並展示它的含義。

Map和Weakmap的比較

使用map,物件會佔用記憶體,可能不會被垃圾回收。Map對一個物件是強引用

let obj = { name: 'toto' }
let mapObj = new Map()
mapObj.set(obj, 'any value')

obj = null
mapObj.size() // 1

 

 

Weakmap則是完全不同的,它不會阻止關鍵物件的垃圾回收

第一條規則,weakmap只接受object作為key,第二條規則是它只儲存對物件的弱引用。

let obj = { name: 'toto' }
let weakmapObj = new WeakMap()
weakmapObj.set(obj, 'any value')

obj = null
weakmapObj .size() // 0

 

物件被垃圾回收器刪除,因為weakmap在物件{ name: ‘toto’ }上只有弱引用,而這個物件已經沒有強引用了。(只有變數obj有保持引用)

何時使用Weakmap?

正如你所看到的,Weakmap可以用在任何地方

快取器函式

const cache = new WeakMap() 

const process = function (obj) { 
    // 如果輸入的值不在快取器中
    if (!cache.has(obj)) { 
        // 想象一個函式需要很大的記憶體或者資源
        // 當輸入相同時,我們不想重複執行bigOperation函式
        const result = bigOperation(obj) 
        // 所以此時執行一次函式並將它的結果存入快取中
        cache.set(obj, result) 
    } 
    return cache.get(obj) 
} 

let obj = { /* any object */ } 
// 第一次我們沒有這個輸入作為快取,所以在第二次的時候我們才不需要執行這個函式,
const firstResult = process(obj) 
// 只需要從快取中取出結果
const secondeResult = process(obj) 
// 源物件將被從weakmap中移除
obj = null 

 

使用map,這個快取器函式應該將obj物件儲存在記憶體中。

但這將導致記憶體洩漏!

當我們對一個不再使用的物件保持引用的時候將會造成記憶體洩漏,所以如果你不再使用物件,請刪除它的任何變數引用。

使用weakmap時我們不應該使用.keys() / .values() /.entries(),因為我們不知道何時垃圾回收器會移除這個物件。

最後一個例子

動態無洩漏記憶體的訪問計數器

// 訪問計數器
let visitsCountMap = new WeakMap()

// 增加訪問計數
function countUser(user) {
  const count = visitsCountMap.get(user) || 0
  visitsCountMap.set(user, count + 1)
}

let toto = { name: "toto" }

countUser(toto) // 計算訪問次數

// 將toto物件從記憶體中移除
toto = null