@naturalcycles/js-lib 14.214.0 → 14.215.0

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.
@@ -1,11 +1,63 @@
1
1
  /**
2
- * deepEquals, but after JSON stringify/parse
3
- * E.g if object A has extra properties with value `undefined` -
4
- * it won't be _deepEquals true, but will be _deepJsonEquals true.
5
- * (because JSON.stringify removes undefined properties).
2
+ Returns true if a and b are deeply equal.
3
+
4
+ Equality is checked recursively, with the following rules/caveats:
5
+ - Primitive values are checked with ===
6
+ - NaN === NaN
7
+ - Array length should be the same, and every value should be equal
8
+ - Sets are checked similarly to arrays (but order doesn't matter in Sets)
9
+ - Objects and Maps are checked that all values match. Undefined values are treated the same as absent key (important!)
10
+ - Order of object/Map keys doesn't matter, unlike when comparing JSON.stringify(a) === JSON.stringify(b)
11
+ - Regex are compared by their source and flags
12
+ - Functions are compared by their `.toString`
13
+ - Any object that overrides `.toString()` is compared by that (e.g Function)
14
+ - Any object that overrides `.valueOf()` is compared by that (e.g Date)
15
+
16
+ What are the differences between various deep-equality functions?
17
+ There are:
18
+ - _deepEquals
19
+ - _deepJsonEquals
20
+ - _jsonEquals
21
+
22
+ _deepEquals uses "common sense" equality.
23
+ It tries to work "as you would expect it to".
24
+ With the important caveat that undefined values are treated the same as absent key.
25
+ So, _deepEquals should be the first choice.
26
+ It's also the most performant of 3.
27
+
28
+ _deepJsonEquals uses different logic, that's often not what you expect.
29
+ It should be used to compare objects of how they would look after "passing via JSON.stringify",
30
+ for example when you return it over the API to the Frontend,
31
+ or when you pass it to be saved to the Database.
32
+ If some object has custom .toJSON() implementation - it'll invoke that (similar to JSON.stringify).
33
+ For these cases - it can be better than _deepEquals.
34
+ And it's better than _jsonEquals, because it doesn't fail/depend on object key order.
35
+
36
+ _jsonEquals is simply JSON.stringify(a) === JSON.stringify(b).
37
+ It's the simplest implementation, but also the slowest of 3.
38
+
39
+ TLDR: _deepEquals should be useful in most of the cases, start there.
40
+ */
41
+ export declare function _deepEquals(a: any, b: any): boolean;
42
+ /**
43
+ Returns true if a and b are deeply equal.
44
+
45
+ Equality is checked in the same way as if both arguments are processed via
46
+ JSON.stringify and JSON.parse:
47
+ - undefined values are removed, undefined values in an array are turned into `null`, etc.
48
+ - Any Regex, Map, Set, Function stringifies to {}.
49
+ - Date stringifies to its IsoDateTimeString representation.
50
+ - Any object that implements toJSON is compared by the output of its toJSON().
51
+ - NaN stringifies to null
52
+ - Order of object keys does not matter, unlike when comparing JSON.stringify(a) === JSON.stringify(b)
53
+
54
+ See _deepEquals docs for more details and comparison.
6
55
  */
7
56
  export declare function _deepJsonEquals(a: any, b: any): boolean;
8
57
  /**
9
- * Based on: https://github.com/epoberezkin/fast-deep-equal/
58
+ * Shortcut for JSON.stringify(a) === JSON.stringify(b)
59
+ *
60
+ * Simplest "deep equals" implementation, but also the slowest,
61
+ * and not robust, in the sense that it depends on the order of object keys.
10
62
  */
11
- export declare function _deepEquals(a: any, b: any): boolean;
63
+ export declare function _jsonEquals(a: any, b: any): boolean;
@@ -1,75 +1,169 @@
1
1
  "use strict";
