发布日期:2022 年 7 月 12 日
dequal 是一款高性能、无依赖的深度相等比较工具,核心职责是判断两个值(包括原始类型、复杂引用类型)是否「内容相等」,而非「引用相等」。它支持 Date、RegExp、Array、Set、Map、ArrayBuffer 等多种内置类型,同时处理了 NaN 等特殊值,是比 === 和 JSON.stringify 更全面的相等比较方案。
function dequal(foo, bar) {
var ctor, len, tmp;
// 优先判断引用相等(如同一对象 / 数组)或原始类型严格相等,直接返回 true
if (foo === bar) return true;
if (foo && bar && (ctor = foo.constructor) === bar.constructor) {
// 1、Date 类型
// Date 对象的核心是时间戳,相同时间戳即内容相等
if (ctor === Date) return foo.getTime() === bar.getTime();
// 2、RegExp 类型
// 正则的核心是「模式 + 修饰符」,toString() 会返回完整的正则表达式字符串
if (ctor === RegExp) return foo.toString() === bar.toString();
// 3、Array 类型
if (ctor === Array) {
// 先判断长度是否相等,长度不同直接返回 false;
if ((len = foo.length) === bar.length) {
// 逆序遍历数组,递归调用 dequal 对比每一项
while (len-- && dequal(foo[len], bar[len]));
}
// 遍历完成后若 len === -1(所有项均匹配),返回 true。
return len === -1;
}
// 4、Set 类型
if (ctor === Set) {
// 先判断大小是否相等,大小不同直接返回 false;
if (foo.size !== bar.size) {
return false;
}
for (len of foo) {
tmp = len;
if (tmp && typeof tmp === "object") {
// 遍历 foo 的每一个元素,若元素是对象,通过 find 函数在 bar 中查找匹配的对象
tmp = find(bar, tmp);
if (!tmp) return false;
}
if (!bar.has(tmp)) return false;
}
// 验证 bar 是否包含该元素,所有元素均存在则返回 true。
return true;
}
// 5、Map 类型
if (ctor === Map) {
// 先判断大小是否相等,大小不同直接返回 false;
if (foo.size !== bar.size) {
return false;
}
for (len of foo) {
tmp = len[0];
if (tmp && typeof tmp === "object") {
tmp = find(bar, tmp);
if (!tmp) return false;
}
if (!dequal(len[1], bar.get(tmp))) {
return false;
}
}
return true;
}
// 6、ArrayBuffer / DataView 类型
if (ctor === ArrayBuffer) {
// 先转为 Uint8Array(8 位无符号整数数组),再逐字节对比
foo = new Uint8Array(foo);
bar = new Uint8Array(bar);
} else if (ctor === DataView) {
if ((len = foo.byteLength) === bar.byteLength) {
// 通过 getInt8(len) 逐字节读取二进制数据对比
while (len-- && foo.getInt8(len) === bar.getInt8(len));
}
return len === -1;
}
// 7、数组视图(如 Uint8Array、Float32Array)
if (ArrayBuffer.isView(foo)) {
if ((len = foo.byteLength) === bar.byteLength) {
// 逐元素对比,长度不同直接返回 false。
while (len-- && foo[len] === bar[len]);
}
return len === -1;
}
// 8、普通对象(非内置类型的对象)
if (!ctor || typeof foo === "object") {
len = 0;
// 遍历 foo 的所有可枚举属性
for (ctor in foo) {
if (has.call(foo, ctor) && ++len && !has.call(bar, ctor)) return false;
if (!(ctor in bar) || !dequal(foo[ctor], bar[ctor])) return false;
}
return Object.keys(bar).length === len;
}
}
return foo !== foo && bar !== bar;
}
var has = Object.prototype.hasOwnProperty;
function find(iter, tar, key) {
for (key of iter.keys()) {
if (dequal(key, tar)) return key;
}
}
import { dequal } from 'dequal';
dequal(1, 1); //=> true
dequal({}, {}); //=> true
dequal('foo', 'foo'); //=> true
dequal([1, 2, 3], [1, 2, 3]); //=> true
dequal(dequal, dequal); //=> true
dequal(/foo/, /foo/); //=> true
dequal(null, null); //=> true
dequal(NaN, NaN); //=> true
dequal([], []); //=> true
dequal(
[{ a:1 }, [{ b:{ c:[1] } }]],
[{ a:1 }, [{ b:{ c:[1] } }]]
); //=> true
dequal(1, '1'); //=> false
dequal(null, undefined); //=> false
dequal({ a:1, b:[2,3] }, { a:1, b:[2,5] }); //=> false
dequal(/foo/i, /bar/g); //=> false