@@ -5,6 +5,8 @@ export class ReactionScope {
55 this . reactions = [ ] ;
66 this . children = [ ] ;
77 this . disposers = [ ] ;
8+ this . parent = null ;
9+ this . index = - 1 ; // this scope's slot in parent.children, for O(1) detach
810 }
911
1012 track ( reaction ) {
@@ -32,22 +34,31 @@ export class ReactionScope {
3234 child ( ) {
3335 const childScope = new ReactionScope ( ) ;
3436 childScope . parent = this ;
35- this . children . push ( childScope ) ;
37+ childScope . index = this . children . push ( childScope ) - 1 ;
3638 return childScope ;
3739 }
3840
39- dispose ( ) {
40- for ( const child of this . children ) { child . dispose ( ) ; }
41+ // Detach in O(1): a standalone dispose swap-pops itself out of `parent.children`
42+ // via its stored slot index instead of indexOf + splice, and `fromParent` lets a
43+ // parent tearing down its whole subtree skip the per-child unlink (clearing the
44+ // array once). Bulk teardown O(n²) → O(n), and no more splice-mid-iteration.
45+ // (Vue's EffectScope: same fromParent + indexed swap-pop.)
46+ dispose ( fromParent = false ) {
47+ for ( const child of this . children ) { child . dispose ( true ) ; }
4148 this . children = [ ] ;
4249 for ( const reaction of this . reactions ) { reaction . stop ( ) ; }
4350 this . reactions = [ ] ;
4451 for ( const fn of this . disposers ) { fn ( ) ; }
4552 this . disposers = [ ] ;
46- // Remove from parent to prevent accumulation across branch switches
47- if ( this . parent ) {
48- const idx = this . parent . children . indexOf ( this ) ;
49- if ( idx !== - 1 ) { this . parent . children . splice ( idx , 1 ) ; }
50- this . parent = null ;
53+ if ( ! fromParent && this . parent ) {
54+ const siblings = this . parent . children ;
55+ const last = siblings . pop ( ) ;
56+ if ( last && last !== this && this . index < siblings . length ) {
57+ siblings [ this . index ] = last ;
58+ last . index = this . index ;
59+ }
5160 }
61+ this . parent = null ;
62+ this . index = - 1 ;
5263 }
5364}
0 commit comments