一、正则匹配方案:快速上手

最简单的语法高亮基于正则表达式,按「关键字 → 字符串 → 注释 → 数字 → 函数名」的顺序逐层替换。

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;  /* 状态收敛,后续行无需重算 */
    }
}

五、总结

正则方案适合原型与简单场景;状态机方案是生产级编辑器的标配。大文件优化的核心在于「只算必要的部分」——通过行级状态快照实现增量解析,配合虚拟滚动将渲染量控制在常数级别。这些思路不仅适用于语法高亮,也为代码折叠、缩进提示等功能提供了基础数据结构。