UNPKG

fast-is-equal

Version:

Blazing-fast equality checks, minus the baggage. A lean, standalone alternative to Lodash's isEqual—because speed matters.

129 lines (128 loc) • 4.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.fastIsEqual = fastIsEqual; function fastIsEqual(a, b) { // Fast path for strict equality if (a === b) return true; // Handle NaN if (Number.isNaN(a) && Number.isNaN(b)) return true; // Early exit for null/undefined or type mismatch if (a == null || b == null || typeof a !== typeof b) return a === b; // Check if both are arrays or both are not arrays const aIsArray = Array.isArray(a); const bIsArray = Array.isArray(b); if (aIsArray !== bIsArray) return false; // Check if both are the same type of object (e.g., both Map, both Set, etc.) if (a.constructor !== b.constructor) return false; // Fast path for arrays if (aIsArray && bIsArray) { if (a.length !== b.length) return false; for (let i = 0; i < a.length; i++) { const elemA = a[i]; const elemB = b[i]; if (elemA === elemB) continue; if (Number.isNaN(elemA) && Number.isNaN(elemB)) continue; if (typeof elemA === 'object' || typeof elemB === 'object' || typeof elemA === 'function' || typeof elemB === 'function') { return deepEqual(elemA, elemB, new Map()); } if (elemA !== elemB) return false; } return true; } // For other non-primitive types, use deepEqual with a visited Map return deepEqual(a, b, new Map()); } function deepEqual(valA, valB, visited) { // Strict equality and NaN checks if (valA === valB) return true; if (Number.isNaN(valA) && Number.isNaN(valB)) return true; if (valA == null || valB == null) return valA === valB; if (typeof valA !== typeof valB) return false; // Handle Dates if (valA instanceof Date && valB instanceof Date) { return valA.getTime() === valB.getTime(); } // Handle Regular Expressions if (valA instanceof RegExp && valB instanceof RegExp) { return valA.toString() === valB.toString(); } // Handle Promises (reference equality) if (valA instanceof Promise && valB instanceof Promise) { return valA === valB; } // Handle Arrays if (Array.isArray(valA) && Array.isArray(valB)) { if (valA.length !== valB.length) return false; for (let i = 0; i < valA.length; i++) { const elemA = valA[i]; const elemB = valB[i]; if (elemA === elemB) continue; if (Number.isNaN(elemA) && Number.isNaN(elemB)) continue; if (typeof elemA === 'object' || typeof elemB === 'object' || typeof elemA === 'function' || typeof elemB === 'function') { if (!deepEqual(elemA, elemB, visited)) return false; } else if (elemA !== elemB) { return false; } } return true; } // Handle Maps if (valA instanceof Map && valB instanceof Map) { if (valA.size !== valB.size) return false; for (const [key, value] of valA) { if (!valB.has(key) || !deepEqual(value, valB.get(key), visited)) return false; } return true; } // Handle Sets if (valA instanceof Set && valB instanceof Set) { if (valA.size !== valB.size) return false; for (const value of valA) { if (!valB.has(value)) return false; } return true; } // Handle Objects with circular reference tracking if (typeof valA === 'object' && typeof valB === 'object') { if (visited.has(valA) && visited.get(valA) === valB) return true; visited.set(valA, valB); const keysA = Object.keys(valA); const keysB = Object.keys(valB); if (keysA.length !== keysB.length) return false; for (const key of keysA) { if (!Object.prototype.hasOwnProperty.call(valB, key) || !deepEqual(valA[key], valB[key], visited)) { return false; } } return true; } // Default to strict equality return valA === valB; }