Просмотр исходного кода

fix(reactivity): fix memory leak from dep instances of garbage collected objects

close #11979
close #11971
Evan You 1 год назад
Родитель
Сommit
235ea4772e
2 измененных файлов с 25 добавлено и 8 удалено
  1. 12 1
      packages/reactivity/src/dep.ts
  2. 13 7
      packages/reactivity/src/effect.ts

+ 12 - 1
packages/reactivity/src/dep.ts

@@ -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({

+ 13 - 7
packages/reactivity/src/effect.ts

@@ -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!)
     }
   }
 }