innerHTML.replace()在长文本中会卡死,因其每次赋值都触发全量DOM重建、重排与重绘;应改用TreeWalker遍历TEXT_NODE配合Range.surroundContents()安全高亮,中文需加Unicode边界匹配,并节流处理。

直接用 innerHTML.replace() 做搜索高亮,在 2000 行以上文本中必然卡死——这不是 JS 慢,是浏览器在反复解析 HTML、重建 DOM、触发重排。
为什么 innerHTML.replace() 在长文本里会假死
每次赋值 element.innerHTML = ...,浏览器必须:销毁旧 DOM 树 → 解析新 HTML 字符串 → 构建新节点 → 重排(reflow)→ 重绘(repaint)。哪怕只改一个词,整块容器内容全被重建。
- 正则匹配本身很快,但字符串拼接 + innerHTML 赋值 = 强制全量 DOM 重建
- 原始 HTML 若含
<strong></strong>、<a></a>等标签,正则极易切碎结构,后续Range.surroundContents()报InvalidNodeTypeError - 用户连续输入时,高频触发该逻辑,主线程持续满载,输入框响应延迟飙升
用 TreeWalker 遍历文本节点做安全高亮
真正可访问、语义正确、不破坏结构的高亮,必须只操作 Node.TEXT_NODE,跳过所有元素、注释、脚本节点。
- 用
document.createTreeWalker(root, NodeFilter.SHOW_TEXT)获取纯文本节点流 - 对每个
node.nodeValue调用.matchAll(new RegExp(escapedKeyword, 'gi')),注意先escapeHtml(keyword)处理点、括号等正则元字符 - 每匹配到一处,创建
document.createElement('mark'),再用Range定位起止偏移,调用range.surroundContents(mark) - 中文场景需加 Unicode 边界:
/(?:^|\p{Z}|\p{P})keyword(?=\p{Z}|\p{P}|$)/u,避免“服务”误命中“服务器”
CSS Highlights 是更快但有兼容性代价的选择
如果你不需要屏幕阅读器支持或 SEO 索引(比如内部工具、文档预览),CSS.highlights 可把高亮从 DOM 操作降级为纯渲染层指令,性能提升一个数量级。
立即学习“前端免费学习笔记(深入)”;
- 检测支持:
if ('highlights' in CSS) { ... },否则 fallback 到 TreeWalker 方案 - 用
Range标记匹配位置,批量塞进new Highlight(ranges),再注册:CSS.highlights.set("search-result", highlight) - 样式只能通过
::highlight(search-result)定义,支持background-color、color、text-shadow,不支持background-image或transform - 清除高亮用
CSS.highlights.clear(),不涉及 DOM 修改,无重排
容易被忽略的细节:中文边界与连续输入节流
TreeWalker 方案看似稳妥,但两个点常被跳过:一是中文词边界未处理,导致“接口”高亮“接”字却连带“口”字;二是用户快速输入时没做节流,matchAll + Range 创建频繁触发 GC,反而比简单替换更卡。
实际部署时,matchAll 必须加 /u 标志和 Unicode 类断言;高频触发逻辑建议用 requestIdleCallback 或防抖(debounce 300ms),且每次只处理可视区域内的节点。



















