ac89b5aff6
Using deepEqual, average over 3 calls: - version trigger 1.53ms - version steps 38.3ms - run state 372.8 ms Using stringified hash, average over 3 calls: - version trigger 0.07ms - version steps 0.74 ms - run state 0.521ms Short fastDeepEqual - version trigger 0.028ms - version steps 0.197ms - run state 0.214ms Lib fastDeepEqual - version trigger 0.038ms - version steps 0.187ms - run state 0.230ms
113 lines
3.1 KiB
TypeScript
113 lines
3.1 KiB
TypeScript
// 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<unknown>;
|
|
const secondView = secondValue as unknown as ArrayLike<unknown>;
|
|
|
|
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<string, unknown>)[key],
|
|
(secondValue as Record<string, unknown>)[key],
|
|
)
|
|
) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// true if both NaN, false otherwise
|
|
return firstValue !== firstValue && secondValue !== secondValue;
|
|
};
|