@@ -51,19 +51,59 @@ const CHECKERS_BY_TAG = new Map<string, Checker<any>>()
51
51
. set ( '[object AsyncGeneratorFunction]' , checkFunctions )
52
52
. set ( '[object Function]' , checkFunctions ) ;
53
53
54
+ type ComparisonTrie = Trie < {
55
+ equal ?: boolean ;
56
+ } > ;
57
+
58
+ // Initializing checker.comparisons and checker.boundCheck as proper members of
59
+ // the DeepChecker class makes creating DeepChecker objects considerably more
60
+ // expensive in some environments, even if we initialize them to null and then
61
+ // upgrade them lazily, when needed. Instead, we store these two items of state
62
+ // in a separate Map, which gets cleaned up in the DeepChecker#release method.
63
+ const privateStateMap = new Map < DeepChecker , {
64
+ comparisons ?: ComparisonTrie ;
65
+ boundCheck ?: DeepEqualsHelper ;
66
+ } > ( ) ;
67
+
68
+ function getPrivateState ( checker : DeepChecker ) {
69
+ let state = privateStateMap . get ( checker ) ! ;
70
+ if ( ! state ) privateStateMap . set ( checker , state = Object . create ( null ) ) ;
71
+ return state ;
72
+ }
73
+
74
+ function getComparisons ( checker : DeepChecker ) : ComparisonTrie {
75
+ const state = getPrivateState ( checker ) ;
76
+ return state . comparisons || ( state . comparisons = new Trie ( false ) ) ;
77
+ }
78
+
54
79
function getBoundCheck ( checker : DeepChecker ) : DeepEqualsHelper {
55
- return checker [ "boundCheck" ] || ( checker [ "boundCheck" ] = function ( a , b ) {
56
- return checker . check ( a , b ) ;
57
- } ) ;
80
+ const state = getPrivateState ( checker ) ;
81
+ return state . boundCheck || (
82
+ state . boundCheck = ( a , b ) => checker . check ( a , b )
83
+ ) ;
58
84
}
59
85
86
+ const checkerPool : DeepChecker [ ] = [ ] ;
87
+ const CHECKER_POOL_TARGET_SIZE = 5 ;
88
+
60
89
export class DeepChecker {
61
- // Initialized lazily because not always needed .
62
- private comparisons : null | Trie < { equal ?: boolean ; } > = null ;
90
+ // Use DeepChecker.acquire() instead of new DeepChecker .
91
+ protected constructor ( ) { }
63
92
64
- // Initialized lazily because needed only when custom deepEqualsMethod methods
65
- // are in use.
66
- private boundCheck : null | DeepEqualsHelper = null ;
93
+ static acquire ( ) {
94
+ return checkerPool . pop ( ) || new DeepChecker ( ) ;
95
+ }
96
+
97
+ public release ( ) {
98
+ // If privateStateMap was a WeakMap, we wouldn't necessarily need to perform
99
+ // this cleanup, but not all environments have a (performant) implementation
100
+ // of WeakMap, and the cleanup is easy enough:
101
+ privateStateMap . delete ( this ) ;
102
+
103
+ if ( checkerPool . length < CHECKER_POOL_TARGET_SIZE ) {
104
+ checkerPool . push ( this ) ;
105
+ }
106
+ }
67
107
68
108
public check ( a : any , b : any ) : boolean {
69
109
// If the two values are strictly equal, our job is easy.
@@ -89,9 +129,7 @@ export class DeepChecker {
89
129
90
130
const found =
91
131
bothNonNullObjects &&
92
- ( this . comparisons || (
93
- this . comparisons = new Trie ( false )
94
- ) ) . lookup ( a , b ) ;
132
+ getComparisons ( this ) . lookup ( a , b ) ;
95
133
96
134
// Though cyclic references can make an object graph appear infinite from
97
135
// the perspective of a depth-first traversal, the graph still contains a
0 commit comments