2
+ // Heavily inspired by https://github.com/epoberezkin/fast-deep-equal
2
3
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports._deepEquals = exports._deepJsonEquals = void 0;
4
- const isArray = Array.isArray;
5
- const keyList = Object.keys;
6
- const hasProp = Object.prototype.hasOwnProperty;
4
+ exports._jsonEquals = exports._deepJsonEquals = exports._deepEquals = void 0;
7
5
  /**
8
- * deepEquals, but after JSON stringify/parse
9
- * E.g if object A has extra properties with value `undefined` -
10
- * it won't be _deepEquals true, but will be _deepJsonEquals true.
11
- * (because JSON.stringify removes undefined properties).
12
- */
13
- function _deepJsonEquals(a, b) {
14
- if (a === b)
15
- return true;
16
- const aj = JSON.stringify(a);
17
- const bj = JSON.stringify(b);
18
- if (aj === bj)
19
- return true;
20
- return _deepEquals(JSON.parse(aj), JSON.parse(bj));
21
- }
22
- exports._deepJsonEquals = _deepJsonEquals;
23
- /**
24
- * Based on: https://github.com/epoberezkin/fast-deep-equal/
6
+ Returns true if a and b are deeply equal.
7
+
8
+ Equality is checked recursively, with the following rules/caveats:
9
+ - Primitive values are checked with ===
10
+ - NaN === NaN
11
+ - Array length should be the same, and every value should be equal
12
+ - Sets are checked similarly to arrays (but order doesn't matter in Sets)
13
+ - Objects and Maps are checked that all values match. Undefined values are treated the same as absent key (important!)
14
+ - Order of object/Map keys doesn't matter, unlike when comparing JSON.stringify(a) === JSON.stringify(b)
15
+ - Regex are compared by their source and flags
16
+ - Functions are compared by their `.toString`
17
+ - Any object that overrides `.toString()` is compared by that (e.g Function)
18
+ - Any object that overrides `.valueOf()` is compared by that (e.g Date)
19
+
20
+ What are the differences between various deep-equality functions?
21
+ There are:
22
+ - _deepEquals
23
+ - _deepJsonEquals
24
+ - _jsonEquals
25
+
26
+ _deepEquals uses "common sense" equality.
27
+ It tries to work "as you would expect it to".
28
+ With the important caveat that undefined values are treated the same as absent key.
29
+ So, _deepEquals should be the first choice.
30
+ It's also the most performant of 3.
31
+
32
+ _deepJsonEquals uses different logic, that's often not what you expect.
33
+ It should be used to compare objects of how they would look after "passing via JSON.stringify",
34
+ for example when you return it over the API to the Frontend,
35
+ or when you pass it to be saved to the Database.
36
+ If some object has custom .toJSON() implementation - it'll invoke that (similar to JSON.stringify).
37
+ For these cases - it can be better than _deepEquals.
38
+ And it's better than _jsonEquals, because it doesn't fail/depend on object key order.
39
+
40
+ _jsonEquals is simply JSON.stringify(a) === JSON.stringify(b).
41
+ It's the simplest implementation, but also the slowest of 3.
42
+
43
+ TLDR: _deepEquals should be useful in most of the cases, start there.
25
44
  */
