前端模糊搜索功能实现的那些坑和优化技巧

令狐明昊 交互 阅读 2,668
赞 18 收藏
二维码
手机扫码查看
反馈

优化前:卡得不行

之前做个项目,有个模糊搜索功能,数据量大概5000条左右。用户输入一个字符,就开始遍历整个数组匹配,结果就是输几个字母,页面直接卡死几秒钟。Chrome DevTools 显示 JS 执行时间高达3-4秒,用户体验差得要命。

前端模糊搜索功能实现的那些坑和优化技巧

优化前的代码长这样:

function search(keyword) {
  const results = [];
  for (let i = 0; i < dataList.length; i++) {
    if (dataList[i].name.toLowerCase().includes(keyword.toLowerCase())) {
      results.push(dataList[i]);
    }
  }
  return results;
}

每次输入都遍历全部5000条记录,还要转小写比较,CPU占用直接飙升到100%,页面完全没法操作。

找到瓶颈了!

用 Chrome Performance 面板一录,发现 search 函数执行时间占比超过90%。而且用户连续输入的时候,每个字符都会触发一次搜索,造成大量重复计算。

还发现了另一个问题:用户输入 a-b-c 三个字符时,会分别执行三次搜索,而不是等输入完成后一次性搜 “abc”,这样效率低得可怜。

另外,搜索结果太多的话,渲染也是个问题。一次返回几千条数据给前端渲染,浏览器又要卡住。

优化方案来了

试了几种方案,最后确定了这么几个优化点:

1. 防抖处理

防抖是最基本的操作,延迟搜索,避免频繁触发:

function debounce(func, delay) {
  let timeoutId;
  return function (...args) {
    clearTimeout(timeoutId);
    timeoutId = setTimeout(() => func.apply(this, args), delay);
  };
}

const debouncedSearch = debounce((keyword) => {
  const results = performSearch(keyword);
  renderResults(results);
}, 300);

2. 构建搜索索引

这是最关键的一环。预先构建拼音首字母、全拼、分词等索引,搜索时直接查索引:

class SearchIndex {
  constructor(data) {
    this.data = data;
    this.index = new Map();
    this.buildIndex();
  }

  buildIndex() {
    for (let i = 0; i < this.data.length; i++) {
      const item = this.data[i];
      const text = item.name.toLowerCase();
      
      // 构建字符索引
      for (let j = 0; j < text.length; j++) {
        const char = text[j];
        if (!this.index.has(char)) {
          this.index.set(char, []);
        }
        this.index.get(char).push({
          id: i,
          position: j,
          item: item
        });
      }
    }
  }

  search(keyword) {
    keyword = keyword.toLowerCase();
    if (!keyword) return [];

    // 用第一个字符快速过滤
    const candidates = this.index.get(keyword[0]) || [];
    const results = new Set();

    for (const candidate of candidates) {
      if (candidate.item.name.toLowerCase().includes(keyword)) {
        results.add(candidate.item);
      }
    }

    return Array.from(results);
  }
}

// 初始化
const searchIndex = new SearchIndex(dataList);

function performSearch(keyword) {
  return searchIndex.search(keyword);
}

3. 限制返回数量

搜索结果过多时,只返回前100条,避免页面卡顿:

search(keyword) {
  const allResults = super.search(keyword);
  return allResults.slice(0, 100); // 只返回前100条
}

4. Web Worker 处理复杂搜索

对于更复杂的模糊匹配需求,把搜索逻辑放到 Web Worker 中执行:

// main.js
const worker = new Worker('search-worker.js');

function searchWithWorker(keyword) {
  return new Promise((resolve) => {
    worker.postMessage({ type: 'SEARCH', keyword });
    worker.onmessage = (e) => {
      resolve(e.data.results);
    };
  });
}

// search-worker.js
self.onmessage = function(e) {
  if (e.data.type === 'SEARCH') {
    const results = performComplexSearch(e.data.keyword);
    self.postMessage({ results });
  }
};

性能数据对比

优化后的效果还是很明显的:

  • 搜索响应时间:原来的3-4秒降到平均80-150ms
  • CPU占用:原来峰值100%降到5-10%
  • 内存使用:减少约60%,因为不再频繁创建临时数组
  • 用户体验:输入完全无卡顿,搜索结果实时显示

用 Performance 面板重新测试,JS 执行时间从原来的3000ms降到100ms左右,基本上看不到了。页面滚动、点击等交互也恢复正常。

不过这里要注意我踩过的一个坑:Web Worker 虽然能解决主线程阻塞,但通信开销也不小,对于简单场景反而可能更慢。所以要根据实际数据量来决定是否使用。

还有些细节处理

除了上面的核心优化,还有一些细节需要注意:

缓存机制很重要。相同关键词的搜索结果直接从缓存取,不用重复计算:

const cache = new Map();

function cachedSearch(keyword) {
  if (cache.has(keyword)) {
    return cache.get(keyword);
  }
  
  const results = performSearch(keyword);
  cache.set(keyword, results);
  
  // 限制缓存大小,避免内存泄漏
  if (cache.size > 50) {
    const firstKey = cache.keys().next().value;
    cache.delete(firstKey);
  }
  
  return results;
}

另外,对于中文搜索,还需要考虑拼音匹配的问题。引入了一个轻量级的拼音库,为每条数据生成拼音索引,支持输入拼音首字母匹配中文。

最终效果

经过这些优化,现在搜索体验好了很多。输入响应基本在100ms以内,即使连续输入也不会卡顿。数据量再大一些,比如1万条记录,也能保持流畅。

其实这种优化思路不只是适用于模糊搜索,其他需要大量数据处理的场景也可以参考。关键是找对瓶颈,然后针对性地解决,防抖、索引、异步处理这些手段都很实用。

以上是我个人对模糊搜索性能优化的完整讲解,有更优的实现方式欢迎评论区交流。

本文章不代表JZTHEME立场,仅为作者个人观点 / 研究心得 / 经验分享,旨在交流探讨,供读者参考。
发表评论

暂无评论