一、正则匹配方案:快速上手
最简单的语法高亮基于正则表达式,按「关键字 → 字符串 → 注释 → 数字 → 函数名」的顺序逐层替换。
highlight_regex.js(伪代码)
/* 按优先级顺序匹配,避免字符串内的关键字被着色 */ function highlight(code) { return code .replace(/"[^"]*"/g, '<span class="str">$&</span>') .replace(/\/\/.*$/gm, '<span class="cm">$&</span>') .replace(/\b(int|char|return|if|for)\b/g, '<span class="kw">$&</span>') .replace(/\b\d+\b/g, '<span class="num">$&</span>'); }
局限: 正则无法处理嵌套结构(如字符串内包含转义引号、多行注释),且逐层 replace 的时间复杂度为 O(n×k),k 为规则数。
二、状态机方案:精确且可扩展
用有限状态机(FSM)逐字符扫描,根据当前状态决定如何着色。能正确处理嵌套与边界情况。
fsm_highlight.js(核心逻辑)
const State = { NORMAL: 0, STRING: 1, COMMENT: 2, NUMBER: 3 }; function highlightFSM(code) { let out = "", state = State.NORMAL, buf = ""; const keywords = new Set(["int", "return", "if", "for"]); function flush(cls) { if (!buf) return; if (cls) out += `<span class="${cls}">${buf}</span>`; else out += buf; buf = ""; } for (let i = 0; i < code.length; i++) { const ch = code[i], next = code[i + 1]; if (state === State.NORMAL) { if (ch === '"') { flush(); state = State.STRING; } else if (ch === '/' && next === '/') { flush(); buf = code.slice(i); flush("cm"); break; } else if (/\d/.test(ch)) { flush(); state = State.NUMBER; } } else if (state === State.STRING) { if (ch === '"' && code[i - 1] !== '\\') { buf += ch; flush("str"); state = State.NORMAL; continue; } } else if (state === State.NUMBER) { if (!/\d/.test(ch)) { flush("num"); state = State.NORMAL; } } buf += ch; } flush(state === State.NUMBER ? "num" : null); return out; }
三、大文件性能优化
当文件超过 5000 行时,全量高亮会导致明显卡顿。常用策略有:
- 可视区域渲染(Virtual Rendering): 只高亮当前视口上下各 2~3 屏的内容,滚动时动态计算。
- Web Worker 离屏计算: 将高亮逻辑放入 Worker,避免阻塞主线程的输入响应。
- 增量 Token 缓存: 记录每行的解析状态(如是否在多行注释中),修改某行时只重新解析受影响的后续行,直到状态恢复一致。
- 二分法定位边界: 利用缓存的状态快照,快速定位需要重新解析的起始位置,而非从文件头开始。
四、增量渲染的关键:行状态快照
每次解析完一行,记录其「结束状态」(如是否在块注释内、是否在字符串内)。编辑第 N 行时,从第 N 行开始重新解析,直到某行的结束状态与快照一致,即可停止。
incremental.js(示意)
/* 行快照:记录每行结束时的解析状态 */ const snapshots = []; /* 与行号一一对应 */ function rehighlightFrom(lineNo) { let state = lineNo > 0 ? snapshots[lineNo - 1] : State.NORMAL; for (let i = lineNo; i < lines.length; i++) { const old = snapshots[i]; highlightLine(lines[i], state); snapshots[i] = state; if (state === old) break; /* 状态收敛,后续行无需重算 */ } }
五、总结
正则方案适合原型与简单场景;状态机方案是生产级编辑器的标配。大文件优化的核心在于「只算必要的部分」——通过行级状态快照实现增量解析,配合虚拟滚动将渲染量控制在常数级别。这些思路不仅适用于语法高亮,也为代码折叠、缩进提示等功能提供了基础数据结构。