Conversation
Eliminates two per-call allocations: closure creation and WeakMap instantiation. The comparison function is now a static module-level function with visited state passed as a parameter. WeakMap is lazily initialized on first use — non-recursive comparisons (primitives, Date, RegExp, Set, TypedArray) never allocate it. Benchmarks show 8-22% improvement across all comparison types, with leaf types (Date, RegExp) benefiting most.
Replaces DataView.getUint8() byte-by-byte iteration with Uint8Array indexed access, which also improves performance on binary comparisons. BREAKING CHANGE: TypedArray and DataView subviews now compare only the bytes within their byteOffset/byteLength range, not the full underlying ArrayBuffer. Previously, two views into different regions of the same buffer would incorrectly compare as equal.
Converts sequential identity comparisons on constructor into two switch statements. V8 can optimize switch on reference identity into a jump table, reducing per-type dispatch cost. Benchmarks show Date comparison improved from 1.52x to 1.11x vs dequal. Other types show modest gains from reduced branching.
Previously SharedArrayBuffer fell through to plain object comparison, returning true for any two instances regardless of size or content. Now handled via the same byte-level Uint8Array comparison used for ArrayBuffer.
The valueOf/toString comparison path now verifies that both objects share the same function reference before comparing results. Previously, any object with a non-default valueOf was compared by its return value, even when the other object had a different valueOf implementation. Also fixes boxed NaN inconsistency: isEqual(Object(NaN), Object(NaN)) now returns true, consistent with isEqual(NaN, NaN). The valueOf comparison uses the same self-comparison NaN check as the primitive path.
Previously the key count comparison happened after the property loop, meaning objects where b had extra keys required a full iteration of a before discovering the mismatch. Moving the check before the loop exits immediately on key count difference.
Add symbol-keyed properties to Gotchas as an intentional omission. Update custom classes entry to reflect the valueOf/toString implementation-matching requirement added in the previous commit.
Add detailed Comparison Behavior section explaining how each data type is compared and the design rationale behind key decisions: - Sets/Maps: reference equality follows SameValueZero identity model - Symbols: excluded as hidden metadata, not data-carrying properties - Custom classes: valueOf/toString terminal comparison semantics - TypedArrays: byte-level comparison preserving NaN bit patterns - Circular references: tracked via lazily allocated WeakMap Update Performance section to reflect lazy allocation optimization. Add missing supported types: SharedArrayBuffer, DataView, boxed primitives. Sync CLAUDE.md gotchas and metadata with README changes.
There was a problem hiding this comment.
Pull request overview
This PR delivers a comprehensive v2 overhaul focused on performance, correctness, and documentation. The core implementation moves from a closure-based approach to a module-level recursive function with lazy WeakMap allocation, achieving significant performance gains (17-28% for leaf types) by avoiding unnecessary allocations for non-recursive comparisons.
Changes:
- Lazy WeakMap allocation eliminates overhead for leaf types (Date, RegExp, Set, TypedArrays, ArrayBuffers) while maintaining circular reference detection for recursive structures
- TypedArray/DataView comparisons now correctly respect
byteOffsetandbyteLength, comparing only the viewed slice instead of the full buffer - SharedArrayBuffer support added (previously fell through to incorrect object comparison)
- valueOf/toString hardening requires matching function references, fixing boxed NaN edge cases and preventing false equality between objects with different arrow function implementations
- Comprehensive documentation of comparison semantics, design rationale, and per-type behavior
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| src/is-equal.ts | Core refactor: lazy WeakMap, switch-based dispatch, corrected TypedArray/DataView/SharedArrayBuffer handling, hardened valueOf/toString checks, early key count optimization |
| src/is-equal.test.ts | New tests for boxed NaN handling and valueOf/toString function reference requirements |
| src/fixtures/tests.ts | Updated valueOf/toString expectations to reflect breaking change; added comprehensive TypedArray subview, DataView subview, and SharedArrayBuffer test suites |
| README.md | New "Comparison Behavior" section documenting per-type semantics and design decisions; updated Performance section explaining lazy allocation strategy; expanded Supported Types list |
| CLAUDE.md | Updated implementation notes, gotchas section, and git commit configuration guidance |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Contributor
|
🎉 This PR is included in version 2.0.0 🎉 The release is available on: Your semantic-release bot 📦🚀 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
3d074fd)byteOffsetandbyteLength, comparing only the viewed slice instead of the full underlying buffer (daa5e2d)953ba56)4f71a6f)069afbd)183f29d)85b2158,5649c5f)Breaking Changes
valueOf/toStringcomparison now requires both instances to share the same function reference. Objects with different arrow function implementations forvalueOfnow fall through to property comparison instead of comparing byvalueOf()result.Benchmark (vs master)
Biggest improvements on leaf types from lazy WeakMap + closure removal: