1
1
/** @import { Source } from '#client' */
2
2
import { DEV } from 'esm-env' ;
3
- import { get , active_effect , active_reaction , set_active_reaction } from './runtime.js' ;
3
+ import { get , active_effect , active_reaction , set_active_reaction , untrack } from './runtime.js' ;
4
4
import {
5
5
array_prototype ,
6
6
get_descriptor ,
7
7
get_prototype_of ,
8
8
is_array ,
9
- object_prototype
9
+ object_prototype ,
10
+ define_property
10
11
} from '../shared/utils.js' ;
11
12
import { state as source , set } from './reactivity/sources.js' ;
12
13
import { PROXY_PATH_SYMBOL , STATE_SYMBOL } from '#client/constants' ;
@@ -18,6 +19,19 @@ import { tracing_mode_flag } from '../flags/index.js';
18
19
// TODO move all regexes into shared module?
19
20
const regex_is_valid_identifier = / ^ [ a - z A - Z _ $ ] [ a - z A - Z _ $ 0 - 9 ] * $ / ;
20
21
22
+ // Array methods that mutate the array, according to the ECMAScript specification
23
+ const MUTATING_ARRAY_METHODS = new Set ( [
24
+ 'push' ,
25
+ 'pop' ,
26
+ 'shift' ,
27
+ 'unshift' ,
28
+ 'splice' ,
29
+ 'sort' ,
30
+ 'reverse' ,
31
+ 'copyWithin' ,
32
+ 'fill'
33
+ ] ) ;
34
+
21
35
/**
22
36
* @template T
23
37
* @param {T } value
@@ -43,6 +57,9 @@ export function proxy(value) {
43
57
var stack = DEV && tracing_mode_flag ? get_stack ( 'CreatedAt' ) : null ;
44
58
var reaction = active_reaction ;
45
59
60
+ /** @type {Map<string, Function> | null } */
61
+ var proxied_array_mutating_methods_cache = is_proxied_array ? new Map ( ) : null ;
62
+
46
63
/**
47
64
* @template T
48
65
* @param {() => T } fn
@@ -176,6 +193,44 @@ export function proxy(value) {
176
193
return v === UNINITIALIZED ? undefined : v ;
177
194
}
178
195
196
+ // if this is a proxied array and the property is a mutating method, return a
197
+ // wrapper that executes the native method inside `untrack`, preventing the
198
+ // current reaction from accidentally depending on `length` (or other
199
+ // internals) that the algorithm reads.
200
+ if ( is_proxied_array && typeof prop === 'string' && MUTATING_ARRAY_METHODS . has ( prop ) ) {
201
+ /** @type {Map<string, Function> } */
202
+ const mutating_methods_cache = /** @type {Map<string, Function> } */ ( proxied_array_mutating_methods_cache ) ;
203
+
204
+ var cached_method = mutating_methods_cache . get ( prop ) ;
205
+
206
+ if ( cached_method === undefined ) {
207
+ /**
208
+ * wrapper executes the native mutating method inside `untrack` so that
209
+ * any implicit `length` reads are ignored.
210
+ * @this any
211
+ * @param {...any } args
212
+ */
213
+ cached_method = function ( ...args ) {
214
+ // preserve correct `this` binding and forward result.
215
+ // eslint-disable-next-line prefer-spread
216
+ return untrack ( ( ) => /** @type {any } */ ( array_prototype ) [ prop ] . apply ( this , args ) ) ;
217
+ } ;
218
+
219
+ // give the wrapper a meaningful name for better debugging
220
+ try {
221
+ define_property ( cached_method , 'name' , {
222
+ value : `proxied_array_untracked_${ /** @type {string } */ ( prop ) } `
223
+ } ) ;
224
+ } catch {
225
+ // property might be non-configurable in some engines
226
+ }
227
+
228
+ mutating_methods_cache . set ( prop , cached_method ) ;
229
+ }
230
+
231
+ return cached_method ;
232
+ }
233
+
179
234
return Reflect . get ( target , prop , receiver ) ;
180
235
} ,
181
236
0 commit comments