我们比较两个对象,比较好的一种办法是直接比较两个对象的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计算。