Détail du package

fast-equals

planttheidea31.8mMIT5.2.2

A blazing fast equality comparison, either shallow or deep

fast, equal, equals, deep-equal

readme

fast-equals

Perform blazing fast equality comparisons (either deep or shallow) on two objects passed, while also maintaining a high degree of flexibility for various implementation use-cases. It has no dependencies, and is ~1.8kB when minified and gzipped.

The following types are handled out-of-the-box:

  • Plain objects (including react elements and Arguments)
  • Arrays
  • Typed Arrays
  • Date objects
  • RegExp objects
  • Map / Set iterables
  • Promise objects
  • Primitive wrappers (new Boolean() / new Number() / new String())
  • Custom class instances, including subclasses of native classes

Methods are available for deep, shallow, or referential equality comparison. In addition, you can opt into support for circular objects, or performing a "strict" comparison with unconventional property definition, or both. You can also customize any specific type comparison based on your application's use-cases.

Table of contents

Usage

import { deepEqual } from 'fast-equals';

console.log(deepEqual({ foo: 'bar' }, { foo: 'bar' })); // true

Specific builds

By default, npm should resolve the correct build of the package based on your consumption (ESM vs CommonJS). However, if you want to force use of a specific build, they can be located here:

  • ESM => fast-equals/dist/esm/index.mjs
  • CommonJS => fast-equals/dist/cjs/index.cjs
  • UMD => fast-equals/dist/umd/index.js
  • Minified UMD => fast-equals/dist/min/index.js

If you are having issues loading a specific build type, please file an issue.

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 Maps

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

strictDeepEqual

Performs the same comparison as deepEqual but performs a strict comparison of the objects. In this includes:

  • Checking symbol properties
  • Checking non-enumerable properties in object comparisons
  • Checking full descriptor of properties on the object to match
  • Checking non-index properties on arrays
  • Checking non-key properties on Map / Set objects
const array = [{ foo: 'bar' }];
const otherArray = [{ foo: 'bar' }];

array.bar = 'baz';
otherArray.bar = 'baz';

console.log(strictDeepEqual(array, otherArray)); // true;
console.log(strictDeepEqual(array, [{ foo: 'bar' }])); // false;

strictShallowEqual

Performs the same comparison as shallowEqual but performs a strict comparison of the objects. In this includes:

  • Checking non-enumerable properties in object comparisons
  • Checking full descriptor of properties on the object to match
  • Checking non-index properties on arrays
  • Checking non-key properties on Map / Set objects
const array = ['foo'];
const otherArray = ['foo'];

array.bar = 'baz';
otherArray.bar = 'baz';

console.log(strictDeepEqual(array, otherArray)); // true;
console.log(strictDeepEqual(array, ['foo'])); // false;

strictCircularDeepEqual

Performs the same comparison as circularDeepEqual but performs a strict comparison of the objects. In this includes:

  • Checking Symbol properties on the object
  • Checking non-enumerable properties in object comparisons
  • Checking full descriptor of properties on the object to match
  • Checking non-index properties on arrays
  • Checking non-key properties on Map / Set objects
function Circular(value) {
  this.me = {
    deeply: {
      nested: {
        reference: this,
      },
    },
    value,
  };
}

const first = new Circular('foo');

Object.defineProperty(first, 'bar', {
  enumerable: false,
  value: 'baz',
});

const second = new Circular('foo');

Object.defineProperty(second, 'bar', {
  enumerable: false,
  value: 'baz',
});

console.log(circularDeepEqual(first, second)); // true
console.log(circularDeepEqual(first, new Circular('foo'))); // false

strictCircularShallowEqual

Performs the same comparison as circularShallowEqual but performs a strict comparison of the objects. In this includes:

  • Checking non-enumerable properties in object comparisons
  • Checking full descriptor of properties on the object to match
  • Checking non-index properties on arrays
  • Checking non-key properties on Map / Set objects
const array = ['foo'];
const otherArray = ['foo'];

