]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(reactivity): fix memory leak from dep instances of garbage collected objects
authorEvan You <evan@vuejs.org>
Fri, 20 Sep 2024 12:31:40 +0000 (20:31 +0800)
committerEvan You <evan@vuejs.org>
Fri, 20 Sep 2024 12:32:40 +0000 (20:32 +0800)
close #11979
close #11971

packages/reactivity/src/dep.ts
packages/reactivity/src/effect.ts

index c24f123ded49b670983d3cc6c957965d65fd7c9e..61f0a59c352a3f655fd1dca2b4e41a3c19af6497 100644 (file)
@@ -82,6 +82,13 @@ export class Dep {
    */
   subsHead?: Link
 
+  /**
+   * For object property deps cleanup
+   */
+  target?: unknown = undefined
+  map?: KeyToDepMap = undefined
+  key?: unknown = undefined
+
   constructor(public computed?: ComputedRefImpl | undefined) {
     if (__DEV__) {
       this.subsHead = undefined
@@ -218,7 +225,8 @@ function addSub(link: Link) {
 // which maintains a Set of subscribers, but we simply store them as
 // raw Maps to reduce memory overhead.
 type KeyToDepMap = Map<any, Dep>
-const targetMap = new WeakMap<object, KeyToDepMap>()
+
+export const targetMap: WeakMap<object, KeyToDepMap> = new WeakMap()
 
 export const ITERATE_KEY: unique symbol = Symbol(
   __DEV__ ? 'Object iterate' : '',
@@ -249,6 +257,9 @@ export function track(target: object, type: TrackOpTypes, key: unknown): void {
     let dep = depsMap.get(key)
     if (!dep) {
       depsMap.set(key, (dep = new Dep()))
+      dep.target = target
+      dep.map = depsMap
+      dep.key = key
     }
     if (__DEV__) {
       dep.track({
index 64e4e94be51ea31c8bd524d0c77b0dceee72ee5e..4428b8df256281a3d440f358737af62a02f5ba7d 100644 (file)
@@ -1,7 +1,7 @@
 import { extend, hasChanged } from '@vue/shared'
 import type { ComputedRefImpl } from './computed'
 import type { TrackOpTypes, TriggerOpTypes } from './constants'
-import { type Link, globalVersion } from './dep'
+import { type Link, globalVersion, targetMap } from './dep'
 import { activeEffectScope } from './effectScope'
 import { warn } from './warning'
 
@@ -418,13 +418,19 @@ function removeSub(link: Link) {
     dep.subsHead = nextSub
   }
 
-  if (!dep.subs && dep.computed) {
+  if (!dep.subs) {
     // last subscriber removed
-    // if computed, unsubscribe it from all its deps so this computed and its
-    // value can be GCed
-    dep.computed.flags &= ~EffectFlags.TRACKING
-    for (let l = dep.computed.deps; l; l = l.nextDep) {
-      removeSub(l)
+    if (dep.computed) {
+      // if computed, unsubscribe it from all its deps so this computed and its
+      // value can be GCed
+      dep.computed.flags &= ~EffectFlags.TRACKING
+      for (let l = dep.computed.deps; l; l = l.nextDep) {
+        removeSub(l)
+      }
+    } else if (dep.map) {
+      // property dep, remove it from the owner depsMap
+      dep.map.delete(dep.key)
+      if (!dep.map.size) targetMap.delete(dep.target!)
     }
   }
 }