26
45
  function _deepEquals(a, b) {
27
46
  if (a === b)
28
47
  return true;
48
+ if (Number.isNaN(a)) {
49
+ return Number.isNaN(b);
50
+ }
29
51
  if (a && b && typeof a === 'object' && typeof b === 'object') {
30
- const arrA = isArray(a);
31
- const arrB = isArray(b);
32
- let i;
33
- let length;
34
- let key;
35
- if (arrA !== arrB)
52
+ if (a.constructor !== b.constructor)
36
53
  return false;
37
- if (arrA && arrB) {
38
- length = a.length;
54
+ if (Array.isArray(a)) {
55
+ const length = a.length;
39
56
  if (length !== b.length)
40
57
  return false;
41
- for (i = length; i-- !== 0;)
58
+ for (let i = length; i-- !== 0;) {
42
59
  if (!_deepEquals(a[i], b[i]))
43
60
  return false;
61
+ }
44
62
  return true;
45
63
  }
46
- const dateA = a instanceof Date;
47
- const dateB = b instanceof Date;
48
- if (dateA !== dateB)
49
- return false;
50
- if (dateA && dateB)
51
- return a.getTime() === b.getTime();
52
- const regexpA = a instanceof RegExp;
53
- const regexpB = b instanceof RegExp;
54
- if (regexpA !== regexpB)
55
- return false;
56
- if (regexpA && regexpB)
57
- return a.toString() === b.toString();
58
- const keys = keyList(a);
59
- length = keys.length;
60
- if (length !== keyList(b).length)
61
- return false;
62
- for (i = length; i-- !== 0;)
63
- if (!hasProp.call(b, keys[i]))
64
+ if (a instanceof Map && b instanceof Map) {
65
+ for (const key of new Set([...a.keys(), ...b.keys()])) {
66
+ if (!_deepEquals(a.get(key), b.get(key)))
67
+ return false;
68
+ }
69
+ return true;
70
+ }
71
+ if (a instanceof Set && b instanceof Set) {
72
+ if (a.size !== b.size)
64
73
  return false;
65
- for (i = length; i-- !== 0;) {
66
- key = keys[i];
74
+ for (const key of a) {
75
+ if (!b.has(key))
76
+ return false;
77
+ }
78
+ return true;
79
+ }
80
+ if (a.constructor === RegExp)
81
+ return a.source === b.source && a.flags === b.flags;
82
+ if (a.valueOf !== Object.prototype.valueOf)
83
+ return a.valueOf() === b.valueOf();
84
+ if (a.toString !== Object.prototype.toString)
85
+ return a.toString() === b.toString();
86
+ for (const key of new Set([...Object.keys(a), ...Object.keys(b)])) {
67
87
  if (!_deepEquals(a[key], b[key]))
68
88
  return false;
69
89
  }
70
90
  return true;
71
91
  }
72
- // eslint-disable-next-line no-self-compare
73
- return a !== a && b !== b;
92
+ return a === b;
74
93
  }
75
94
  exports._deepEquals = _deepEquals;
95
+ /**
96
+ Returns true if a and b are deeply equal.
97
+
98
+ Equality is checked in the same way as if both arguments are processed via
99
+ JSON.stringify and JSON.parse:
100
+ - undefined values are removed, undefined values in an array are turned into `null`, etc.
101
+ - Any Regex, Map, Set, Function stringifies to {}.
102
+ - Date stringifies to its IsoDateTimeString representation.
103
+ - Any object that implements toJSON is compared by the output of its toJSON().
104
+ - NaN stringifies to null
105
+ - Order of object keys does not matter, unlike when comparing JSON.stringify(a) === JSON.stringify(b)
106
+
107
+ See _deepEquals docs for more details and comparison.
108
+ */
109
+ function _deepJsonEquals(a, b) {
110
+ if (a === b)
111
+ return true;
112
+ if (Number.isNaN(a)) {
113
+ a = null;
114
+ }
115
+ else if (typeof a === 'function') {
116
+ a = undefined;
117
+ }
118
+ else if (a && typeof a === 'object') {
119
+ if (a instanceof Date) {
120
+ a = a.valueOf();
121
+ }
122
+ else if ('toJSON' in a) {
123
+ a = a.toJSON();
124
+ }
125
+ }
126
+ if (Number.isNaN(b)) {
127
+ b = null;
128
+ }
129
+ else if (typeof b === 'function') {
130
+ b = undefined;
131
+ }
132
+ else if (b && typeof b === 'object') {
133
+ if (b instanceof Date) {
134
+ b = b.valueOf();
135
+ }
136
+ else if ('toJSON' in b) {
137
+ b = b.toJSON();
138
+ }
139
+ }
140
+ if (a && b && typeof a === 'object' && typeof b === 'object') {
141
+ if (Array.isArray(a)) {
142
+ const length = a.length;
143
+ if (length !== b.length)
144
+ return false;
145
+ for (let i = length; i-- !== 0;) {
146
+ if (!_deepJsonEquals(a[i], b[i]))
147
+ return false;
148
+ }
149
+ return true;
150
+ }
151
+ for (const key of new Set([...Object.keys(a), ...Object.keys(b)])) {
152
+ if (!_deepJsonEquals(a[key], b[key]))
153
+ return false;
154
+ }
155
+ return true;
156
+ }
157
+ return a === b;
158
+ }
159
+ exports._deepJsonEquals = _deepJsonEquals;
160
+ /**
161
+ * Shortcut for JSON.stringify(a) === JSON.stringify(b)
162
+ *
163
+ * Simplest "deep equals" implementation, but also the slowest,
164
+ * and not robust, in the sense that it depends on the order of object keys.
165
+ */
166
+ function _jsonEquals(a, b) {
167
+ return JSON.stringify(a) === JSON.stringify(b);
168
+ }
169
+ exports._jsonEquals = _jsonEquals;
@@ -1,70 +1,163 @@
1
- const isArray = Array.isArray;
2
- const keyList = Object.keys;
3
- const hasProp = Object.prototype.hasOwnProperty;
1
+ // Heavily inspired by https://github.com/epoberezkin/fast-deep-equal
4
2
  /**
5
- * deepEquals, but after JSON stringify/parse
6
- * E.g if object A has extra properties with value `undefined` -
7
- * it won't be _deepEquals true, but will be _deepJsonEquals true.
8
- * (because JSON.stringify removes undefined properties).
3
+ Returns true if a and b are deeply equal.
4
+
5
+ Equality is checked recursively, with the following rules/caveats:
6
+ - Primitive values are checked with ===
7
+ - NaN === NaN
8
+ - Array length should be the same, and every value should be equal
9
+ - Sets are checked similarly to arrays (but order doesn't matter in Sets)
10
+ - Objects and Maps are checked that all values match. Undefined values are treated the same as absent key (important!)
11
+ - Order of object/Map keys doesn't matter, unlike when comparing JSON.stringify(a) === JSON.stringify(b)
12
+ - Regex are compared by their source and flags
13
+ - Functions are compared by their `.toString`
14
+ - Any object that overrides `.toString()` is compared by that (e.g Function)
15
+ - Any object that overrides `.valueOf()` is compared by that (e.g Date)
16
+
17
+ What are the differences between various deep-equality functions?
18
+ There are:
19
+ - _deepEquals
20
+ - _deepJsonEquals
21
+ - _jsonEquals
22
+
23
+ _deepEquals uses "common sense" equality.
24
+ It tries to work "as you would expect it to".
25
+ With the important caveat that undefined values are treated the same as absent key.
26
+ So, _deepEquals should be the first choice.
27
+ It's also the most performant of 3.
28
+
29
+ _deepJsonEquals uses different logic, that's often not what you expect.
30
+ It should be used to compare objects of how they would look after "passing via JSON.stringify",
31
+ for example when you return it over the API to the Frontend,
32
+ or when you pass it to be saved to the Database.
33
+ If some object has custom .toJSON() implementation - it'll invoke that (similar to JSON.stringify).
34
+ For these cases - it can be better than _deepEquals.
35
+ And it's better than _jsonEquals, because it doesn't fail/depend on object key order.
36
+
37
+ _jsonEquals is simply JSON.stringify(a) === JSON.stringify(b).
38
+ It's the simplest implementation, but also the slowest of 3.
39
+
40
+ TLDR: _deepEquals should be useful in most of the cases, start there.
9
41
  */
10
- export function _deepJsonEquals(a, b) {
42
+ export function _deepEquals(a, b) {
11
43
  if (a === b)
12
44
  return true;
13
- const aj = JSON.stringify(a);
14
- const bj = JSON.stringify(b);
15
- if (aj === bj)
45
+ if (Number.isNaN(a)) {
46
+ return Number.isNaN(b);
47
+ }
48
+ if (a && b && typeof a === 'object' && typeof b === 'object') {
49
+ if (a.constructor !== b.constructor)
50
+ return false;
51
+ if (Array.isArray(a)) {
52
+ const length = a.length;
53
+ if (length !== b.length)
54
+ return false;
55
+ for (let i = length; i-- !== 0;) {
56
+ if (!_deepEquals(a[i], b[i]))
57
+ return false;
58
+ }
59
+ return true;
60
+ }
61
+ if (a instanceof Map && b instanceof Map) {
62
+ for (const key of new Set([...a.keys(), ...b.keys()])) {
63
+ if (!_deepEquals(a.get(key), b.get(key)))
64
+ return false;
65
+ }
66
+ return true;
67
+ }
68
+ if (a instanceof Set && b instanceof Set) {
69
+ if (a.size !== b.size)
70
+ return false;
71
+ for (const key of a) {
72
+ if (!b.has(key))
73
+ return false;
74
+ }
75
+ return true;
76
+ }
77
+ if (a.constructor === RegExp)
78
+ return a.source === b.source && a.flags === b.flags;
79
+ if (a.valueOf !== Object.prototype.valueOf)
80
+ return a.valueOf() === b.valueOf();
81
+ if (a.toString !== Object.prototype.toString)
82
+ return a.toString() === b.toString();
83
+ for (const key of new Set([...Object.keys(a), ...Object.keys(b)])) {
84
+ if (!_deepEquals(a[key], b[key]))
85
+ return false;
86
+ }
16
87
  return true;
17
- return _deepEquals(JSON.parse(aj), JSON.parse(bj));
88
+ }
89
+ return a === b;
18
90
  }
19
91
  /**
20
- * Based on: https://github.com/epoberezkin/fast-deep-equal/
92
+ Returns true if a and b are deeply equal.
93
+
94
+ Equality is checked in the same way as if both arguments are processed via
95
+ JSON.stringify and JSON.parse:
96
+ - undefined values are removed, undefined values in an array are turned into `null`, etc.
97
+ - Any Regex, Map, Set, Function stringifies to {}.
98
+ - Date stringifies to its IsoDateTimeString representation.
99
+ - Any object that implements toJSON is compared by the output of its toJSON().
100
+ - NaN stringifies to null
101
+ - Order of object keys does not matter, unlike when comparing JSON.stringify(a) === JSON.stringify(b)
102
+
103
+ See _deepEquals docs for more details and comparison.
21
104
  */
22
- export function _deepEquals(a, b) {
105
+ export function _deepJsonEquals(a, b) {
23
106
  if (a === b)
24
107
  return true;
108
+ if (Number.isNaN(a)) {
109
+ a = null;
110
+ }
111
+ else if (typeof a === 'function') {
112
+ a = undefined;
113
+ }
114
+ else if (a && typeof a === 'object') {
115
+ if (a instanceof Date) {
116
+ a = a.valueOf();
117
+ }
118
+ else if ('toJSON' in a) {
119
+ a = a.toJSON();
120
+ }
121
+ }
122
+ if (Number.isNaN(b)) {
123
+ b = null;
124
+ }
125
+ else if (typeof b === 'function') {
126
+ b = undefined;
127
+ }
128
+ else if (b && typeof b === 'object') {
129
+ if (b instanceof Date) {
130
+ b = b.valueOf();
131
+ }
132
+ else if ('toJSON' in b) {
133
+ b = b.toJSON();
134
+ }
135
+ }
25
136
  if (a && b && typeof a === 'object' && typeof b === 'object') {
26
- const arrA = isArray(a);
27
- const arrB = isArray(b);
28
- let i;
29
- let length;
30
- let key;
31
- if (arrA !== arrB)
32
- return false;
33
- if (arrA && arrB) {
34
- length = a.length;
137
+ if (Array.isArray(a)) {
138
+ const length = a.length;
35
139
  if (length !== b.length)
36
140
  return false;
37
- for (i = length; i-- !== 0;)
38
- if (!_deepEquals(a[i], b[i]))
141
+ for (let i = length; i-- !== 0;) {
142
+ if (!_deepJsonEquals(a[i], b[i]))
39
143
  return false;
144
+ }
40
145
  return true;
41
146
  }
42
- const dateA = a instanceof Date;
43
- const dateB = b instanceof Date;
44
- if (dateA !== dateB)
45
- return false;
46
- if (dateA && dateB)
47
- return a.getTime() === b.getTime();
48
- const regexpA = a instanceof RegExp;
49
- const regexpB = b instanceof RegExp;
50
- if (regexpA !== regexpB)
51
- return false;
52
- if (regexpA && regexpB)
53
- return a.toString() === b.toString();
54
- const keys = keyList(a);
55
- length = keys.length;
56
- if (length !== keyList(b).length)
57
- return false;
58
- for (i = length; i-- !== 0;)
59
- if (!hasProp.call(b, keys[i]))
60
- return false;
61
- for (i = length; i-- !== 0;) {
62
- key = keys[i];
63
- if (!_deepEquals(a[key], b[key]))
147
+ for (const key of new Set([...Object.keys(a), ...Object.keys(b)])) {
148
+ if (!_deepJsonEquals(a[key], b[key]))
64
149
  return false;
65
150
  }
66
151
  return true;
67
152
  }
68
- // eslint-disable-next-line no-self-compare
69
- return a !== a && b !== b;
153
+ return a === b;
154
+ }
155
+ /**
156
+ * Shortcut for JSON.stringify(a) === JSON.stringify(b)
157
+ *
158
+ * Simplest "deep equals" implementation, but also the slowest,
159
+ * and not robust, in the sense that it depends on the order of object keys.
160
+ */
161
+ export function _jsonEquals(a, b) {
162
+ return JSON.stringify(a) === JSON.stringify(b);
70
163
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@naturalcycles/js-lib",
3
- "version": "14.214.0",
3
+ "version": "14.215.0",
4
4
  "scripts": {
5
5
  "prepare": "husky",
6
6
  "build-prod": "build-prod-esm-cjs",
@@ -25,9 +25,6 @@
25
25
  "vitepress": "^1.0.0",
26
26
  "vue": "^3.2.45"
27
27
  },
28
- "resolutions": {
29
- "expect-type": "0.15.0"
30
- },
31
28
  "files": [
32
29
  "dist",
33
30
  "dist-esm",
@@ -1,68 +1,159 @@
1
- const isArray = Array.isArray
2
- const keyList = Object.keys
3
- const hasProp = Object.prototype.hasOwnProperty
1
+ // Heavily inspired by https://github.com/epoberezkin/fast-deep-equal
4
2
 
5
3
  /**
6
- * deepEquals, but after JSON stringify/parse
7
- * E.g if object A has extra properties with value `undefined` -
8
- * it won't be _deepEquals true, but will be _deepJsonEquals true.
9
- * (because JSON.stringify removes undefined properties).
10
- */
11
- export function _deepJsonEquals(a: any, b: any): boolean {
12
- if (a === b) return true
13
- const aj = JSON.stringify(a)
14
- const bj = JSON.stringify(b)
15
- if (aj === bj) return true
16
- return _deepEquals(JSON.parse(aj), JSON.parse(bj))
17
- }
18
-
19
- /**
20
- * Based on: https://github.com/epoberezkin/fast-deep-equal/
4
+ Returns true if a and b are deeply equal.
5
+
6
+ Equality is checked recursively, with the following rules/caveats:
7
+ - Primitive values are checked with ===
8
+ - NaN === NaN
9
+ - Array length should be the same, and every value should be equal
10
+ - Sets are checked similarly to arrays (but order doesn't matter in Sets)
11
+ - Objects and Maps are checked that all values match. Undefined values are treated the same as absent key (important!)
12
+ - Order of object/Map keys doesn't matter, unlike when comparing JSON.stringify(a) === JSON.stringify(b)
13
+ - Regex are compared by their source and flags
14
+ - Functions are compared by their `.toString`
15
+ - Any object that overrides `.toString()` is compared by that (e.g Function)
16
+ - Any object that overrides `.valueOf()` is compared by that (e.g Date)
17
+
18
+ What are the differences between various deep-equality functions?
19
+ There are:
20
+ - _deepEquals
21
+ - _deepJsonEquals
22
+ - _jsonEquals
23
+
24
+ _deepEquals uses "common sense" equality.
25
+ It tries to work "as you would expect it to".
26
+ With the important caveat that undefined values are treated the same as absent key.
27
+ So, _deepEquals should be the first choice.
28
+ It's also the most performant of 3.
29
+
30
+ _deepJsonEquals uses different logic, that's often not what you expect.
31
+ It should be used to compare objects of how they would look after "passing via JSON.stringify",
32
+ for example when you return it over the API to the Frontend,
33
+ or when you pass it to be saved to the Database.
34
+ If some object has custom .toJSON() implementation - it'll invoke that (similar to JSON.stringify).
35
+ For these cases - it can be better than _deepEquals.
36
+ And it's better than _jsonEquals, because it doesn't fail/depend on object key order.
37
+
38
+ _jsonEquals is simply JSON.stringify(a) === JSON.stringify(b).
39
+ It's the simplest implementation, but also the slowest of 3.
40
+
41
+ TLDR: _deepEquals should be useful in most of the cases, start there.
21
42
  */
22
43
  export function _deepEquals(a: any, b: any): boolean {
23
44
  if (a === b) return true
24
45
 
25
- if (a && b && typeof a === 'object' && typeof b === 'object') {
26
- const arrA = isArray(a)
27
- const arrB = isArray(b)
28
- let i: number
29
- let length: number
30
- let key: string
46
+ if (Number.isNaN(a)) {
47
+ return Number.isNaN(b)
48
+ }
31
49
 
32
- if (arrA !== arrB) return false
50
+ if (a && b && typeof a === 'object' && typeof b === 'object') {
51
+ if (a.constructor !== b.constructor) return false
33
52
 
34
- if (arrA && arrB) {
35
- length = a.length
53
+ if (Array.isArray(a)) {
54
+ const length = a.length
36
55
  if (length !== b.length) return false
37
- for (i = length; i-- !== 0; ) if (!_deepEquals(a[i], b[i])) return false
56
+ for (let i = length; i-- !== 0; ) {
57
+ if (!_deepEquals(a[i], b[i])) return false
58
+ }
59
+ return true
60
+ }
61
+
62
+ if (a instanceof Map && b instanceof Map) {
63
+ for (const key of new Set([...a.keys(), ...b.keys()])) {
64
+ if (!_deepEquals(a.get(key), b.get(key))) return false
65
+ }
38
66
  return true
39
67
  }
40
68
 
41
- const dateA = a instanceof Date
42
- const dateB = b instanceof Date
43
- if (dateA !== dateB) return false
44
- if (dateA && dateB) return a.getTime() === b.getTime()
69
+ if (a instanceof Set && b instanceof Set) {
70
+ if (a.size !== b.size) return false
71
+ for (const key of a) {
72
+ if (!b.has(key)) return false
73
+ }
74
+ return true
75
+ }
45
76
 
46
- const regexpA = a instanceof RegExp
47
- const regexpB = b instanceof RegExp
48
- if (regexpA !== regexpB) return false
49
- if (regexpA && regexpB) return a.toString() === b.toString()
77
+ if (a.constructor === RegExp) return a.source === b.source && a.flags === b.flags
78
+ if (a.valueOf !== Object.prototype.valueOf) return a.valueOf() === b.valueOf()
79
+ if (a.toString !== Object.prototype.toString) return a.toString() === b.toString()
50
80
 
51
- const keys = keyList(a)
52
- length = keys.length
81
+ for (const key of new Set([...Object.keys(a), ...Object.keys(b)])) {
82
+ if (!_deepEquals(a[key], b[key])) return false
83
+ }
53
84
 
54
- if (length !== keyList(b).length) return false
85
+ return true
86
+ }
55
87
 
56
- for (i = length; i-- !== 0; ) if (!hasProp.call(b, keys[i]!)) return false
88
+ return a === b
89
+ }
57
90
 
58
- for (i = length; i-- !== 0; ) {
59
- key = keys[i]!
60
- if (!_deepEquals(a[key], b[key])) return false
91
+ /**
92
+ Returns true if a and b are deeply equal.
93
+
94
+ Equality is checked in the same way as if both arguments are processed via
95
+ JSON.stringify and JSON.parse:
96
+ - undefined values are removed, undefined values in an array are turned into `null`, etc.
97
+ - Any Regex, Map, Set, Function stringifies to {}.
98
+ - Date stringifies to its IsoDateTimeString representation.
99
+ - Any object that implements toJSON is compared by the output of its toJSON().
100
+ - NaN stringifies to null
101
+ - Order of object keys does not matter, unlike when comparing JSON.stringify(a) === JSON.stringify(b)
102
+
103
+ See _deepEquals docs for more details and comparison.
104
+ */
105
+ export function _deepJsonEquals(a: any, b: any): boolean {
106
+ if (a === b) return true
107
+
108
+ if (Number.isNaN(a)) {
109
+ a = null
110
+ } else if (typeof a === 'function') {
111
+ a = undefined
112
+ } else if (a && typeof a === 'object') {
113
+ if (a instanceof Date) {
114
+ a = a.valueOf()
115
+ } else if ('toJSON' in a) {
116
+ a = a.toJSON()
117
+ }
118
+ }
119
+ if (Number.isNaN(b)) {
120
+ b = null
121
+ } else if (typeof b === 'function') {
122
+ b = undefined
123
+ } else if (b && typeof b === 'object') {
124
+ if (b instanceof Date) {
125
+ b = b.valueOf()
126
+ } else if ('toJSON' in b) {
127
+ b = b.toJSON()
128
+ }
129
+ }
130
+
131
+ if (a && b && typeof a === 'object' && typeof b === 'object') {
132
+ if (Array.isArray(a)) {
133
+ const length = a.length
134
+ if (length !== b.length) return false
135
+ for (let i = length; i-- !== 0; ) {
136
+ if (!_deepJsonEquals(a[i], b[i])) return false
137
+ }
138
+ return true
139
+ }
140
+
141
+ for (const key of new Set([...Object.keys(a), ...Object.keys(b)])) {
142
+ if (!_deepJsonEquals(a[key], b[key])) return false
61
143
  }
62
144
 
63
145
  return true
64
146
  }
65
147
 
66
- // eslint-disable-next-line no-self-compare
67
- return a !== a && b !== b
148
+ return a === b
149
+ }
150
+
151
+ /**
152
+ * Shortcut for JSON.stringify(a) === JSON.stringify(b)
153
+ *
154
+ * Simplest "deep equals" implementation, but also the slowest,
155
+ * and not robust, in the sense that it depends on the order of object keys.
156
+ */
157
+ export function _jsonEquals(a: any, b: any): boolean {
158
+ return JSON.stringify(a) === JSON.stringify(b)
68
159
  }