// Source: https://github.com/epoberezkin/fast-deep-equal/blob/master/src/index.jst export const fastDeepEqual = ( firstValue: unknown, secondValue: unknown, ): boolean => { if (firstValue === secondValue) return true; if ( firstValue && secondValue && typeof firstValue === 'object' && typeof secondValue === 'object' ) { if (firstValue.constructor !== secondValue.constructor) return false; if (Array.isArray(firstValue)) { const firstArray = firstValue; const secondArray = secondValue as unknown[]; if (firstArray.length !== secondArray.length) return false; for (let index = firstArray.length; index-- !== 0; ) { if (!fastDeepEqual(firstArray[index], secondArray[index])) return false; } return true; } if (firstValue instanceof Map && secondValue instanceof Map) { if (firstValue.size !== secondValue.size) return false; for (const [key] of firstValue.entries()) { if (!secondValue.has(key)) return false; } for (const [key, value] of firstValue.entries()) { if (!fastDeepEqual(value, secondValue.get(key))) return false; } return true; } if (firstValue instanceof Set && secondValue instanceof Set) { if (firstValue.size !== secondValue.size) return false; for (const item of firstValue) { if (!secondValue.has(item)) return false; } return true; } if (ArrayBuffer.isView(firstValue) && ArrayBuffer.isView(secondValue)) { const firstView = firstValue as unknown as ArrayLike; const secondView = secondValue as unknown as ArrayLike; if (firstView.length !== secondView.length) return false; for (let index = firstView.length; index-- !== 0; ) { if (firstView[index] !== secondView[index]) return false; } return true; } if (firstValue.constructor === RegExp) { const firstRegex = firstValue as RegExp; const secondRegex = secondValue as RegExp; return ( firstRegex.source === secondRegex.source && firstRegex.flags === secondRegex.flags ); } if (firstValue.valueOf !== Object.prototype.valueOf) { return firstValue.valueOf() === secondValue.valueOf(); } if (firstValue.toString !== Object.prototype.toString) { return firstValue.toString() === secondValue.toString(); } const firstKeys = Object.keys(firstValue); const secondKeys = Object.keys(secondValue); if (firstKeys.length !== secondKeys.length) return false; for (let index = firstKeys.length; index-- !== 0; ) { if (!(firstKeys[index] in secondValue)) return false; } for (let index = firstKeys.length; index-- !== 0; ) { const key = firstKeys[index]; if ( !fastDeepEqual( (firstValue as Record)[key], (secondValue as Record)[key], ) ) { return false; } } return true; } // true if both NaN, false otherwise return firstValue !== firstValue && secondValue !== secondValue; };