.. | ||
dist | ||
.release-it.beta.json | ||
.release-it.json | ||
BUILD.md | ||
CHANGELOG.md | ||
index.d.ts | ||
LICENSE | ||
package.json | ||
README.md |
fast-equals
Perform blazing fast equality comparisons (either deep or shallow) on two objects passed. It has no dependencies, and is ~1kB when minified and gzipped.
Unlike most equality validation libraries, the following types are handled out-of-the-box:
NaN
Date
objectsRegExp
objectsMap
/Set
iterablesPromise
objectsreact
elements
Starting with version 1.5.0
, circular objects are supported for both deep and shallow equality (see circularDeepEqual
and circularShallowEqual
). You can also create a custom nested comparator, for specific scenarios (see below).
Table of contents
Usage
You can either import the individual functions desired:
import { deepEqual } from 'fast-equals';
console.log(deepEqual({ foo: 'bar' }, { foo: 'bar' })); // true
Or if you want to import all functions under a namespace:
import * as fe from 'fast-equals';
console.log(fe.deep({ foo: 'bar' }, { foo: 'bar' })); // true
Specific builds
There are three builds, an ESM build for modern build systems / runtimes, a CommonJS build for traditional NodeJS environments, and a UMD build for legacy implementations. The ideal one will likely be chosen for you automatically, however if you want to use a specific build you can always import it directly:
- ESM =>
fast-equals/dist/fast-equals.esm.js
- For older
nodejs
versions that do not allow ESM with file extensions other than.mjs
=>fast-equals/dist/fast-equals.mjs
- For older
- CommonJS =>
fast-equals/dist/fast-equals.cjs.js
- UMD =>
fast-equals/dist/fast-equals.js
There is also a pre-minified version of the UMD build available:
- Minified UMD =>
fast-equals/dist/fast-equals.min.js
Available methods
deepEqual
Performs a deep equality comparison on the two objects passed and returns a boolean representing the value equivalency of the objects.
import { deepEqual } from 'fast-equals';
const objectA = { foo: { bar: 'baz' } };
const objectB = { foo: { bar: 'baz' } };
console.log(objectA === objectB); // false
console.log(deepEqual(objectA, objectB)); // true
Comparing Map
s
Map
objects support complex keys (objects, Arrays, etc.), however the spec for key lookups in Map
are based on SameZeroValue
. If the spec were followed for comparison, the following would always be false
:
const mapA = new Map([[{ foo: 'bar' }, { baz: 'quz' }]]);
const mapB = new Map([[{ foo: 'bar' }, { baz: 'quz' }]]);
deepEqual(mapA, mapB);
To support true deep equality of all contents, fast-equals
will perform a deep equality comparison for key and value parirs. Therefore, the above would be true
.
shallowEqual
Performs a shallow equality comparison on the two objects passed and returns a boolean representing the value equivalency of the objects.
import { shallowEqual } from 'fast-equals';
const nestedObject = { bar: 'baz' };
const objectA = { foo: nestedObject };
const objectB = { foo: nestedObject };
const objectC = { foo: { bar: 'baz' } };
console.log(objectA === objectB); // false
console.log(shallowEqual(objectA, objectB)); // true
console.log(shallowEqual(objectA, objectC)); // false
sameValueZeroEqual
Performs a SameValueZero
comparison on the two objects passed and returns a boolean representing the value equivalency of the objects. In simple terms, this means either strictly equal or both NaN
.
import { sameValueZeroEqual } from 'fast-equals';
const mainObject = { foo: NaN, bar: 'baz' };
const objectA = 'baz';
const objectB = NaN;
const objectC = { foo: NaN, bar: 'baz' };
console.log(sameValueZeroEqual(mainObject.bar, objectA)); // true
console.log(sameValueZeroEqual(mainObject.foo, objectB)); // true
console.log(sameValueZeroEqual(mainObject, objectC)); // false
circularDeepEqual
Performs the same comparison as deepEqual
but supports circular objects. It is slower than deepEqual
, so only use if you know circular objects are present.
function Circular(value) {
this.me = {
deeply: {
nested: {
reference: this,
},
},
value,
};
}
console.log(circularDeepEqual(new Circular('foo'), new Circular('foo'))); // true
console.log(circularDeepEqual(new Circular('foo'), new Circular('bar'))); // false
Just as with deepEqual
, both keys and values are compared for deep equality.
circularShallowEqual
Performs the same comparison as shallowequal
but supports circular objects. It is slower than shallowEqual
, so only use if you know circular objects are present.
const array = ['foo'];
array.push(array);
console.log(circularShallowEqual(array, ['foo', array])); // true
console.log(circularShallowEqual(array, [array])); // false
createCustomEqual
Creates a custom equality comparator that will be used on nested values in the object. Unlike deepEqual
and shallowEqual
, this is a partial-application function that will receive the internal comparator and should return a function that compares two objects.
The signature is as follows:
type EqualityComparator = (a: any, b: any, meta?: any) => boolean;
type InternalEqualityComparator = (a: any, b: any, indexOrKeyA: any, indexOrKeyB: any, parentA: any, parentB: any, meta: any) => boolean;
type EqualityComparatorCreator = (
deepEqual: EqualityComparator,
) => InternalEqualityComparator;
function createCustomEqual(
createIsEqual?: EqualityComparatorCreator,
): EqualityComparator;
The meta
parameter in EqualityComparator
and InternalEqualityComparator
is whatever you want it to be. It will be passed through to all equality checks, and is meant specifically for use with custom equality methods. For example, with the circularDeepEqual
and circularShallowEqual
methods, it is used to pass through a cache of processed objects.
NOTE: Map
implementations compare equality for both keys and value. When using a custom comparator and comparing equality of the keys, the iteration index is provided as both indexOrKeyA
and indexOrKeyB
to help use-cases where ordering of keys matters to equality.
An example for a custom equality comparison that also checks against values in the meta object:
import { createCustomEqual } from 'fast-equals';
const isDeepEqualOrFooMatchesMeta = createCustomEqual(
(deepEqual) => (objectA, objectB, indexOrKeyA, indexOrKeyB, parentA, parentB, meta) =>
objectA.foo === meta ||
objectB.foo === meta ||
deepEqual(objectA, objectB, meta),
);
const objectA = { foo: 'bar' };
const objectB = { foo: 'baz' };
const meta = 'bar';
console.log(isDeepEqualOrFooMatchesMeta(objectA, objectB, meta)); // true
Benchmarks
All benchmarks were performed on an i7-8650U Ubuntu Linux laptop with 24GB of memory using NodeJS version 12.19.1
, and are based on averages of running comparisons based deep equality on the following object types:
- Primitives (
String
,Number
,null
,undefined
) Function
Object
Array
Date
RegExp
react
elements- A mixed object with a combination of all the above types
Operations / second | |
---|---|
fast-equals | 153,880 |
fast-deep-equal | 144,035 |
react-fast-compare | 130,324 |
nano-equal | 104,624 |
fast-equals (circular) | 97,610 |
shallow-equal-fuzzy | 83,946 |
underscore.isEqual | 47,370 |
lodash.isEqual | 25,053 |
deep-eql | 22,146 |
assert.deepStrictEqual | 532 |
deep-equal | 209 |
Caveats that impact the benchmark (and accuracy of comparison):
Map
s,Promise
s, andSet
s were excluded from the benchmark entirely because no library other thandeep-eql
fully supported their comparisonassert.deepStrictEqual
does not supportNaN
orSameValueZero
equality for datesdeep-eql
does not supportSameValueZero
equality for zero equality (positive and negative zero are not equal)deep-equal
does not supportNaN
and does not strictly compare object type, or date / regexp values, nor usesSameValueZero
equality for datesfast-deep-equal
does not supportNaN
orSameValueZero
equality for datesnano-equal
does not strictly compare object property structure, array length, or object type, norSameValueZero
equality for datesreact-fast-compare
does not supportNaN
orSameValueZero
equality for dates, and does not comparefunction
equalityshallow-equal-fuzzy
does not strictly compare object type or regexp values, norSameValueZero
equality for datesunderscore.isEqual
does not supportSameValueZero
equality for primitives or dates
All of these have the potential of inflating the respective library's numbers in comparison to fast-equals
, but it was the closest apples-to-apples comparison I could create of a reasonable sample size. It should be noted that react
elements can be circular objects, however simple elements are not; I kept the react
comparison very basic to allow it to be included.
Development
Standard practice, clone the repo and npm i
to get the dependencies. The following npm scripts are available:
- benchmark => run benchmark tests against other equality libraries
- build => build
main
,module
, andbrowser
distributables withrollup
- clean => run
rimraf
on thedist
folder - dev => start webpack playground App
- dist => run
build
- lint => run ESLint on all files in
src
folder (also runs ondev
script) - lint:fix => run
lint
script, but with auto-fixer - prepublish:compile => run
lint
,test:coverage
,transpile:lib
,transpile:es
, anddist
scripts - start => run
dev
- test => run AVA with NODE_ENV=test on all files in
test
folder - test:coverage => run same script as
test
with code coverage calculation vianyc
- test:watch => run same script as
test
but keep persistent watcher