From 235ea4772ed2972914cf142da8b7ac1fb04f7585 Mon Sep 17 00:00:00 2001 From: Evan You Date: Fri, 20 Sep 2024 20:31:40 +0800 Subject: [PATCH] fix(reactivity): fix memory leak from dep instances of garbage collected objects close #11979 close #11971 --- packages/reactivity/src/dep.ts | 13 ++++++++++++- packages/reactivity/src/effect.ts | 20 +++++++++++++------- 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/packages/reactivity/src/dep.ts b/packages/reactivity/src/dep.ts index c24f123ded4..61f0a59c352 100644 --- a/packages/reactivity/src/dep.ts +++ b/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 -const targetMap = new WeakMap() + +export const targetMap: WeakMap = 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({ diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index 64e4e94be51..4428b8df256 100644 --- a/packages/reactivity/src/effect.ts +++ b/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!) } } }