主线程直接运行高亮逻辑会导致卡顿,需用web worker异步处理;但highlight.js等库在worker中报错(如referenceerror: document is not defined)是因为其初始化依赖document、window等dom对象,而worker环境仅提供self、postmessage等有限api,无dom访问能力。

主线程直接跑高亮逻辑,超 300 行 HTML 就会卡顿;用 Web Worker 拆出去,光标不冻、输入不丢、滚动流畅——但必须避开全局对象、非序列化数据、DOM 依赖这三类典型错误。
为什么 highlight.js 或 Prism 在 Worker 里直接 import 就报错
常见现象是 ReferenceError: document is not defined 或 self.hljs is undefined。原因很直接:Worker 环境没有 document、window、localStorage,而很多高亮库(尤其老版本 highlight.js)在初始化时会偷偷读取 document.currentScript 或检测 self.location 来自动加载语言包。
- 用
importScripts('highlight.min.js')替代import,避免 ES 模块解析器尝试访问 DOM - 禁用 highlight.js 的自动初始化:
self.hljs.configure({ ignoreUnescapedHTML: true, throwUnescapedHTML: false }) - 不要传
HTMLElement或带innerHTML的对象进 Worker,只传纯字符串code和language字段
怎么让 CodeMirror 5 / Lexical / 自研编辑器真正用上 Worker 高亮
关键不在“能不能调用”,而在“谁负责切片、谁负责缓存、谁负责丢弃过期结果”。Worker 本身无状态,编辑器却持续变化。
- 主线程按可视区域切块:用
getViewport()获取当前显示行号,只把startLine - 5到endLine + 5的代码发给 Worker - 每次请求带
requestId和version(比如编辑器doc.changeGeneration),Worker 返回时原样带上,主线程收到后比对当前版本,不一致就丢弃 - 用
AbortController不现实(Worker 不支持),改用布尔标志:let shouldAbort = false,主线程设为true,Worker 内部在循环 tokenization 前检查该 flag - 缓存键用
hash(code.substring(0, 200)) + language,避免整段缓存吃内存;命中则跳过 Worker 调用,直接返回 token 数组
Worker 通信传什么、怎么传才不拖慢性能
传整个 HTML 字符串 没问题,但传 token 数组 或 diff 结果 过大时,结构化克隆会拷贝内存,实测 10MB token 数据传输耗时 >120ms。
- 只传必要字段:
{ id: number, code: string, language: string },别塞editorRef、theme这类无关信息 - 返回结果若超过 500 个 token,改用
postMessage(data, [buffer])传递ArrayBuffer,主线程用Uint8Array解析 - 设置
Transferable前先structuredClone测试是否含Date、RegExp、function—— 这些会被静默丢弃,导致返回空对象 - Worker 内设超时:
self.hljs.highlightAuto(code, { language: lang }).value加 try/catch +setTimeout保护,单次高亮超过 800ms 强制返回截断结果
最容易被忽略的是:Worker 不能“看到”编辑器当前光标位置或选区,所有位置映射(比如高亮某行后要滚动到那里)必须由主线程做完计算再下发指令;另外,CSS 主题色、字体大小这些渲染参数,永远别传进 Worker——它只管分词,不管颜色怎么画。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











