You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

优化HTMLLIElement数组CSS类更新操作的执行性能

大列表搜索过滤卡顿优化

问题描述

我有一个存储HTMLLIElement的数组,需要根据搜索输入框的取值给对应元素设置CSS类。当数组元素量较大时,页面会出现卡顿、响应迟缓的问题,经定位性能瓶颈来自每个li元素的文本比对逻辑,初始实现代码如下:

// 获取搜索输入值转小写,拿到选择列表下所有li转为数组
const searchValue = myHappySeachInput.toLowerCase();
const selectListItems = myHappySelectList.querySelectorAll("li");
const myHappyArray = Array.from(selectListItems);

// 遍历每个li,比对搜索值和元素内文本,按需增删CSS类
myHappyArray.forEach((li: HTMLLIElement) => {
  const label = li.querySelector("label");
  const text = label ? label.innerText.toLowerCase() : "";

  if (text.indexOf(searchValue) < 0) {
    li.classList.add("display-none");
  } else {
    li.classList.remove("display-none");
  }
});

后续我参考建议拆分了查询匹配和类名赋值逻辑,做了第一版优化:

// 先给所有li加上隐藏类
myHappyArray.forEach((li) => li.classList.add("display-none"));
// 过滤出所有匹配搜索值的li
const matchingLiElements = myHappyArray.filter((li) => li.querySelector("label")?.innerText.toLowerCase().includes(searchValue));
// 仅给匹配的元素移除隐藏类
matchingLiElements.forEach((li) => li.classList.remove("display-none"));

这版优化已经将执行耗时降低了80%-90%,但仍有缺陷:每次触发搜索时还是需要更新全量数组元素的CSS类,希望能找到更优的实现方案。

进一步优化方案

核心优化思路是砍掉重复计算、缩小DOM操作范围,可以从这几个点落地:

  • 缓存静态数据:列表首次渲染完成后只遍历一次所有li,提前把每个li对应的label引用、label文本转小写后的结果缓存好(可以挂在元素自定义属性上,也可以用WeakMap存储,避免内存泄漏),后续搜索时直接读缓存,完全省掉每次搜索触发的querySelectorDOM查询、innerText取值、小写转换这类重复开销
  • 增量更新DOM:记录上一次搜索的匹配元素集合,本次搜索完成后,仅对两类元素做类名修改:①上次匹配、本次不匹配的元素,加隐藏类;②本次匹配、上次未匹配的元素,移除隐藏类。状态没有变化的元素完全不需要操作,彻底避免全量修改类名的开销
  • 加搜索防抖:给输入框的搜索回调加100-200ms的防抖,用户连续快速输入时不执行过滤逻辑,等输入停顿后再触发计算,减少无意义的执行次数
  • 超大量列表优化重排:如果列表元素量级达到万级,可以在批量修改类名前先把列表父容器设为display: none,所有类名修改完成后再恢复显示,把多次重排合并为一次

参考实现代码:

// 初始化阶段仅执行一次:缓存搜索用的文本
const selectListItems = myHappySelectList.querySelectorAll("li");
const myHappyArray = Array.from(selectListItems) as HTMLLIElement[];
const textCache = new WeakMap<HTMLLIElement, string>();
myHappyArray.forEach(li => {
  const label = li.querySelector("label");
  textCache.set(li, label ? label.innerText.toLowerCase() : "");
});
// 记录上一次的匹配集合
let lastMatchedSet = new Set<HTMLLIElement>();

// 搜索逻辑,绑定到输入框防抖后的回调上
function doSearch(inputValue: string) {
  const searchValue = inputValue.toLowerCase();
  // 匹配过程无任何DOM操作,直接读缓存
  const currentMatchedSet = new Set(
    myHappyArray.filter(li => textCache.get(li)!.includes(searchValue))
  );

  // 仅操作状态变化的元素
  lastMatchedSet.forEach(li => {
    if (!currentMatchedSet.has(li)) {
      li.classList.add("display-none");
    }
  });
  currentMatchedSet.forEach(li => {
    if (!lastMatchedSet.has(li)) {
      li.classList.remove("display-none");
    }
  });

  lastMatchedSet = currentMatchedSet;
}

这套实现下,搜索时的DOM操作量会随着搜索结果的变化量缩小,绝大多数场景下只需要操作极少量元素,哪怕是上万条的列表也不会出现卡顿。


内容的提问来源于stack exchange,提问作者Veckodag

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 18:51:20