主线程运行diff会卡住编辑器,因diff_main是cpu密集型操作,处理超5000字符html时单次耗时200ms+,完全阻塞dom更新、事件响应与光标渲染;web worker可解此困,但需规避全局对象不可用、html未预处理、非序列化数据丢失三大坑,并严格划分主线程(清洗、分块、渲染)与worker(纯字符串diff计算)职责。

为什么主线程跑 diff 会卡住编辑器
因为 diff_main 这类函数是纯 CPU 密集型操作,尤其在处理超过 5000 字符的 HTML 片段时,单次调用可能耗时 200ms+。JavaScript 单线程下,这段执行会完全阻塞 DOM 更新、事件响应和光标闪烁——用户输入后光标“冻住”,撤销按钮点击无反馈,就是这个原因。
Web Worker 中必须避开的三个坑
直接把 diff-match-patch 或 jsdiff 拷进 Worker 就跑,大概率失败。常见错误包括:
-
document、window、localStorage等全局对象在 Worker 里不可用,任何依赖它们的代码(比如某些 patch 渲染逻辑)会报ReferenceError - HTML 字符串含实体(
&、)或换行不一致时,<code>diff_main可能产出错位 diff,导致高亮错乱——Worker 里没 DOM parser 帮你 normalize,得主线程预处理 - 传给 Worker 的文本若含函数、Date、RegExp 等非可序列化值,
postMessage会静默丢弃字段,看起来“没返回结果”
主线程和 Worker 的分工边界怎么划
Worker 只做三件事:接收干净字符串、调用 diff_main + diff_cleanupSemantic、返回 diffs 数组。其余全由主线程负责:
- 主线程清理 HTML:用
DOMParser解析再textContent提纯,或正则剔除 script/style 标签(避免 diff 被干扰) - 主线程控制粒度:对超长 HTML,按
<p></p>、<div> 分块,每块单独发给 Worker 并行计算,最后合并结果 <li>主线程做渲染:拿到 <code>diffs后,用diff_prettyHtml或自定义高亮逻辑生成 DOM,不能让 Worker 碰innerHTML - Worker 通信只传 Plain Object:
{ text1: string, text2: string, id: number },返回{ id: number, diffs: [...], time: number } - 复用一个长期存活的 Worker 实例,用
id字段标记请求/响应配对 - 如果 diff 结果数组很大(比如 10 万字符对比产出上万 diff),用
postMessage(data, [arrayBuffer])传递ArrayBuffer,避免结构化克隆拷贝内存 - Worker 内设超时:
dmp.Diff_Timeout = 0.8,防止某次异常长文本拖垮整个队列 - 主线程加节流:连续编辑时,只对最后两次快照做 diff,中间变更丢弃——用户并不需要每敲一个字都看到差异
性能关键:transferable 和复用 Worker
每次 new Worker() 开销不小,尤其在频繁触发 diff(如实时预览)时。正确做法是:
真正难的不是把 diff 搬进 Worker,而是让主线程和 Worker 在数据清洗、分块策略、错误降级(比如超时后 fallback 到行级 diff)上严丝合缝。漏掉任意一环,都会在 10 万字符量级上暴露成卡顿或错位。











