1. 研究
这里问的是两个对象相等,你直接使用 == 或 === 比较的话,比较的是两个对象的引用地址,所以这里问的应该是对象的结构较。 举个例子,下面这种就属于“结构相等”,当然也可以出现多维数组和嵌套对象。
js
let arr1 = ['a', 'b']
let arr2 = ['a', 'b']
let obj1 = {0: 'a', 1: 'b'}
let obj2 = {0: 'a', 1: 'b'}
isEqual(arr1, arr2) // true
isEqual(obj1, obj2) // true
isEqual(arr1, obj1) // falselet arr1 = ['a', 'b']
let arr2 = ['a', 'b']
let obj1 = {0: 'a', 1: 'b'}
let obj2 = {0: 'a', 1: 'b'}
isEqual(arr1, arr2) // true
isEqual(obj1, obj2) // true
isEqual(arr1, obj1) // false下面来研究一下这个问题,我们知道 JS 中的对象有很多类型,这里我的研究范围是:
- ArrayPlain
- Object
- Map
- Set
- Function 其他的类型,有的不可枚举( WeakMap 、WeakSet),有的不好比较(Function,不可能比较函数签名和函数体吧;
还有 Date 等没有比较意义,如果不是同一个引用地址,直接返回 false 了)。
2. 代码
js
// 我们先完成一些类型判断辅助函数,使得我们的分析可以继续。
function isObject(item) {
// 判断是否为对象类型
return Object(item) === item
}
function typeOf(item) {
// 判断所有类型
return Object.prototype.toString.call(item).slice(8, -1)
}
typeOf([1, 2]) // 'Array'
// 我们的函数签名是 isEqual(item1, item2)
function isEqual(item1, item2) {
if (!isObject(item1) && !isObject(item2)) {
// 两个都是基本类型,直接进行严格比较
if (Number.isNaN(item1) && Number.isNaN(item2)) {
// 注意特判 isEqual(NaN, NaN)
return true
}
return item1 === item2
}
if (!isObject(item1) || !isObject(item2)) {
// 一个对象,一个基本类型,直接返回 false
// 对于基本类型和对象类型,为了防止 == 导致的类型转换,直接返回 false
return false
}
// 辅助函数typeOf()
if (typeOf(item1) !== typeOf(item2)) {
// 类型不同,直接返回 false
return false
}
// 后面比较的都是类型相同的对象类型
if (item1 === item2) {
// 直接比较引用地址,相等则返回 true
return true
}
// 对于 Array 和 Plain Object。所谓 Plain Object 就是 JS 中普通的键值对对象({name: 'peter', age: 20})。
// 其实数组也可以算是一种特殊普通对象,因为键值都是数字,所以一起使用 for k in item 进行递归处理(考虑多维数组和嵌套对象)。
if (['Array', 'Object'].includes(typeOf(item1))) {
// plain object 和 array,for ... in 比较每一项值
let l1 = Object.keys(item1).length
let l2 = Object.keys(item2).length
if (l1 !== l2) {
return false
}
for (let k in item1) {
if (!isEqual(item1[k], item2[k])) {
return false
}
}
return true
}
// 最后处理 Map 和 Set 类型,之所以放到一起,是因为可以通过 [...item] 将其转换为数组,然后进行递归比较。
if (['Map', 'Set'].includes(typeOf(item1))) {
// 处理 map、set 类型,转换为数组再比较
const [arr1, arr2] = [
[...item1],
[...item2]
]
return isEqual(arr1, arr2)
}
// 其他的暂时全部返回 fasle,可以理解为没有比较的意义,比如比较两个函数或者两个 Date 对象之类的。
return false
}// 我们先完成一些类型判断辅助函数,使得我们的分析可以继续。
function isObject(item) {
// 判断是否为对象类型
return Object(item) === item
}
function typeOf(item) {
// 判断所有类型
return Object.prototype.toString.call(item).slice(8, -1)
}
typeOf([1, 2]) // 'Array'
// 我们的函数签名是 isEqual(item1, item2)
function isEqual(item1, item2) {
if (!isObject(item1) && !isObject(item2)) {
// 两个都是基本类型,直接进行严格比较
if (Number.isNaN(item1) && Number.isNaN(item2)) {
// 注意特判 isEqual(NaN, NaN)
return true
}
return item1 === item2
}
if (!isObject(item1) || !isObject(item2)) {
// 一个对象,一个基本类型,直接返回 false
// 对于基本类型和对象类型,为了防止 == 导致的类型转换,直接返回 false
return false
}
// 辅助函数typeOf()
if (typeOf(item1) !== typeOf(item2)) {
// 类型不同,直接返回 false
return false
}
// 后面比较的都是类型相同的对象类型
if (item1 === item2) {
// 直接比较引用地址,相等则返回 true
return true
}
// 对于 Array 和 Plain Object。所谓 Plain Object 就是 JS 中普通的键值对对象({name: 'peter', age: 20})。
// 其实数组也可以算是一种特殊普通对象,因为键值都是数字,所以一起使用 for k in item 进行递归处理(考虑多维数组和嵌套对象)。
if (['Array', 'Object'].includes(typeOf(item1))) {
// plain object 和 array,for ... in 比较每一项值
let l1 = Object.keys(item1).length
let l2 = Object.keys(item2).length
if (l1 !== l2) {
return false
}
for (let k in item1) {
if (!isEqual(item1[k], item2[k])) {
return false
}
}
return true
}
// 最后处理 Map 和 Set 类型,之所以放到一起,是因为可以通过 [...item] 将其转换为数组,然后进行递归比较。
if (['Map', 'Set'].includes(typeOf(item1))) {
// 处理 map、set 类型,转换为数组再比较
const [arr1, arr2] = [
[...item1],
[...item2]
]
return isEqual(arr1, arr2)
}
// 其他的暂时全部返回 fasle,可以理解为没有比较的意义,比如比较两个函数或者两个 Date 对象之类的。
return false
}03. 测试用例
js
// 测试用例
console.log(isEqual(1, '1'))
console.log(isEqual(null, undefined))
console.log(isEqual(100, 100))
console.log(isEqual(NaN, NaN))
console.log(isEqual(NaN, 'aaa'))
console.log(isEqual([1, 2, 3], [1, 2, 3]))
console.log(isEqual([1, 2, 3], [1, 2]))
console.log(isEqual([1, 2, [3, [4, 5, 6]]], [1, 2, [3, [4, 5, 6]]]))
console.log(isEqual({
a: 'a',
b: 'b'
}, {
a: 'a',
b: 'b'
}))
console.log(isEqual({
0: 'a',
1: 'b'
}, ['a', 'b']))
let s1 = Symbol.for('s1'),
s2 = Symbol.for('s1'),
s3 = Symbol('s3')
s4 = Symbol('s4')
s5 = Symbol('s4')
console.log(isEqual(s1, s2))
console.log(isEqual(s3, s4))
console.log(isEqual(s4, s5))
console.log(isEqual(add1, add1))
console.log(isEqual(add1, add2))
function add1(a, b) {
return a + b
}
function add2(a, b) {
return a + b
}
let map1 = new Map().set(1, 'a').set(2, 'b')
let map2 = new Map().set(1, 'a').set(2, 'b')
let map3 = new Map().set(1, {
a: 'a'
}).set(2, 'b').set(3, [1, 2])
let map4 = new Map().set(1, {
a: 'a'
}).set(2, 'b').set(3, [1, 2])
console.log(isEqual(map1, map2))
console.log(isEqual(map3, map4))
let set1 = new Set([1, 2, 2, 3, {
a: 1
}])
let set2 = new Set([1, 2, 2, 3, {
a: 1
}])
let set3 = new Set([1, 2, 2, 3, {
a: 2
}])
console.log(isEqual(set1, set2))
console.log(isEqual(set2, set3))// 测试用例
console.log(isEqual(1, '1'))
console.log(isEqual(null, undefined))
console.log(isEqual(100, 100))
console.log(isEqual(NaN, NaN))
console.log(isEqual(NaN, 'aaa'))
console.log(isEqual([1, 2, 3], [1, 2, 3]))
console.log(isEqual([1, 2, 3], [1, 2]))
console.log(isEqual([1, 2, [3, [4, 5, 6]]], [1, 2, [3, [4, 5, 6]]]))
console.log(isEqual({
a: 'a',
b: 'b'
}, {
a: 'a',
b: 'b'
}))
console.log(isEqual({
0: 'a',
1: 'b'
}, ['a', 'b']))
let s1 = Symbol.for('s1'),
s2 = Symbol.for('s1'),
s3 = Symbol('s3')
s4 = Symbol('s4')
s5 = Symbol('s4')
console.log(isEqual(s1, s2))
console.log(isEqual(s3, s4))
console.log(isEqual(s4, s5))
console.log(isEqual(add1, add1))
console.log(isEqual(add1, add2))
function add1(a, b) {
return a + b
}
function add2(a, b) {
return a + b
}
let map1 = new Map().set(1, 'a').set(2, 'b')
let map2 = new Map().set(1, 'a').set(2, 'b')
let map3 = new Map().set(1, {
a: 'a'
}).set(2, 'b').set(3, [1, 2])
let map4 = new Map().set(1, {
a: 'a'
}).set(2, 'b').set(3, [1, 2])
console.log(isEqual(map1, map2))
console.log(isEqual(map3, map4))
let set1 = new Set([1, 2, 2, 3, {
a: 1
}])
let set2 = new Set([1, 2, 2, 3, {
a: 1
}])
let set3 = new Set([1, 2, 2, 3, {
a: 2
}])
console.log(isEqual(set1, set2))
console.log(isEqual(set2, set3))
liang14658fox