当 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 Worker | 3.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 压力、纯文本模式应对超大文件。合理组合这些策略,可以在保持功能完整的同时提供流畅的用户体验。