大文件 JS 代码格式化性能优化

工具相关 ·

当 JS 文件超过 100KB 或几千行时,格式化操作可能变得缓慢,甚至导致浏览器卡死。本文将介绍如何优化大文件的格式化性能。

性能瓶颈分析

1. 主线程阻塞

// 同步格式化,大文件会阻塞主线程
function formatLargeFile(code) {
    // 100KB+ 的代码,格式化可能需要 2-5 秒
    var result = formatJs(code, 4, 'collapse');
    // 期间页面完全无响应
    return result;
}

2. 内存占用

格式化过程中需要:

  • 原始代码字符串
  • 解析后的 AST(抽象语法树)
  • 格式化后的字符串
  • 语法高亮的 HTML

对于 1MB 的文件,内存峰值可能超过 50MB。

3. DOM 渲染压力

格式化结果插入 DOM 时:

  • 大量 HTML 节点的创建
  • 语法高亮的 DOM 操作
  • 浏览器的重排和重绘

优化方案

方案一:Web Worker

将格式化计算移到 Worker 线程:

// 主线程
const worker = new Worker('format-worker.js');

worker.postMessage({
    action: 'format',
    code: largeCode,
    indent: 4
});

worker.onmessage = function(e) {
    // 格式化完成,更新 UI
    renderOutput(e.data.result);
};

// Worker 线程 (format-worker.js)
importScripts('js-format.js');

self.onmessage = function(e) {
    var result = formatJs(e.data.code, e.data.indent, e.data.style);
    self.postMessage({ result: result });
};

优势:

  • 不阻塞主线程
  • 页面保持响应
  • 用户体验流畅

方案二:分块处理

function formatInChunks(code, chunkSize = 5000) {
    const lines = code.split('\n');
    const chunks = [];
    
    for (let i = 0; i < lines.length; i += chunkSize) {
        chunks.push(lines.slice(i, i + chunkSize).join('\n'));
    }
    
    return chunks;
}

// 逐块格式化,每块之间释放主线程
async function formatAsync(code) {
    const chunks = formatInChunks(code);
    const results = [];
    
    for (const chunk of chunks) {
        const result = formatJs(chunk, 4, 'collapse');
        results.push(result);
        // 释放主线程
        await new Promise(resolve => setTimeout(resolve, 0));
    }
    
    return results.join('\n');
}

方案三:虚拟渲染

对于输出区域,不渲染所有行:

class VirtualOutput {
    constructor(container, lineHeight = 18) {
        this.container = container;
        this.lineHeight = lineHeight;
        this.lines = [];
        this.visibleRange = { start: 0, end: 50 };
    }
    
    setLines(lines) {
        this.lines = lines;
        this.container.style.height = lines.length * this.lineHeight + 'px';
        this.render();
    }
    
    render() {
        const { start, end } = this.visibleRange;
        const visibleLines = this.lines.slice(start, end);
        this.container.innerHTML = visibleLines.join('\n');
        this.container.style.transform = `translateY(${start * this.lineHeight}px)`;
    }
    
    onScroll(scrollTop) {
        const newStart = Math.floor(scrollTop / this.lineHeight);
        const newEnd = newStart + 50;
        
        if (newStart !== this.visibleRange.start) {
            this.visibleRange = { start: newStart, end: newEnd };
            this.render();
        }
    }
}

方案四:纯文本模式

对于超大文件,切换到纯文本模式:

function renderOutput(text) {
    const LARGE_THRESHOLD = 80 * 1024;  // 80KB
    const LARGE_LINES = 2500;
    const lineCount = (text.match(/\n/g) || []).length + 1;
    
    if (text.length > LARGE_THRESHOLD || lineCount > LARGE_LINES) {
        // 使用 textarea 代替语法高亮
        const textarea = document.createElement('textarea');
        textarea.value = text;
        textarea.readOnly = true;
        outputContainer.innerHTML = '';
        outputContainer.appendChild(textarea);
    } else {
        // 正常语法高亮
        outputArea.innerHTML = highlightJs(text);
    }
}

性能对比

测试环境

  • 文件大小:500KB,15000 行
  • 浏览器:Chrome 120
  • 设备:中端笔记本

测试结果

方案格式化时间页面卡死内存峰值用户体验
同步处理3.2s是85MB差
Web Worker3.5s否90MB好
分块处理4.1s否75MB中
纯文本模式0.8s否25MB好
虚拟渲染3.2s + 16ms/帧否45MB好

实际实现

本工具的优化策略

// 统一入口:防重复 + Worker 优先 + 同步降级
function runAction(label, msg, syncFn) {
    if (processing) return;  // 防堆叠
    setProcessing(true);
    
    // 显示加载提示
    outputArea.innerHTML = '<div class="proc-indicator">处理中...</div>';
    
    if (workerOk && worker) {
        // Worker 模式
        worker.postMessage(msg);
    } else {
        // 同步降级,延迟执行避免阻塞 UI 更新
        setTimeout(function() {
            try {
                renderOutput(syncFn());
            } catch (e) {
                outputArea.innerHTML = '<div class="proc-indicator proc-err">出错:' + e.message + '</div>';
            } finally {
                setProcessing(false);
            }
        }, 10);
    }
}

大文件检测

function isLargeFile(text) {
    const LARGE_THRESHOLD = 80 * 1024;
    const LARGE_LINES = 2500;
    const lineCount = (text.match(/\n/g) || []).length + 1;
    return text.length > LARGE_THRESHOLD || lineCount > LARGE_LINES;
}

渐进式渲染

async function formatAndRender(code) {
    // 先显示前 100 行
    const lines = code.split('\n');
    const preview = lines.slice(0, 100).join('\n');
    renderOutput(preview + '\n\n... 处理中 ...');
    
    // 异步格式化完整代码
    await new Promise(resolve => setTimeout(resolve, 50));
    const fullResult = formatJs(code, 4, 'collapse');
    
    // 渲染完整结果
    renderOutput(fullResult);
}

最佳实践

1. 文件分割

// 不推荐:单文件 500KB
import { allFunctions } from './huge-file.js';

// 推荐:按功能分割
import { authFunctions } from './auth.js';
import { dataFunctions } from './data.js';
import { uiFunctions } from './ui.js';

2. 懒加载

// 按需加载重型功能
button.addEventListener('click', async () => {
    const { heavyProcessor } = await import('./heavy-module.js');
    heavyProcessor.run();
});

3. 缓存结果

const formatCache = new Map();

function cachedFormat(code, indent, style) {
    const key = `${code.length}-${indent}-${style}`;
    if (formatCache.has(key)) {
        return formatCache.get(key);
    }
    const result = formatJs(code, indent, style);
    formatCache.set(key, result);
    return result;
}

总结

大文件 JS 格式化的核心优化策略是:Web Worker 避免阻塞、虚拟渲染减少 DOM 压力、纯文本模式应对超大文件。合理组合这些策略,可以在保持功能完整的同时提供流畅的用户体验。

阅读 14