我们比较两个对象,比较好的一种办法是直接比较两个对象的hash值,对于相同结构和内容的对象,hash值应该相等。怎么获取对象的hash值?
/**
* 获取一个字符串的hash
* @param {*} str
*/
function getStringHash(str) {
let hash = 5381
let i = str.length
while(i) {
hash = (hash * 33) ^ str.charCodeAt(--i)
}
return hash >>> 0
}
/**
* 格式化对象,类似JSON.stringify,但是支持自引用嵌套
* @param {*} obj
*/
export function stringifyObject(obj) {
const exists = [obj] // 存储已经处理过的,避免死循环
const used = [] // 记录被用到的引用标记
const stringifyObjectByKeys = (obj) => {
if (isArray(obj)) {
let items = obj.map((item) => {
if (item && typeof item === 'object') {
return stringifyObjectByKeys(item)
}
else {
return JSON.stringify(item)
}
})
let str = '[' + items.join(',') + ']'
return str
}
let str = '{'
let keys = Object.keys(obj)
let total = keys.length
keys.sort()
keys.forEach((key, i) => {
let value = obj[key]
str += key + ':'
if (value && typeof value === 'object') {
let index = exists.indexOf(value)
if (index > -1) {
str += '#' + index
used.push(index)
}
else {
exists.push(value)
let num = exists.length - 1
str += '#' + num + stringifyObjectByKeys(value)
}
}
else {
str += JSON.stringify(value)
}
if (i < total - 1) {
str += ','
}
})
str += '}'
return str
}
let str = stringifyObjectByKeys(obj)
exists.forEach((item, i) => {
if (!used.includes(i)) {
str = str.replace(new RegExp(`:#${i}`, 'g'), ':')
}
})
if (used.includes(0)) {
str = '#0' + str
}
return str
}
/**
* 获取一个对象的hash值
* @param {*} obj
*/
export function getObjectHash(obj) {
if (typeof obj !== 'object') {
return
}
let str = stringifyObject(obj)
let hash = getStringHash(str)
return hash
}
这里面考虑到了一个非常严重的问题,就是当一个对象如果存在自引用的情况,普通的遍历会引起死循环而导致程序挂掉,但这个函数不会,它会用一个临时变量去记录已经处理过的对象,如果在下一次处理到一个对象时,首先会去临时变量里面查找,如果找到的话,会把这个变量的索引值作为参考系数,直接用于hash计算。

