fast-is-equal 1.0.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.
- package/README.md +181 -0
- package/dist/__tests__/fastIsEqual.test.d.ts +1 -0
- package/dist/__tests__/fastIsEqual.test.js +180 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +128 -0
- package/package.json +44 -0
package/README.md
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
# fast-is-equal
|
|
2
|
+
|
|
3
|
+
Blazing-fast equality checks, minus the baggage. A lean, standalone alternative to Lodash’s `isEqual`—because speed matters.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
Using yarn:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
yarn add fast-is-equal
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Using npm:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install fast-is-equal
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
```typescript
|
|
21
|
+
import { fastIsEqual } from 'fast-is-equal';
|
|
22
|
+
|
|
23
|
+
console.log(fastIsEqual(1, 1)); // true
|
|
24
|
+
console.log(fastIsEqual({ a: 1 }, { a: 1 })); // true
|
|
25
|
+
console.log(fastIsEqual([1, 2], [1, 3])); // false
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Features
|
|
29
|
+
- Lightweight and dependency-free.
|
|
30
|
+
- Handles primitives, objects, arrays, Maps, Sets, circular references, and more.
|
|
31
|
+
- Optimized for performance (see benchmarks).
|
|
32
|
+
|
|
33
|
+
## Benchmarks
|
|
34
|
+
`fast-is-equal` outperforms Lodash’s `isEqual` in most cases. Run `npm run benchmark` locally to compare:
|
|
35
|
+
```bash
|
|
36
|
+
Performance Comparison: fastIsEqual vs Lodash isEqual
|
|
37
|
+
Iterations per test case: 1000000
|
|
38
|
+
----------------------------------------
|
|
39
|
+
Test Case 1: Numbers
|
|
40
|
+
fastIsEqual: 0.000003 ms
|
|
41
|
+
Lodash isEqual: 0.000006 ms
|
|
42
|
+
Difference (fastIsEqual - Lodash): -0.000002 ms
|
|
43
|
+
fastIsEqual is 1.62x faster than Lodash
|
|
44
|
+
----------------------------------------
|
|
45
|
+
Test Case 2: Strings
|
|
46
|
+
fastIsEqual: 0.000005 ms
|
|
47
|
+
Lodash isEqual: 0.000005 ms
|
|
48
|
+
Difference (fastIsEqual - Lodash): -0.000000 ms
|
|
49
|
+
fastIsEqual is 1.08x faster than Lodash
|
|
50
|
+
----------------------------------------
|
|
51
|
+
Test Case 3: Booleans
|
|
52
|
+
fastIsEqual: 0.000005 ms
|
|
53
|
+
Lodash isEqual: 0.000005 ms
|
|
54
|
+
Difference (fastIsEqual - Lodash): -0.000000 ms
|
|
55
|
+
fastIsEqual is 1.06x faster than Lodash
|
|
56
|
+
----------------------------------------
|
|
57
|
+
Test Case 4: NaN
|
|
58
|
+
fastIsEqual: 0.000006 ms
|
|
59
|
+
Lodash isEqual: 0.000012 ms
|
|
60
|
+
Difference (fastIsEqual - Lodash): -0.000006 ms
|
|
61
|
+
fastIsEqual is 1.98x faster than Lodash
|
|
62
|
+
----------------------------------------
|
|
63
|
+
Test Case 5: Simple Object (equal)
|
|
64
|
+
fastIsEqual: 0.000097 ms
|
|
65
|
+
Lodash isEqual: 0.000271 ms
|
|
66
|
+
Difference (fastIsEqual - Lodash): -0.000174 ms
|
|
67
|
+
fastIsEqual is 2.80x faster than Lodash
|
|
68
|
+
----------------------------------------
|
|
69
|
+
Test Case 6: Simple Object (unequal)
|
|
70
|
+
fastIsEqual: 0.000105 ms
|
|
71
|
+
Lodash isEqual: 0.000271 ms
|
|
72
|
+
Difference (fastIsEqual - Lodash): -0.000165 ms
|
|
73
|
+
fastIsEqual is 2.57x faster than Lodash
|
|
74
|
+
----------------------------------------
|
|
75
|
+
Test Case 7: Nested Object (equal)
|
|
76
|
+
fastIsEqual: 0.000184 ms
|
|
77
|
+
Lodash isEqual: 0.000835 ms
|
|
78
|
+
Difference (fastIsEqual - Lodash): -0.000651 ms
|
|
79
|
+
fastIsEqual is 4.53x faster than Lodash
|
|
80
|
+
----------------------------------------
|
|
81
|
+
Test Case 8: Nested Object (unequal)
|
|
82
|
+
fastIsEqual: 0.000197 ms
|
|
83
|
+
Lodash isEqual: 0.000850 ms
|
|
84
|
+
Difference (fastIsEqual - Lodash): -0.000653 ms
|
|
85
|
+
fastIsEqual is 4.31x faster than Lodash
|
|
86
|
+
----------------------------------------
|
|
87
|
+
Test Case 9: Array of Primitives (equal)
|
|
88
|
+
fastIsEqual: 0.000017 ms
|
|
89
|
+
Lodash isEqual: 0.000102 ms
|
|
90
|
+
Difference (fastIsEqual - Lodash): -0.000085 ms
|
|
91
|
+
fastIsEqual is 5.95x faster than Lodash
|
|
92
|
+
----------------------------------------
|
|
93
|
+
Test Case 10: Array of Primitives (unequal)
|
|
94
|
+
fastIsEqual: 0.000015 ms
|
|
95
|
+
Lodash isEqual: 0.000103 ms
|
|
96
|
+
Difference (fastIsEqual - Lodash): -0.000088 ms
|
|
97
|
+
fastIsEqual is 6.79x faster than Lodash
|
|
98
|
+
----------------------------------------
|
|
99
|
+
Test Case 11: Array of Objects (equal)
|
|
100
|
+
fastIsEqual: 0.000078 ms
|
|
101
|
+
Lodash isEqual: 0.000638 ms
|
|
102
|
+
Difference (fastIsEqual - Lodash): -0.000560 ms
|
|
103
|
+
fastIsEqual is 8.15x faster than Lodash
|
|
104
|
+
----------------------------------------
|
|
105
|
+
Test Case 12: Circular Reference
|
|
106
|
+
fastIsEqual: 0.000095 ms
|
|
107
|
+
Lodash isEqual: 0.000493 ms
|
|
108
|
+
Difference (fastIsEqual - Lodash): -0.000399 ms
|
|
109
|
+
fastIsEqual is 5.22x faster than Lodash
|
|
110
|
+
----------------------------------------
|
|
111
|
+
Test Case 13: Map (equal)
|
|
112
|
+
fastIsEqual: 0.000074 ms
|
|
113
|
+
Lodash isEqual: 0.001383 ms
|
|
114
|
+
Difference (fastIsEqual - Lodash): -0.001309 ms
|
|
115
|
+
fastIsEqual is 18.67x faster than Lodash
|
|
116
|
+
----------------------------------------
|
|
117
|
+
Test Case 14: Map (unequal)
|
|
118
|
+
fastIsEqual: 0.000076 ms
|
|
119
|
+
Lodash isEqual: 0.001330 ms
|
|
120
|
+
Difference (fastIsEqual - Lodash): -0.001255 ms
|
|
121
|
+
fastIsEqual is 17.59x faster than Lodash
|
|
122
|
+
----------------------------------------
|
|
123
|
+
Test Case 15: Set (equal)
|
|
124
|
+
fastIsEqual: 0.000073 ms
|
|
125
|
+
Lodash isEqual: 0.000949 ms
|
|
126
|
+
Difference (fastIsEqual - Lodash): -0.000876 ms
|
|
127
|
+
fastIsEqual is 13.07x faster than Lodash
|
|
128
|
+
----------------------------------------
|
|
129
|
+
Test Case 16: Set (unequal)
|
|
130
|
+
fastIsEqual: 0.000070 ms
|
|
131
|
+
Lodash isEqual: 0.000930 ms
|
|
132
|
+
Difference (fastIsEqual - Lodash): -0.000860 ms
|
|
133
|
+
fastIsEqual is 13.22x faster than Lodash
|
|
134
|
+
----------------------------------------
|
|
135
|
+
Test Case 17: Empty Object vs Array
|
|
136
|
+
fastIsEqual: 0.000009 ms
|
|
137
|
+
Lodash isEqual: 0.000043 ms
|
|
138
|
+
Difference (fastIsEqual - Lodash): -0.000034 ms
|
|
139
|
+
fastIsEqual is 4.74x faster than Lodash
|
|
140
|
+
----------------------------------------
|
|
141
|
+
Test Case 18: Map vs Set
|
|
142
|
+
fastIsEqual: 0.000018 ms
|
|
143
|
+
Lodash isEqual: 0.000469 ms
|
|
144
|
+
Difference (fastIsEqual - Lodash): -0.000452 ms
|
|
145
|
+
fastIsEqual is 26.55x faster than Lodash
|
|
146
|
+
----------------------------------------
|
|
147
|
+
Average Performance:
|
|
148
|
+
fastIsEqual: 0.000063 ms
|
|
149
|
+
Lodash isEqual: 0.000483 ms
|
|
150
|
+
fastIsEqual is on average 7.71x faster than Lodash
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## License
|
|
154
|
+
MIT
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
#### 1.3 Add a License File
|
|
158
|
+
Create a `LICENSE` file with the MIT license (or your preferred license):
|
|
159
|
+
```plaintext
|
|
160
|
+
MIT License
|
|
161
|
+
|
|
162
|
+
Copyright (c) 2025 Your Name
|
|
163
|
+
|
|
164
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
165
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
166
|
+
in the Software without restriction, including without limitation the rights
|
|
167
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
168
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
169
|
+
furnished to do so, subject to the following conditions:
|
|
170
|
+
|
|
171
|
+
The above copyright notice and this permission notice shall be included in all
|
|
172
|
+
copies or substantial portions of the Software.
|
|
173
|
+
|
|
174
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
175
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
176
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
177
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
178
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
179
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
180
|
+
SOFTWARE.
|
|
181
|
+
```
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const index_1 = require("../index");
|
|
4
|
+
describe('isEqual', () => {
|
|
5
|
+
// **Primitives**
|
|
6
|
+
it('should return true for identical primitives', () => {
|
|
7
|
+
expect((0, index_1.fastIsEqual)(1, 1)).toBe(true);
|
|
8
|
+
expect((0, index_1.fastIsEqual)('a', 'a')).toBe(true);
|
|
9
|
+
expect((0, index_1.fastIsEqual)(true, true)).toBe(true);
|
|
10
|
+
});
|
|
11
|
+
it('should return false for different primitives', () => {
|
|
12
|
+
expect((0, index_1.fastIsEqual)(1, 2)).toBe(false);
|
|
13
|
+
expect((0, index_1.fastIsEqual)('a', 'b')).toBe(false);
|
|
14
|
+
expect((0, index_1.fastIsEqual)(true, false)).toBe(false);
|
|
15
|
+
});
|
|
16
|
+
// **NaN**
|
|
17
|
+
it('should return true for NaN and NaN', () => {
|
|
18
|
+
expect((0, index_1.fastIsEqual)(NaN, NaN)).toBe(true);
|
|
19
|
+
});
|
|
20
|
+
// **Null and Undefined**
|
|
21
|
+
it('should return true for null and null', () => {
|
|
22
|
+
expect((0, index_1.fastIsEqual)(null, null)).toBe(true);
|
|
23
|
+
});
|
|
24
|
+
it('should return true for undefined and undefined', () => {
|
|
25
|
+
expect((0, index_1.fastIsEqual)(undefined, undefined)).toBe(true);
|
|
26
|
+
});
|
|
27
|
+
it('should return false for null and undefined', () => {
|
|
28
|
+
expect((0, index_1.fastIsEqual)(null, undefined)).toBe(false);
|
|
29
|
+
});
|
|
30
|
+
// **Objects**
|
|
31
|
+
it('should return true for identical objects', () => {
|
|
32
|
+
const obj = { a: 1, b: { c: 2 } };
|
|
33
|
+
expect((0, index_1.fastIsEqual)(obj, obj)).toBe(true);
|
|
34
|
+
});
|
|
35
|
+
it('should return true for deeply equal objects', () => {
|
|
36
|
+
const obj1 = { a: 1, b: { c: 2 } };
|
|
37
|
+
const obj2 = { a: 1, b: { c: 2 } };
|
|
38
|
+
expect((0, index_1.fastIsEqual)(obj1, obj2)).toBe(true);
|
|
39
|
+
});
|
|
40
|
+
it('should return false for objects with different keys', () => {
|
|
41
|
+
const obj1 = { a: 1 };
|
|
42
|
+
const obj2 = { b: 1 };
|
|
43
|
+
expect((0, index_1.fastIsEqual)(obj1, obj2)).toBe(false);
|
|
44
|
+
});
|
|
45
|
+
it('should return false for objects with different values', () => {
|
|
46
|
+
const obj1 = { a: 1 };
|
|
47
|
+
const obj2 = { a: 2 };
|
|
48
|
+
expect((0, index_1.fastIsEqual)(obj1, obj2)).toBe(false);
|
|
49
|
+
});
|
|
50
|
+
// **Arrays**
|
|
51
|
+
it('should return true for identical arrays', () => {
|
|
52
|
+
const arr = [1, 2, 3];
|
|
53
|
+
expect((0, index_1.fastIsEqual)(arr, arr)).toBe(true);
|
|
54
|
+
});
|
|
55
|
+
it('should return true for deeply equal arrays', () => {
|
|
56
|
+
const arr1 = [1, [2, 3]];
|
|
57
|
+
const arr2 = [1, [2, 3]];
|
|
58
|
+
expect((0, index_1.fastIsEqual)(arr1, arr2)).toBe(true);
|
|
59
|
+
});
|
|
60
|
+
it('should return false for arrays with different lengths', () => {
|
|
61
|
+
const arr1 = [1, 2];
|
|
62
|
+
const arr2 = [1, 2, 3];
|
|
63
|
+
expect((0, index_1.fastIsEqual)(arr1, arr2)).toBe(false);
|
|
64
|
+
});
|
|
65
|
+
it('should return false for arrays with different elements', () => {
|
|
66
|
+
const arr1 = [1, 2];
|
|
67
|
+
const arr2 = [1, 3];
|
|
68
|
+
expect((0, index_1.fastIsEqual)(arr1, arr2)).toBe(false);
|
|
69
|
+
});
|
|
70
|
+
// **Dates**
|
|
71
|
+
it('should return true for identical dates', () => {
|
|
72
|
+
const date = new Date();
|
|
73
|
+
expect((0, index_1.fastIsEqual)(date, date)).toBe(true);
|
|
74
|
+
});
|
|
75
|
+
it('should return true for dates with the same timestamp', () => {
|
|
76
|
+
const date1 = new Date('2023-01-01');
|
|
77
|
+
const date2 = new Date('2023-01-01');
|
|
78
|
+
expect((0, index_1.fastIsEqual)(date1, date2)).toBe(true);
|
|
79
|
+
});
|
|
80
|
+
it('should return false for dates with different timestamps', () => {
|
|
81
|
+
const date1 = new Date('2023-01-01');
|
|
82
|
+
const date2 = new Date('2023-01-02');
|
|
83
|
+
expect((0, index_1.fastIsEqual)(date1, date2)).toBe(false);
|
|
84
|
+
});
|
|
85
|
+
// **Regular Expressions**
|
|
86
|
+
it('should return true for identical regexes', () => {
|
|
87
|
+
const regex = /a/g;
|
|
88
|
+
expect((0, index_1.fastIsEqual)(regex, regex)).toBe(true);
|
|
89
|
+
});
|
|
90
|
+
it('should return true for regexes with the same pattern and flags', () => {
|
|
91
|
+
const regex1 = /a/g;
|
|
92
|
+
const regex2 = /a/g;
|
|
93
|
+
expect((0, index_1.fastIsEqual)(regex1, regex2)).toBe(true);
|
|
94
|
+
});
|
|
95
|
+
it('should return false for regexes with different patterns', () => {
|
|
96
|
+
const regex1 = /a/g;
|
|
97
|
+
const regex2 = /b/g;
|
|
98
|
+
expect((0, index_1.fastIsEqual)(regex1, regex2)).toBe(false);
|
|
99
|
+
});
|
|
100
|
+
it('should return false for regexes with different flags', () => {
|
|
101
|
+
const regex1 = /a/g;
|
|
102
|
+
const regex2 = /a/i;
|
|
103
|
+
expect((0, index_1.fastIsEqual)(regex1, regex2)).toBe(false);
|
|
104
|
+
});
|
|
105
|
+
// **Maps**
|
|
106
|
+
it('should return true for identical maps', () => {
|
|
107
|
+
const map = new Map([['a', 1]]);
|
|
108
|
+
expect((0, index_1.fastIsEqual)(map, map)).toBe(true);
|
|
109
|
+
});
|
|
110
|
+
it('should return true for maps with the same key-value pairs', () => {
|
|
111
|
+
const map1 = new Map([['a', 1]]);
|
|
112
|
+
const map2 = new Map([['a', 1]]);
|
|
113
|
+
expect((0, index_1.fastIsEqual)(map1, map2)).toBe(true);
|
|
114
|
+
});
|
|
115
|
+
it('should return false for maps with different key-value pairs', () => {
|
|
116
|
+
const map1 = new Map([['a', 1]]);
|
|
117
|
+
const map2 = new Map([['a', 2]]);
|
|
118
|
+
expect((0, index_1.fastIsEqual)(map1, map2)).toBe(false);
|
|
119
|
+
});
|
|
120
|
+
it('should return false for maps with different sizes', () => {
|
|
121
|
+
const map1 = new Map([['a', 1]]);
|
|
122
|
+
const map2 = new Map([['a', 1], ['b', 2]]);
|
|
123
|
+
expect((0, index_1.fastIsEqual)(map1, map2)).toBe(false);
|
|
124
|
+
});
|
|
125
|
+
// **Sets**
|
|
126
|
+
it('should return true for identical sets', () => {
|
|
127
|
+
const set = new Set([1, 2]);
|
|
128
|
+
expect((0, index_1.fastIsEqual)(set, set)).toBe(true);
|
|
129
|
+
});
|
|
130
|
+
it('should return true for sets with the same elements', () => {
|
|
131
|
+
const set1 = new Set([1, 2]);
|
|
132
|
+
const set2 = new Set([1, 2]);
|
|
133
|
+
expect((0, index_1.fastIsEqual)(set1, set2)).toBe(true);
|
|
134
|
+
});
|
|
135
|
+
it('should return false for sets with different elements', () => {
|
|
136
|
+
const set1 = new Set([1, 2]);
|
|
137
|
+
const set2 = new Set([1, 3]);
|
|
138
|
+
expect((0, index_1.fastIsEqual)(set1, set2)).toBe(false);
|
|
139
|
+
});
|
|
140
|
+
it('should return false for sets with different sizes', () => {
|
|
141
|
+
const set1 = new Set([1, 2]);
|
|
142
|
+
const set2 = new Set([1]);
|
|
143
|
+
expect((0, index_1.fastIsEqual)(set1, set2)).toBe(false);
|
|
144
|
+
});
|
|
145
|
+
// **Circular References**
|
|
146
|
+
it('should return true for circular references', () => {
|
|
147
|
+
const obj1 = {};
|
|
148
|
+
obj1.self = obj1;
|
|
149
|
+
const obj2 = {};
|
|
150
|
+
obj2.self = obj2;
|
|
151
|
+
expect((0, index_1.fastIsEqual)(obj1, obj2)).toBe(true);
|
|
152
|
+
});
|
|
153
|
+
it('should return false for different circular references', () => {
|
|
154
|
+
const obj1 = {};
|
|
155
|
+
obj1.self = obj1;
|
|
156
|
+
const obj2 = { self: {} };
|
|
157
|
+
expect((0, index_1.fastIsEqual)(obj1, obj2)).toBe(false);
|
|
158
|
+
});
|
|
159
|
+
// **Other Types (Functions, Promises)**
|
|
160
|
+
it('should return true for the same function reference', () => {
|
|
161
|
+
const func = () => { };
|
|
162
|
+
expect((0, index_1.fastIsEqual)(func, func)).toBe(true);
|
|
163
|
+
});
|
|
164
|
+
it('should return false for different functions', () => {
|
|
165
|
+
const func1 = () => { };
|
|
166
|
+
const func2 = () => { };
|
|
167
|
+
expect((0, index_1.fastIsEqual)(func1, func2)).toBe(false);
|
|
168
|
+
});
|
|
169
|
+
it('should return false for different promises', () => {
|
|
170
|
+
const p1 = Promise.resolve(1);
|
|
171
|
+
const p2 = Promise.resolve(1);
|
|
172
|
+
expect((0, index_1.fastIsEqual)(p1, p2)).toBe(false);
|
|
173
|
+
});
|
|
174
|
+
// **Mixed Types**
|
|
175
|
+
it('should return false for different types', () => {
|
|
176
|
+
expect((0, index_1.fastIsEqual)(1, '1')).toBe(false);
|
|
177
|
+
expect((0, index_1.fastIsEqual)({}, [])).toBe(false);
|
|
178
|
+
expect((0, index_1.fastIsEqual)(new Map(), new Set())).toBe(false);
|
|
179
|
+
});
|
|
180
|
+
});
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function fastIsEqual(a: any, b: any): boolean;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.fastIsEqual = fastIsEqual;
|
|
4
|
+
function fastIsEqual(a, b) {
|
|
5
|
+
// Fast path for strict equality
|
|
6
|
+
if (a === b)
|
|
7
|
+
return true;
|
|
8
|
+
// Handle NaN
|
|
9
|
+
if (Number.isNaN(a) && Number.isNaN(b))
|
|
10
|
+
return true;
|
|
11
|
+
// Early exit for null/undefined or type mismatch
|
|
12
|
+
if (a == null || b == null || typeof a !== typeof b)
|
|
13
|
+
return a === b;
|
|
14
|
+
// Check if both are arrays or both are not arrays
|
|
15
|
+
const aIsArray = Array.isArray(a);
|
|
16
|
+
const bIsArray = Array.isArray(b);
|
|
17
|
+
if (aIsArray !== bIsArray)
|
|
18
|
+
return false;
|
|
19
|
+
// Check if both are the same type of object (e.g., both Map, both Set, etc.)
|
|
20
|
+
if (a.constructor !== b.constructor)
|
|
21
|
+
return false;
|
|
22
|
+
// Fast path for arrays
|
|
23
|
+
if (aIsArray && bIsArray) {
|
|
24
|
+
if (a.length !== b.length)
|
|
25
|
+
return false;
|
|
26
|
+
for (let i = 0; i < a.length; i++) {
|
|
27
|
+
const elemA = a[i];
|
|
28
|
+
const elemB = b[i];
|
|
29
|
+
if (elemA === elemB)
|
|
30
|
+
continue;
|
|
31
|
+
if (Number.isNaN(elemA) && Number.isNaN(elemB))
|
|
32
|
+
continue;
|
|
33
|
+
if (typeof elemA === 'object' || typeof elemB === 'object' ||
|
|
34
|
+
typeof elemA === 'function' || typeof elemB === 'function') {
|
|
35
|
+
return deepEqual(elemA, elemB, new Map());
|
|
36
|
+
}
|
|
37
|
+
if (elemA !== elemB)
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
// For other non-primitive types, use deepEqual with a visited Map
|
|
43
|
+
return deepEqual(a, b, new Map());
|
|
44
|
+
}
|
|
45
|
+
function deepEqual(valA, valB, visited) {
|
|
46
|
+
// Strict equality and NaN checks
|
|
47
|
+
if (valA === valB)
|
|
48
|
+
return true;
|
|
49
|
+
if (Number.isNaN(valA) && Number.isNaN(valB))
|
|
50
|
+
return true;
|
|
51
|
+
if (valA == null || valB == null)
|
|
52
|
+
return valA === valB;
|
|
53
|
+
if (typeof valA !== typeof valB)
|
|
54
|
+
return false;
|
|
55
|
+
// Handle Dates
|
|
56
|
+
if (valA instanceof Date && valB instanceof Date) {
|
|
57
|
+
return valA.getTime() === valB.getTime();
|
|
58
|
+
}
|
|
59
|
+
// Handle Regular Expressions
|
|
60
|
+
if (valA instanceof RegExp && valB instanceof RegExp) {
|
|
61
|
+
return valA.toString() === valB.toString();
|
|
62
|
+
}
|
|
63
|
+
// Handle Promises (reference equality)
|
|
64
|
+
if (valA instanceof Promise && valB instanceof Promise) {
|
|
65
|
+
return valA === valB;
|
|
66
|
+
}
|
|
67
|
+
// Handle Arrays
|
|
68
|
+
if (Array.isArray(valA) && Array.isArray(valB)) {
|
|
69
|
+
if (valA.length !== valB.length)
|
|
70
|
+
return false;
|
|
71
|
+
for (let i = 0; i < valA.length; i++) {
|
|
72
|
+
const elemA = valA[i];
|
|
73
|
+
const elemB = valB[i];
|
|
74
|
+
if (elemA === elemB)
|
|
75
|
+
continue;
|
|
76
|
+
if (Number.isNaN(elemA) && Number.isNaN(elemB))
|
|
77
|
+
continue;
|
|
78
|
+
if (typeof elemA === 'object' || typeof elemB === 'object' ||
|
|
79
|
+
typeof elemA === 'function' || typeof elemB === 'function') {
|
|
80
|
+
if (!deepEqual(elemA, elemB, visited))
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
else if (elemA !== elemB) {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
// Handle Maps
|
|
90
|
+
if (valA instanceof Map && valB instanceof Map) {
|
|
91
|
+
if (valA.size !== valB.size)
|
|
92
|
+
return false;
|
|
93
|
+
for (const [key, value] of valA) {
|
|
94
|
+
if (!valB.has(key) || !deepEqual(value, valB.get(key), visited))
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
// Handle Sets
|
|
100
|
+
if (valA instanceof Set && valB instanceof Set) {
|
|
101
|
+
if (valA.size !== valB.size)
|
|
102
|
+
return false;
|
|
103
|
+
for (const value of valA) {
|
|
104
|
+
if (!valB.has(value))
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
// Handle Objects with circular reference tracking
|
|
110
|
+
if (typeof valA === 'object' && typeof valB === 'object') {
|
|
111
|
+
if (visited.has(valA) && visited.get(valA) === valB)
|
|
112
|
+
return true;
|
|
113
|
+
visited.set(valA, valB);
|
|
114
|
+
const keysA = Object.keys(valA);
|
|
115
|
+
const keysB = Object.keys(valB);
|
|
116
|
+
if (keysA.length !== keysB.length)
|
|
117
|
+
return false;
|
|
118
|
+
for (const key of keysA) {
|
|
119
|
+
if (!Object.prototype.hasOwnProperty.call(valB, key) ||
|
|
120
|
+
!deepEqual(valA[key], valB[key], visited)) {
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return true;
|
|
125
|
+
}
|
|
126
|
+
// Default to strict equality
|
|
127
|
+
return valA === valB;
|
|
128
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "fast-is-equal",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Blazing-fast equality checks, minus the baggage. A lean, standalone alternative to Lodash's isEqual—because speed matters.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"lodash",
|
|
7
|
+
"isEqual",
|
|
8
|
+
"typescript",
|
|
9
|
+
"react",
|
|
10
|
+
"react-native"
|
|
11
|
+
],
|
|
12
|
+
"homepage": "https://github.com/JairajJangle/fast-is-equal#readme",
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/JairajJangle/fast-is-equal/issues"
|
|
15
|
+
},
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "git+https://github.com/JairajJangle/fast-is-equal.git"
|
|
19
|
+
},
|
|
20
|
+
"license": "MIT",
|
|
21
|
+
"author": "Jairaj Jangle <reachout.jairaj.jangle@gmail.com> (https://github.com/JairajJangle)",
|
|
22
|
+
"main": "dist/index.js",
|
|
23
|
+
"types": "dist/index.d.ts",
|
|
24
|
+
"scripts": {
|
|
25
|
+
"test": "jest",
|
|
26
|
+
"build": "rimraf dist && tsc",
|
|
27
|
+
"prepublishOnly": "npm run build",
|
|
28
|
+
"benchmark": "ts-node benchmarks/fastIsEqual.benchmark.ts"
|
|
29
|
+
},
|
|
30
|
+
"files": [
|
|
31
|
+
"dist"
|
|
32
|
+
],
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@types/jest": "^29.5.3",
|
|
35
|
+
"@types/lodash": "^4.17.16",
|
|
36
|
+
"jest": "^29.7.0",
|
|
37
|
+
"lodash": "^4.17.21",
|
|
38
|
+
"rimraf": "^6.0.1",
|
|
39
|
+
"ts-jest": "^29.1.1",
|
|
40
|
+
"ts-node": "^10.9.2",
|
|
41
|
+
"tslib": "^2.8.1",
|
|
42
|
+
"typescript": "^5.8.2"
|
|
43
|
+
}
|
|
44
|
+
}
|