array.push(array);
otherArray.push(otherArray);

array.bar = 'baz';
otherArray.bar = 'baz';

console.log(circularShallowEqual(array, otherArray)); // true
console.log(circularShallowEqual(array, ['foo', 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 factory method that receives the default options used internally, and allows you to override the defaults as needed. This is generally for extreme edge-cases, or supporting legacy environments.

The signature is as follows:

interface Cache<Key extends object, Value> {
  delete(key: Key): boolean;
  get(key: Key): Value | undefined;
  set(key: Key, value: any): any;
}

interface ComparatorConfig<Meta> {
  areArraysEqual: TypeEqualityComparator<any[], Meta>;
  areDatesEqual: TypeEqualityComparator<Date, Meta>;
  areErrorsEqual: TypeEqualityComparator<Error, Meta>;
  areFunctionsEqual: TypeEqualityComparator<(...args: any[]) => any, Meta>;
  areMapsEqual: TypeEqualityComparator<Map<any, any>, Meta>;
  areObjectsEqual: TypeEqualityComparator<Record<string, any>, Meta>;
  arePrimitiveWrappersEqual: TypeEqualityComparator<
    boolean | string | number,
    Meta
  >;
  areRegExpsEqual: TypeEqualityComparator<RegExp, Meta>;
  areSetsEqual: TypeEqualityComparator<Set<any>, Meta>;
  areTypedArraysEqual: TypeEqualityComparatory<TypedArray, Meta>;
  areUrlsEqual: TypeEqualityComparatory<URL, Meta>;
}

function createCustomEqual<Meta>(options: {
  circular?: boolean;
  createCustomConfig?: (
    defaultConfig: ComparatorConfig<Meta>,
  ) => Partial<ComparatorConfig<Meta>>;
  createInternalComparator?: (
    compare: <A, B>(a: A, b: B, state: State<Meta>) => boolean,
  ) => (
    a: any,
    b: any,
    indexOrKeyA: any,
    indexOrKeyB: any,
    parentA: any,
    parentB: any,
    state: State<Meta>,
  ) => boolean;
  createState?: () => { cache?: Cache; meta?: Meta };
  strict?: boolean;
}): <A, B>(a: A, b: B) => boolean;

Create a custom equality comparator. This allows complete control over building a bespoke equality method, in case your use-case requires a higher degree of performance, legacy environment support, or any other non-standard usage. The recipes provide examples of use in different use-cases, but if you have a specific goal in mind and would like assistance feel free to file an issue.

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.

Recipes

Some recipes have been created to provide examples of use-cases for createCustomEqual. Even if not directly applicable to the problem you are solving, they can offer guidance of how to structure your solution.

Benchmarks

All benchmarks were performed on an i9-11900H Ubuntu Linux 24.04 laptop with 64GB of memory using NodeJS version 20.17.0, 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
Testing mixed objects equal...
┌─────────┬─────────────────────────────────┬────────────────┐
│ (index) │ Package                         │ Ops/sec        │
├─────────┼─────────────────────────────────┼────────────────┤
│ 0       │ 'fast-equals'                   │ 1256867.529926 │
│ 1       │ 'fast-deep-equal'               │ 1207041.997437 │
│ 2       │ 'shallow-equal-fuzzy'           │ 1142536.391324 │
│ 3       │ 'react-fast-compare'            │ 1140373.249605 │
│ 4       │ 'dequal/lite'                   │ 708240.354044  │
│ 5       │ 'dequal'                        │ 704655.931143  │
│ 6       │ 'fast-equals (circular)'        │ 595853.718756  │
│ 7       │ 'underscore.isEqual'            │ 433596.570863  │
│ 8       │ 'assert.deepStrictEqual'        │ 310595.198662  │
│ 9       │ 'lodash.isEqual'                │ 232192.454526  │
│ 10      │ 'fast-equals (strict)'          │ 175941.250843  │
│ 11      │ 'fast-equals (strict circular)' │ 154606.328398  │
│ 12      │ 'deep-eql'                      │ 136052.484375  │
│ 13      │ 'deep-equal'                    │ 854.061311     │
└─────────┴─────────────────────────────────┴────────────────┘

Testing mixed objects not equal...
┌─────────┬─────────────────────────────────┬────────────────┐
│ (index) │ Package                         │ Ops/sec        │
├─────────┼─────────────────────────────────┼────────────────┤
│ 0       │ 'fast-equals'                   │ 3795307.779634 │
│ 1       │ 'fast-deep-equal'               │ 2987150.35694  │
│ 2       │ 'react-fast-compare'            │ 2733075.404272 │
│ 3       │ 'fast-equals (circular)'        │ 2311547.685659 │
│ 4       │ 'dequal/lite'                   │ 1156909.54415  │
│ 5       │ 'dequal'                        │ 1151209.161878 │
│ 6       │ 'fast-equals (strict)'          │ 1102248.247412 │
│ 7       │ 'fast-equals (strict circular)' │ 1020639.089577 │
│ 8       │ 'nano-equal'                    │ 1009557.685012 │
│ 9       │ 'underscore.isEqual'            │ 770286.698227  │
│ 10      │ 'lodash.isEqual'                │ 296338.570457  │
│ 11      │ 'deep-eql'                      │ 152741.182224  │
│ 12      │ 'assert.deepStrictEqual'        │ 20163.203513   │
│ 13      │ 'deep-equal'                    │ 3519.448516    │
└─────────┴─────────────────────────────────┴────────────────┘

Caveats that impact the benchmark (and accuracy of comparison):

  • Maps, Promises, and Sets were excluded from the benchmark entirely because no library other than deep-eql fully supported their comparison
  • fast-deep-equal, react-fast-compare and nano-equal throw on objects with null as prototype (Object.create(null))
  • assert.deepStrictEqual does not support NaN or SameValueZero equality for dates
  • deep-eql does not support SameValueZero equality for zero equality (positive and negative zero are not equal)
  • deep-equal does not support NaN and does not strictly compare object type, or date / regexp values, nor uses SameValueZero equality for dates
  • fast-deep-equal does not support NaN or SameValueZero equality for dates
  • nano-equal does not strictly compare object property structure, array length, or object type, nor SameValueZero equality for dates
  • react-fast-compare does not support NaN or SameValueZero equality for dates, and does not compare function equality
  • shallow-equal-fuzzy does not strictly compare object type or regexp values, nor SameValueZero equality for dates
  • underscore.isEqual does not support SameValueZero 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, and browser distributables with rollup
  • clean => run rimraf on the dist folder
  • dev => start webpack playground App
  • dist => run build
  • lint => run ESLint on all files in src folder (also runs on dev script)
  • lint:fix => run lint script, but with auto-fixer
  • prepublish:compile => run lint, test:coverage, transpile:lib, transpile:es, and dist 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 via nyc
  • test:watch => run same script as test but keep persistent watcher

changelog

fast-equals CHANGELOG

5.2.2

  • #139 - Add file extensions to type definition files to allow it to work in projects with NodeNext module resolution

5.2.1

Bugfixes

  • #138 - Actually fix reference to src code in index.d.ts by flattening types in file

5.2.0

Enhancements

  • Support Preact objects in equality comparison

Bugfixes

  • #137 - Fix circular React references in object comparisons

5.1.3

Enhancements

  • #136 - More than double speed of iterables (Map / Set) equality comparisons

Maintenance

  • #135 - Include dequal and dequal/lite in benchmark comparisons

5.1.2

Maintenance

Re-release of 5.1.0 with correct pre-release setup.

5.1.1

DO NOT USE

This was an accidental pre-release when cleaning up release setup.

5.1.0

Enhancements

  • #127 - Add support for custom Function instance comparisons (resolves #118)
  • #128 - Add support for URL instance comparisons (resolves #121)
  • #129 - Add support for Error instance comparisons (resolves #123)
  • #130 - Add support for custom Number instance comparisons (resolves #112)

Bugfixes

  • #132 - Fix assert.deepEqual check in benchmark (resolves #125)
  • #126 - Export explicit types via export type (attempts to resolve #114)

5.0.1

Bugfixes

  • Fix reference to metaOverride in typings and documentation (holdover from temporary API in v5 beta)

5.0.0

Breaking changes

constructor equality now required

To align with other implementations common in the community, but also to be more functionally correct, the two objects being compared now must have equal constructors.

Map / Set comparisons no longer support IE11

In previous verisons, .forEach() was used to ensure that support for Symbol was not required, as IE11 did not have Symbol and therefore both Map and Set did not have iterator-based methods such as .values() or .entries(). Since IE11 is no longer a supported browser, and support for those methods is present in all browsers and Node for quite a while, the comparison has moved to use these methods. This results in a ~20% performance increase.

createCustomEqual contract has changed

To better facilitate strict comparisons, but also to allow for meta use separate from caching, the contract for createCustomEqual has changed. See the README documentation for more details, but froma high-level:

  • meta is no longer passed through to equality comparators, but rather a general state object which contains meta
  • cache now also lives on the state object, which allows for use of the meta property separate from but in parallel with the circular cache
  • equals is now on state, which prevents the need to pass through the separate isEqual method for the equality comparator

createCustomCircularEqual has been removed

You can create a custom circular equality comparator through createCustomEqual now by providing circular: true to the options.

Custom meta values are no longer passed at callsite

To use meta properties for comparisons, they must be returned in a createState method.

Deep links have changed

If you were deep-linking into a specific asset type (ESM / CJS / UMD), they have changed location.

NOTE: You may no longer need to deep-link, as the build resolution has improved.

Enhancements

New "strict" comparators available

The following new comparators are available:

  • strictDeepEqual
  • strictShallowEqual
  • strictCircularDeepEqual
  • strictCircularShallowEqual

This will perform the same comparisons as their non-strict counterparts, but will verify additional properties (non-enumerable properties on objects, keyed objects on Array / Map / Set) and that the descriptors for the properties align.

TypedArray support

Support for comparing all typed array values is now supported, and you can provide a custom comparator via the new areTypedArraysEqual option in the createCustomEqual configuration.

Better build system resolution

The library now leverages the exports property in the package.json to provide builds specific to your method of consumption (ESM / CommonJS / UMD). There is still a minified UMD version available if you want to use it instead.

arePrimitiveWrappersEqual option added to createCustomEqual configuration

If you want a custom comparator for primitive wrappers (new Boolean() / new Number() / new String()) it is now available.

4.0.3

  • Remove unnecessary second strict equality check for objects in edge-case scenarios

4.0.2

  • #85 - createCustomCircularEqual typing is incorrect

4.0.1

  • #81 - Fix typing issues related to importing in index.d.ts file

4.0.0

Breaking Changes

Certain ES2015 features are now required

In previous versions, there were automatic fallbacks for certain ES2015 features if they did not exist:

Due to the omnipresence of support in both browser and NodeJS, these have been deprecated. There is still an option if you require support for these legacy environments, however; see createCustomEqual and createCustomCircularEqual for more details.

createCustomEqual contract has changed

To allow more flexibility and customizability for a variety of edge cases, createCustomEqual now allows override of specific type value comparisons in addition to the general comparator it did prior. See the documentation for more details.

Enhancements

createCustomCircularEqual added

Like createCustomEqual, it will create a custom equality comparator, with the exception that it will handle circular references. See the documentation for more details.

Cross-realm comparisons are now supported

Prior to 4.x.x., instanceof was used internally for checking of object classes, which only worked when comparing objects from the same Realm. This has changed to instead use an object's StringTag, which is not realm-specific.

TypeScript typings improved

For better typing in edge-case scenarios like custom comparators with meta values, typings have been refactored for accuracy and better narrow flow-through.

3.0.3

  • Fix #77 - better circular object validation

3.0.2

  • Fix #73 - support comparison of primitive wrappers
  • #76 - improve speed and accuracy of RegExp comparison in modern environments

3.0.1

  • Fix #71 - use generic types for better type flow-through

3.0.0

Breaking changes

When creating a custom equality comparator via createCustomEqual, the equality method has an expanded contract:

// Before
type EqualityComparator = (objectA: any, objectB: any, meta: any) => boolean;

// After
type InternalEqualityComparator = (
  objectA: any,
  objectB: any,
  indexOrKeyA: any,
  indexOrKeyB: any,
  parentA: any,
  parentB: any,
  meta: any,
) => boolean;

If you have a custom equality comparator, you can ignore the differences by just passing additional undefined parameters, or you can use the parameters to further improve / clarify the logic.

  • Add #57 - support additional metadata for custom equality comparators

2.0.4

  • Fix #58 - duplicate entries in Map / Set can create false equality success
  • #60 - Add documentation for key equality of Map being a part of deepEqual

2.0.3

  • Fix #50 - copy-pasta in cacheable check

2.0.2

  • Optimize iterables comparisons to not double-iterate
  • Optimize loop-based comparisons for speed
  • Improve cache handling in circular handlers
  • Improve stability of memory by reducing variable instantiation

2.0.1

  • Fix #41 - prevent .rpt2_cache directory from being published for better CI environment support (thanks @herberttn)

2.0.0

Breaking changes

  • There are longer fast-equals/es, fast-equals/lib, fast-equals/mjs locations
    • Instead, there are 3 builds in dist for different consumption types:
      • fast-equals.js (UMD / browser)
      • fast-equals.esm.js (ESM / module)
      • fast-equals.cjs.js (CommonJS / main)
  • There is no default export anymore, only the previously-existing named exports
    • To get all into a namespace, use import * as fe from 'fast-equals

Updates

  • Rewritten completely in TypeScript
  • Improve speed of Map / Set comparisons
  • Improve speed of React element comparisons

Fixes

  • Consider pure objects (Object.create(null)) to be plain objects
  • Fix typings for createCustomEqual

1.6.3

  • Check the size of the iterable before converting to arrays

1.6.2

  • Fix #23 - false positives for map
  • Replace uglify with terser
  • Use rollup to build all the distributables (main, module, and browser)
    • Maintain lib and es transpilations in case consumers were deep-linking

1.6.1

  • Upgrade to babel@7
  • Add "sideEffects": false to package.json for better tree-shaking in webpack

1.6.0

1.5.3

  • Fix Map / Set comparison to not require order to match to be equal

1.5.2

  • Improve speed of object comparison through custom hasKey method

1.5.1

  • Fix lack of support for unicode and sticky RegExp flag checks

1.5.0

1.4.1

  • Fix issue where lastIndex was not being tested on RegExp objects

1.4.0

  • Add support for comparing promise-like objects (strict equality only)

1.3.1

  • Make react comparison more accurate, and a touch faster

1.3.0

  • Add support for deep-equal comparisons between react elements
  • Add comparison with react-fast-compare
  • Use rollup for dist file builds

1.2.1

  • Fix errors from TypeScript typings in strict mode (thanks @HitoriSensei)

1.2.0

1.1.0

1.0.6

  • Support invalid date equality via isSameValueZero

1.0.5

  • Replace isStrictlyEqual with isSameValueZero to ensure that shallowEqual accounts for NaN equality

1.0.4

  • Only check values when comparing Set objects (improves performance of Set check by ~12%)

1.0.3

  • Make Map and Set comparisons more explicit

1.0.2

  • Fix symmetrical comparison of iterables
  • Reduce footprint

1.0.1

  • Prevent babel transpilation of typeof into helper for faster runtime

1.0.0

  • Initial release