]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
refactor(reactivity): optimize child effect scope dereferencing (#4184)
authorBas van Meurs <bvanmeurs1985@gmail.com>
Thu, 29 Jul 2021 14:26:24 +0000 (16:26 +0200)
committerGitHub <noreply@github.com>
Thu, 29 Jul 2021 14:26:24 +0000 (10:26 -0400)
packages/reactivity/__tests__/effectScope.spec.ts
packages/reactivity/src/effectScope.ts

index 55073470b81f7a5ea70366e79fd8f7534cda5d63..e34723cc3d104d4aea368fb7f03ca42ac4d25a3f 100644 (file)
@@ -77,8 +77,9 @@ describe('reactivity/effect/scope', () => {
       })
     })
 
-    expect(scope.effects.length).toBe(2)
-    expect(scope.effects[1]).toBeInstanceOf(EffectScope)
+    expect(scope.effects.length).toBe(1)
+    expect(scope.scopes.length).toBe(1)
+    expect(scope.scopes[0]).toBeInstanceOf(EffectScope)
 
     expect(dummy).toBe(0)
     counter.num = 7
@@ -194,9 +195,9 @@ describe('reactivity/effect/scope', () => {
   it('should derefence child scope from parent scope after stopping child scope (no memleaks)', async () => {
     const parent = new EffectScope()
     const child = parent.run(() => new EffectScope())!
-    expect(parent.effects.includes(child)).toBe(true)
+    expect(parent.scopes.includes(child)).toBe(true)
     child.stop()
-    expect(parent.effects.includes(child)).toBe(false)
+    expect(parent.scopes.includes(child)).toBe(false)
   })
 
   it('test with higher level APIs', async () => {
index 0526f90d9d3302effb0d58e1b84de150892151c1..e88abfc20cff9f0bf85cc84087be05de294ce939 100644 (file)
@@ -1,4 +1,3 @@
-import { remove } from '@vue/shared'
 import { ReactiveEffect } from './effect'
 import { warn } from './warning'
 
@@ -7,17 +6,23 @@ const effectScopeStack: EffectScope[] = []
 
 export class EffectScope {
   active = true
-  effects: (ReactiveEffect | EffectScope)[] = []
+  effects: ReactiveEffect[] = []
   cleanups: (() => void)[] = []
   parent: EffectScope | undefined
+  private children: EffectScope[] | undefined
+  private parentIndex: number | undefined
 
   constructor(detached = false) {
     if (!detached) {
-      recordEffectScope(this)
-      this.parent = activeEffectScope
+      this.recordEffectScope()
     }
   }
 
+  get scopes(): EffectScope[] {
+    if (!this.children) this.children = []
+    return this.children
+  }
+
   run<T>(fn: () => T): T | undefined {
     if (this.active) {
       try {
@@ -45,14 +50,31 @@ export class EffectScope {
     }
   }
 
-  stop(fromParent = false) {
+  stop() {
     if (this.active) {
-      this.effects.forEach(e => e.stop(true))
+      this.effects.forEach(e => e.stop())
+      this.children?.forEach(e => e.stop())
       this.cleanups.forEach(cleanup => cleanup())
+      this.parent?.derefChildScope(this)
       this.active = false
-      if (!fromParent && this.parent) {
-        remove(this.parent.effects, this)
-      }
+    }
+  }
+
+  private recordEffectScope() {
+    const parent = activeEffectScope
+    if (parent && parent.active) {
+      this.parent = parent
+      this.parentIndex = parent.scopes.push(this) - 1
+    }
+  }
+
+  private derefChildScope(scope: EffectScope) {
+    // reuse the freed index by moving the last array entry
+    const last = this.scopes.pop()
+    if (last && last !== scope) {
+      const childIndex = scope.parentIndex!
+      this.scopes[childIndex] = last
+      last.parentIndex = childIndex
     }
   }
 }
@@ -62,7 +84,7 @@ export function effectScope(detached?: boolean) {
 }
 
 export function recordEffectScope(
-  effect: ReactiveEffect | EffectScope,
+  effect: ReactiveEffect,
   scope?: EffectScope | null
 ) {
   scope = scope || activeEffectScope