使用方法
- 把要处理的 JavaScript 粘进左边「输入 JavaScript」框。页面已经预置了 9 行演示代码(
function total (list)那一段,186 个字符),打开即可直接跑。 - 确认两个选项:默认勾选「保留
/*! */版权注释」和「必要时保留换行」。前者保护许可证声明,后者防止删掉换行后触发自动分号插入(ASI)改变语义。 - 点「压缩」,或在输入框里按 Ctrl + Enter(macOS 用 Cmd + Enter),右侧立刻给出结果。
- 看结果下方的统计条:压缩后字符数、压缩率、原始字符数、压缩前后 UTF-8 字节数、删掉与补回的字符数。演示代码从 186 字符压到 132 字符,压缩率 29.03%。
- 确认无误后点「复制结果」或「下载 .js」。下载的是 UTF-8 无 BOM 文件,可以直接被打包工具读取。
这个 JavaScript压缩 工具能解决什么问题
四类场景用得上:一是把临时写的脚本、书签小工具、邮件模板里嵌的内联脚本压成几行,减少粘贴时的体积和噪声;二是想快速知道「去掉注释和缩进之后这段代码到底有多重」,用来判断要不要上构建流程;三是复制别人的示例代码时先去掉大段教学注释,只留可执行部分;四是在没法用 Node、也没装构建工具的环境里(比如只有浏览器的公用电脑)做一次轻量瘦身。
它明确不做这些事:不把 calculateTotalPrice 改写成 a,不分析作用域,不做死代码消除,不合并常量,不转换语法。这些是 Terser、UglifyJS、esbuild 这类基于抽象语法树(AST)的压缩器干的活,需要完整的解析器和几百 KB 的代码体积;本页是纯前端即时工具,做不到也不打算做。如果你的目标是上线构建产物的体积,请直接使用构建工具链,本页只当快速估算和临时瘦身用。另外,CSS 和 HTML 各有各的压缩规则(比如 CSS 要处理 @media 内的空格、HTML 要保护 pre 里的空白),本页只实现 JavaScript 词法,用底部列出的专门工具更合适。
压缩原理与规则说明
本工具不解析成语法树,而是一个逐字符状态机,一次线性扫描得到结果,时间复杂度 O(n)。它维护 5 个状态:普通代码、单引号字符串、双引号字符串、模板字符串、以及跳过中的注释。字符串和模板字符串里的内容整段原样拷贝,包括其中的空格、换行和转义序列——所以 "a b c" 里的连续空格一个都不会少。
为什么不用一条正则全局替换?最直觉的写法是 src.replace(/\/\/[^\n]*/g, '') 删行注释,但它会把字符串里的双斜杠一起吃掉:var u = "https://a.com"; 会被截成 var u = "https:,直接变成语法错误。注释只存在于「代码」状态,不在字符串里,这个区别无法用一条正则表达,只能逐字符跟踪状态。
正则字面量 vs 除号是本算法唯一需要"猜"的地方,因为 / 在 ECMAScript(ECMA-262)里既是除号也是正则字面量的开头。判据沿用 JSMin 的思路,看前一个有效字符 last:如果 last 为空(代码开头),或者属于 ( ) , = : [ ! & | ? { } ; + - * % ~ ^ < > 这一组运算符与标点,或者前一个词是 return typeof instanceof in of new delete void do else yield await case 之一,那么 / 一定是正则开头,进入「扫描到未转义的 / 为止,且要跳过 [...] 字符类内部的 /」;否则当作除号原样输出。这就是为什么 var x = a / b / c; 能正确压成 var x=a/b/c;,而 var re = /a\/b/g; 里的 \/ 不会被误当成正则结尾。
删除换行是最危险的一步,因为 ECMAScript 的自动分号插入(ASI)只在源码里真的有行终止符时才生效。把换行删掉,ASI 就不再触发,语义可能整个变掉。本工具用三条保守规则,宁可少删一个字节也不改语义:
- 下一行第一个有效字符是
([反引号+-/时保留换行——var a = b换行(function () { })()一旦删掉换行,就变成了拿b当函数调用,语义完全不同。 - 上一行末尾是
returnthrowbreakcontinueyield这类受限产生式,或者是++--的后半个+-时保留换行——return换行1表示返回 undefined,压成return 1就变成返回 1。 - 上一行最后一个有效字符不是
{ ; , : ( [ = ? * % < > ! ~ & | ^之一时保留换行。这一组字符保证上一行不会是一个可以结束的语句,删掉换行不会粘连;除此之外(例如上一行以})]或标识符结尾)一律保留换行。
行内的多余空白照常删除,只在三种情况下补回一个空格:两侧都是词字符(var sum 不能变成 varsum)、两侧都是 + 或 -(a + +b 不能变成 a++b)、数字后面紧跟 .(1 .toString() 不能变成 1.toString())。以 /*! 或 /* @license 开头的块注释整段保留,这是开源许可证声明的通行约定。
完整示例(数值仅作演示)
演示代码共 9 行、186 个字符、210 个 UTF-8 字节(字节数比字符数多 24,是因为注释里的中文按 UTF-8 各占 3 字节)。压缩后为 2 行、132 个字符、132 字节:
- 净减少 54 个字符,字符压缩率
1 − 132 ÷ 186 = 29.03%; - 按字节算压缩率
1 − 132 ÷ 210 = 37.14%,比按字符算更高,因为被删掉的注释里全是中文; - 删掉的空白字符 45 个、行注释字符 15 个,合计 60;又补回 6 个必要分隔符,
186 − 60 + 6 = 132,与输出长度逐位对得上; - 输出里保留了 1 处换行,位置在 for 循环的
}之后——它属于上面第三条规则(上一行以}结尾),删掉会粘连; - 正则字面量
/price\s*[:=]/g被原样保留,里面的\s和两个/都没有被破坏。
这段代码的变量名 total、list、sum、re 在输出里一个都没变。这就是本工具与 AST 级压缩的分界线:只删「不影响词法的空白和注释」,不动「标识符」。
输入范围与对照表
工具区那张 16 行对照表是本工具对自己跑一遍的真实结果,默认显示 12 行,点「加载更多」展开全部,也可以导出 CSV(带 BOM)或复制成 TSV。下面这张表按「会被怎么处理」归类,方便对照规则:
| 写法 | 会被怎么处理 | 规则依据 |
|---|---|---|
// 行注释 / /* 块注释 */ | 整段删除 | 只存在于代码状态,17→8 与 20→8 字符 |
/*! 版权 2026 */ | 整段保留 | 许可证声明,压缩率仅 8.0% |
var u = "https://a.com"; | 字符串里的 // 不动 | 处于字符串状态,24→22 字符 |
var re = /a\/b/g; | 正则里的 \/ 不动 | 字符类与转义被跳过,17→15 字符 |
var x = a / b / c; | 按除号处理 | 前一个字符是标识符,18→12 字符 |
var t = `a` 换行 `b`; | 模板内换行保留 | 换行是字符串内容,有语义 |
var a = b 换行 (function(){})() | 换行保留 | 下一行以 ( 开头,ASI 危险 |
return 换行 1; | 换行保留 | 受限产生式,删了会改变返回值 |
var big = 9007199254740993; | 数字一个字符不动 | 不改写字面量,27→25 字符 |
var n = 1.50; | 数字一个字符不动 | 不改写数值表示,13→11 字符 |
label: for (;;) { break label; } | 换行删掉,标签保留 | 上一行以 { 结尾,34→27 字符 |
var s = "a b c"; | 字符串内空白不动 | 字符串是数据,19→17 字符 |
输入体积阈值:小于 204800 字符(200 KB)即时处理;超过 200 KB 会在结果区提示体积后照常处理;超过 2097152 字符(2 MB)直接拒绝并说明原因——字符级扫描虽然是 O(n),但一次性处理几 MB 文本会让标签页明显卡顿,这是保护而不是限制。
输入范围、边界与精度说明
- 未闭合的结构一律报错,不静默输出。字符串缺配对引号、模板字符串缺结束反引号、块注释缺
*/、正则缺结束/,四种情况都会给出「第几行第几列」的具体位置。行列号按\n计数,行号与列号都从 1 开始,指向的是原始输入而不是压缩后结果。 - 正则误判时的兜底:如果代码里写的是除法,但前一个字符恰好落在运算符集合里(例如
return a / b,return会让/被判成正则开头),正则扫描会在遇到换行或文件末尾时停下并报错。这不是故障,是除号与正则在词法上本来就歧义;写成return (a / b)即可消除歧义。 - 不做任何语义改写:不合并
var、不把if (a) { b() }简化成a && b()、不删debugger、不碰eval与with(它们会让作用域分析失效,而本工具根本不做作用域分析,所以反而没有这个风险)。 - 不处理 TypeScript、JSX、CoffeeScript 的语法:状态机只实现了 ECMAScript 的字符串、模板字符串、正则、注释四类词法结构。类型注解、JSX 标签、缩进语法都不在其中,扫出来大概率报错。请先用对应编译器转成标准 JavaScript 再粘贴。
- 字节数按 UTF-8 计算且正确处理代理对:U+FFFF 以上的字符(表情符号等)按 4 字节计,中文按 3 字节计。用
String.length当字节数会把中文少算两倍,这也是演示代码 186 字符对应 210 字节的原因。 - 超过 9007199254740991(2^53 − 1)的整数字面量在 JavaScript 里本就无法精确表示,本工具原样输出,不改写也不提示——那是源码自身的问题。
- 输出不含 BOM:下载的
.js是 UTF-8 无 BOM,避免某些打包工具把开头的 BOM 当成一个奇怪字符。
常见问题
为什么变量名没有被缩短?这不是压缩失败了吗?
不是失败,是本工具的设计边界。缩短标识符必须先把代码解析成抽象语法树,再分析每个变量的作用域,确认改名不会撞上外层同名变量或 with / eval 的动态作用域。这是 Terser(-m 参数)、UglifyJS、esbuild 做的事。本工具只做词法层面的空白与注释删除,不碰标识符,所以输出一定只是输入程序的"另一种写法"。
压缩率只有 29%,是不是太低了?
29.03% 是只删注释和空白的正常水平。真实项目里 AST 级压缩能到 60% 以上,主要来自变量名缩短、死代码消除和常量折叠这三块,而这三块本工具都不做。想知道这段代码的上限体积,可以在构建工具里跑一次 Terser 做对比;本页的价值在于即时、无需安装,且输出保留原始标识符,便于对照阅读。
为什么会保留一个换行,感觉没压干净?
那个换行是故意留下的。按 ECMAScript 的自动分号插入规则,删掉某些换行会让两行粘连成完全不同的表达式——比如 var a = b 后面紧跟 (function(){})(),删掉换行就变成把 b 当函数调用。本工具宁可多留 1 个字节也不改语义,统计条里的"保留换行处数"就是这个数量。取消勾选「必要时保留换行」可以强制删除,但请自己确认语义。
我的代码里有正则,会不会被压坏?
正则字面量会被原样保留,包括其中的空格、\s 和转义的 \/。真正的风险在"除号被误判成正则开头":判据是前一个有效字符,如果前一个字符恰好是运算符或 return 这类关键字,本工具会按正则去扫描,扫到换行或文件末尾还找不到结束的 / 就报错。这时给除法加上括号即可。
模板字符串里的缩进为什么没删?
因为模板字符串的内容是数据不是代码。反引号之间的每一个换行和空格都是字符串的一部分,删掉就等于改了运行时输出的字符串值。AST 级压缩器同样不会动模板字符串里的空白(除非启用专门的常量折叠选项),所以这不是本工具的疏漏。
报错里的「第 X 行第 Y 列」是压缩前还是压缩后的位置?
压缩前,也就是你原始输入里的位置。行号按换行符计数从 1 开始,列号是该行内的第几个字符,也从 1 开始。因为压缩会删掉大量字符,输出里的位置对不上原始代码,所以一律报原始位置。
输入只有注释时会发生什么?
压缩结果会是空字符串,不会报错。结果区会提示"压缩后没有剩余内容——输入可能只有注释",提醒你检查是不是整段都被删掉了。这种情况常见于只粘了一整段函数说明文档的片段。
代码会被传到服务器吗?
不会。整个状态机就是一个 while 循环加几个 if,没有任何网络请求,也没有引入第三方库。你可以打开浏览器开发者工具的 Network 面板确认:页面加载完之后不会再发出任何请求。