JavaScript中Set存储对象无法去重的原因及实现对象唯一存储的最佳实践
问题本质
Set判断元素唯一性采用SameValueZero比较算法:对于基础类型(字符串、数字、布尔值等)按值比较,对于引用类型(对象、数组等)则按内存引用地址比较,而非对比对象的属性内容。你每次调用scrapedMessages.add()时传入的都是新创建的对象,哪怕多个对象的属性和值完全一致,它们的内存地址也不相同,因此Set会将其判定为不同元素,最终出现重复数据。
Set本身可以实现对象去重,但需要你自行定义判重的依据,以下是适配Node.js爬虫场景的可行方案和最佳实践:
推荐解决方案
方案1:业务唯一键判重(性能最优,适配你的爬虫场景)
你的爬取数据中text+senderID+timestamp三个字段可唯一标识一条消息,直接拼接这三个字段作为判重依据即可,性能远高于全对象序列化:
// 用Set存储唯一键做判重,数组存储原对象 const uniqueMsgKeys = new Set() const scrapedMessages = [] const currentMsg = { text, ...(images.length > 0 && { images }), senderID, timestamp, } // 用特殊分隔符拼接,避免不同字段内容拼接后撞串 const msgUniqueKey = `${text}|||${senderID}|||${timestamp}` if (!uniqueMsgKeys.has(msgUniqueKey)) { uniqueMsgKeys.add(msgUniqueKey) scrapedMessages.push(currentMsg) }
方案2:排序后序列化判重(适配无明确唯一键的场景)
如果爬取的对象结构不固定,没有可直接复用的业务唯一键,可以先对对象的键排序后再序列化,避免因键顺序不同导致相同内容的对象序列化结果不一致:
const uniqueSerialized = new Set() const scrapedMessages = [] const currentMsg = { text, ...(images.length > 0 && { images }), senderID, timestamp, } // 按字母顺序排序对象键后再序列化 const sortedStr = JSON.stringify(currentMsg, Object.keys(currentMsg).sort()) if (!uniqueSerialized.has(sortedStr)) { uniqueSerialized.add(sortedStr) scrapedMessages.push(currentMsg) }
方案3:用Map实现一键存取值(适合需要直接获取唯一对象集合的场景)
如果不需要单独维护判重Set和对象数组,可以用Map存储,键为唯一标识,值为原对象,最终直接取Map的values即可得到去重后的对象集合:
const msgMap = new Map() const currentMsg = { text, ...(images.length > 0 && { images }), senderID, timestamp, } const msgUniqueKey = `${text}|||${senderID}|||${timestamp}` msgMap.set(msgUniqueKey, currentMsg) // 最后直接导出所有唯一对象 const scrapedMessages = Array.from(msgMap.values())
最佳实践建议
- 爬虫场景数据量通常较大,优先选择业务唯一键判重,性能比全对象序列化高1~2个数量级
- 若必须使用序列化判重,一定要先对对象的键做排序处理,否则会出现相同内容被判定为不同的问题
- 不要直接将普通对象存入Set做去重,除非你能保证相同内容的对象指向同一个内存引用
内容的提问来源于stack exchange,提问作者coolsaint
相关产品推荐
相关产品推荐

