mobx 7.0.4 → 7.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # mobx
2
2
 
3
+ ## 7.0.5
4
+
5
+ ### Patch Changes
6
+
7
+ - [`105d66d73529812a8eddc0847891e7f0c574e129`](https://github.com/mobxjs/mobx/commit/105d66d73529812a8eddc0847891e7f0c574e129) [#4713](https://github.com/mobxjs/mobx/pull/4713) Thanks [@xaviergonz](https://github.com/xaviergonz)! - perf: reduce memory used by large, mostly unobserved state. `intercept` / `observe` handler arrays are allocated at their exact size, an observable's internal observers `Set` is released once its last observer leaves, observable objects created with the same `deep` / `autoBind` options share one default annotation instead of allocating their own, and `endBatch()` skips the unobservation pass when nothing is pending.
8
+
3
9
  ## 7.0.4
4
10
 
5
11
  ### Patch Changes
@@ -820,6 +820,19 @@ function createAutoAnnotation(options) {
820
820
  extend_
821
821
  };
822
822
  }
823
+ // The auto annotation only depends on `deep` and `autoBind`, so share one instance per combination
824
+ // instead of allocating one for every object created with options
825
+ const autoAnnotations = [autoAnnotation];
826
+ function getAutoAnnotation(options) {
827
+ var _autoAnnotations$idx;
828
+ const deep = options.deep !== false;
829
+ const autoBind = !!options.autoBind;
830
+ const idx = (deep ? 0 : 1) | (autoBind ? 2 : 0);
831
+ return (_autoAnnotations$idx = autoAnnotations[idx]) != null ? _autoAnnotations$idx : autoAnnotations[idx] = createAutoAnnotation({
832
+ deep,
833
+ autoBind
834
+ });
835
+ }
823
836
  function make_$1(adm, key, descriptor, source) {
824
837
  var _this$options_3, _this$options_4;
825
838
  // getter -> computed
@@ -936,7 +949,7 @@ function getEnhancerFromOptions(options) {
936
949
  }
937
950
  function getAnnotationFromOptions(options) {
938
951
  var _options$defaultDecor;
939
- return options ? (_options$defaultDecor = options.defaultDecorator) != null ? _options$defaultDecor : createAutoAnnotation(options) : undefined;
952
+ return options ? (_options$defaultDecor = options.defaultDecorator) != null ? _options$defaultDecor : getAutoAnnotation(options) : undefined;
940
953
  }
941
954
  function getEnhancerFromAnnotation(annotation) {
942
955
  var _annotation$options_$, _annotation$options_;
@@ -1988,34 +2001,41 @@ function endBatch() {
1988
2001
  // the nested call instead of recursing; the outer loop re-reads list.length on
1989
2002
  // every iteration, so it picks up anything the nested dispose() pushes onto the
1990
2003
  // same pendingUnobservations array.
1991
- if (!globalState.isRunningUnobservations) {
1992
- globalState.isRunningUnobservations = true;
1993
- try {
1994
- const list = globalState.pendingUnobservations;
1995
- for (let i = 0; i < list.length; i++) {
1996
- const observable = list[i];
1997
- observable.isPendingUnobservation = false;
1998
- if (!observable.observers_ || observable.observers_.size === 0) {
1999
- if (observable.isBeingObserved) {
2000
- // if this observable had reactive observers, trigger the hooks
2001
- observable.isBeingObserved = false;
2002
- observable.onBUO();
2003
- }
2004
- if (observable instanceof ComputedValue) {
2005
- // computed values are automatically teared down when the last observer leaves
2006
- // this process happens recursively, this computed might be the last observabe of another, etc..
2007
- observable.suspend_();
2008
- }
2009
- }
2004
+ if (!globalState.isRunningUnobservations && globalState.pendingUnobservations.length > 0) {
2005
+ runPendingUnobservations();
2006
+ }
2007
+ }
2008
+ }
2009
+ // Only called when there is something to unobserve, so the common endBatch() skips the
2010
+ // try/finally and the pendingUnobservations reallocation entirely
2011
+ function runPendingUnobservations() {
2012
+ globalState.isRunningUnobservations = true;
2013
+ try {
2014
+ const list = globalState.pendingUnobservations;
2015
+ for (let i = 0; i < list.length; i++) {
2016
+ const observable = list[i];
2017
+ observable.isPendingUnobservation = false;
2018
+ if (!observable.observers_ || observable.observers_.size === 0) {
2019
+ // release the empty Set so unobserved atoms don't keep paying for it
2020
+ observable.observers_ = null;
2021
+ if (observable.isBeingObserved) {
2022
+ // if this observable had reactive observers, trigger the hooks
2023
+ observable.isBeingObserved = false;
2024
+ observable.onBUO();
2025
+ }
2026
+ if (observable instanceof ComputedValue) {
2027
+ // computed values are automatically teared down when the last observer leaves
2028
+ // this process happens recursively, this computed might be the last observabe of another, etc..
2029
+ observable.suspend_();
2010
2030
  }
2011
- globalState.pendingUnobservations = [];
2012
- } finally {
2013
- // Always release the guard, even if an onBUO handler (user code) threw,
2014
- // otherwise every future endBatch() would see isRunningUnobservations
2015
- // stuck true and silently stop draining pendingUnobservations forever.
2016
- globalState.isRunningUnobservations = false;
2017
2031
  }
2018
2032
  }
2033
+ globalState.pendingUnobservations = [];
2034
+ } finally {
2035
+ // Always release the guard, even if an onBUO handler (user code) threw,
2036
+ // otherwise every future endBatch() would see isRunningUnobservations
2037
+ // stuck true and silently stop draining pendingUnobservations forever.
2038
+ globalState.isRunningUnobservations = false;
2019
2039
  }
2020
2040
  }
2021
2041
  /**
@@ -3294,8 +3314,13 @@ function hasInterceptors(interceptable) {
3294
3314
  return interceptable.interceptors_ !== undefined && interceptable.interceptors_.length > 0;
3295
3315
  }
3296
3316
  function registerInterceptor(interceptable, handler) {
3297
- const interceptors = interceptable.interceptors_ || (interceptable.interceptors_ = []);
3298
- interceptors.push(handler);
3317
+ let interceptors = interceptable.interceptors_;
3318
+ if (interceptors) {
3319
+ interceptors.push(handler);
3320
+ } else {
3321
+ // Allocate at the exact size: pushing onto an empty array over-allocates its backing store
3322
+ interceptors = interceptable.interceptors_ = [handler];
3323
+ }
3299
3324
  return once(() => {
3300
3325
  const idx = interceptors.indexOf(handler);
3301
3326
  if (idx !== -1) {
@@ -3327,8 +3352,13 @@ function hasListeners(listenable) {
3327
3352
  return listenable.changeListeners_ !== undefined && listenable.changeListeners_.length > 0;
3328
3353
  }
3329
3354
  function registerListener(listenable, handler) {
3330
- const listeners = listenable.changeListeners_ || (listenable.changeListeners_ = []);
3331
- listeners.push(handler);
3355
+ let listeners = listenable.changeListeners_;
3356
+ if (listeners) {
3357
+ listeners.push(handler);
3358
+ } else {
3359
+ // Allocate at the exact size: pushing onto an empty array over-allocates its backing store
3360
+ listeners = listenable.changeListeners_ = [handler];
3361
+ }
3332
3362
  return once(() => {
3333
3363
  const idx = listeners.indexOf(handler);
3334
3364
  if (idx !== -1) {