前端模糊搜索功能实现的那些坑和优化技巧
优化前:卡得不行
之前做个项目,有个模糊搜索功能,数据量大概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万条记录,也能保持流畅。
其实这种优化思路不只是适用于模糊搜索,其他需要大量数据处理的场景也可以参考。关键是找对瓶颈,然后针对性地解决,防抖、索引、异步处理这些手段都很实用。
以上是我个人对模糊搜索性能优化的完整讲解,有更优的实现方式欢迎评论区交流。

暂无评论