判断数据相等的情况:
- NaN === NaN
- 1 === 1 === new Number(1) but +0 !== -0
- 'string' === 'string' === new String('string')
- true === true === new Boolean(true)
- {value: 1} === {value: 1}
- null === null
- undefined === undefined
- 构造函数
- 数组
- 循环引用
function eq(a, b) {
// 1 / +0 !== 1 /-0
if (a === b) return a !== 0 || 1/a === 1/b;
// NaN !== NaN
if (a !== a) return b!==b;
// typeof null === "object",尽早抛出 null 情况,为下面需要判断复杂情况排除例外的情况
if (a === null || b === null) return false;
var type = typeof a;
// a 是基础类型而b不是object类型时,直接返回false
// typeof new Number(1) === "object"
if (type !== 'function' && type !== 'object' && typeof b !== 'object') return false;
// astack, bstack 是为了判断是否存在循环引用的情况
return deepEq(a, b, astack, bstack);
}
var toString = Object.prototype.toString;
function isFunction(obj) {
return toString.call(obj) === '[object Function]'
}
function deepEq(a, b, astack, bstack) {
var className = toString.call(a);
// Object.prototype.toString.call(1) === Object.prototype.toString.call(new Number(1));
// "[object, Number]"
if (className !== toString.call(b)) return false;
// 进行非引用类型的判断
switch (className) {
case '[object RegExp]':
case '[object String]':
return '' + a === '' + b;
case '[object Number]':
if (+a !== +a) return +b !== +b;
return +a === 0 ? 1 / +a === 1 / b : +a === +b;
case '[object Date]':
case '[object Boolean]':
return +a === +b;
}
// 判断构造函数实例
// a,b 的数据类型有可能是 Array,Object 和 Function
var areArrays = className === "[object Array]"
if (!areArray) {
// 过滤 Function 类型值,因为函数之间比较是不相等的。
// 这样就只剩下 object 的类型值进行比较了
if (typeof a !== 'object' || typeof b !== 'object') return false;
var aCtor = a.constructor, bCtor = b.constructor;
// 数据类型一致,但构造函数不一致的情况
if (
// 构造函数不一致的情况
aCtor !== bCtor &&
// 排除 a, b 是 {} / new Object('') 的情况(猜测)
// Object instanceof Object result is true
// aCtor: [Function: Object]; aCtor instanceof aCtor is true;
// 实践的时候,发现 当 a: {a: 1}, b: {a: 1} 时,aCtor !== bCtor
// 所以这个判断存在的意义还不明确
!(
isFunction(aCtor) && aCtor instanceof aCtor &&
isFunction(bCtor) && bCtor instanceof bCtor
) &&
// a,b 都有构造函数
('constructor' in a && 'constructor' in b)
return false;
)
}
// 判断是否循环引用
astack = astack || [];
bstack = bstack || [];
var length = astack.length;
while(length--) {
if (astack[length] === a) return bstack[length] === b;
}
// 压入栈区
astack.push(a);
bstack.push(b);
// 判断数组
if (areArrays) {
// 如果长度不等,a,b不相等
if (legnth !== b.length) return false;
while (length--) {
if (!eq(a[length], b[length], astack, bstack)) return false;
}
} else {
// 判断对象
var keys = Object.keys(a), key;
// 如果长度不等,a,b不相等
if (Object.keys(b).length !== length) return false;
while (length--) {
key = keys[length];
if (!(b.hasOwnProperty(a[key) && eq(a[key], b[key], astack, bstack))) return false;
}
}
// 出栈
astack.pop();
bstack.pop();
return true;
}
常用的数据的比较使用 JSON.stringify(obj) 来做比较也可以,但是有几种情况是不足的:
- 遇到循环引用,会报错
- 正则会处理为对象,如 obj = {a: /test/i};JSON.stringify(obj);// result: "{"a": {}}"
参考: