Set对比:一次筛选优化复盘
Set对比最有说服力的方式,不是背复杂度,而是看一次真实筛选改造。我们把“商品列表匹配收藏状态”的代码从 Array.includes 改成 Set.has,耗时从卡顿到丝滑,但也遇到了一点反直觉细节。
Q1:这个案例到底在比什么?
场景是一个后台商品页:接口返回 12000 个商品,另一个接口返回用户收藏的 3800 个商品ID。页面要给每个商品打上 isFavorite。老代码写法很直:products.map 时,用 favoriteIds.includes(product.id) 判断。
这就是典型 set对比 场景:Array.includes 每次都要在收藏数组里找一遍,12000 次循环乘 3800 次潜在查找,最坏情况很吓人。改造后,把 favoriteIds 转成 favoriteSet,再用 favoriteSet.has(product.id)。
Q2:改之前为什么会卡?
不是 includes 写错了,而是它不适合高频查找。includes 像在一排柜子里挨个翻钥匙,数据少没问题;数据一多,翻几万次,浏览器主线程就开始喘。
当时页面表现是:商品列表接口回来后,白屏多停了大概半秒,低配电脑更明显。Chrome Performance 里能看到一段长任务,主要耗在数组查找上。这个问题不玄学,数据量一上来就暴露。
Q3:改成Set后代码长什么样?
核心就两行:const favoriteSet = new Set(favoriteIds); 然后 products.map(item => ({...item, isFavorite: favoriteSet.has(item.id)}))。逻辑没变,只是把“每次线性查找”换成了“先建索引再查询”。
这里有个小坑:商品ID类型必须一致。favoriteIds 里如果是字符串 "1001",product.id 是数字 1001,Set.has 会返回 false。我们最后统一在接口适配层转成字符串,避免散落在业务代码里到处 String(id)。
Q4:Set一定比Array快吗?
不一定。这个 set对比 的结论只适用于“多次查找”。如果 favoriteIds 只有 20 个,products 只有 50 条,你改成 Set 基本没必要,性能差异不值得让代码多一层转换。
还有一种情况:你只查一次,比如判断某个ID是否存在,直接 includes 更顺手。Set 的优势来自复用:建一次 Set,查几千、几万次,它才开始回血。
Q5:这次复盘留下了什么经验?
我现在判断用不用 Set,会先问三个问题:数据量是否超过几百?是否重复查询?是否只关心“存在/不存在”?三个答案里有两个是“是”,Set 基本值得上。
但别把优化写成炫技。最稳的做法是把转换放在靠近数据源的位置,比如接口返回后立刻建 Set;业务组件只读 has 结果。这样代码不会散,后面换接口也好维护。
常见问题
- Set和Array对比,核心区别是什么?
- Array 是有序列表,适合展示和保留重复;Set 是唯一集合,适合去重和高频存在性判断。
- includes改成has能提升多少?
- 取决于数据量和查询次数。几百条以内通常无感,上万条数据、多次查询时提升会很明显。
- Set.has为什么查不到明明存在的ID?
- 大概率是类型不同。数字 1 和字符串 "1" 在 Set 里不是同一个值,接口数据要先统一类型。