杂记 › 技术笔记 › 取消勾选「保留注释」后,format-js 把 24 字符 URL 压成 13 字符残缺码

取消勾选「保留注释」后,format-js 把 24 字符 URL 压成 13 字符残缺码

取消勾选保留注释后,这一页的压缩先删注释再保护字符串,24字符URL只剩13字符并判语法错误;默认勾选时字符串完整,行注释却把下一句吃进去,美化两次也不幂等。

一行 URL 进去,13 字符出来

var a = "https://x.com"; 这行 24 字符的代码切到压缩,输出只剩 var a="https:,13 字符,new Function 判 SyntaxError: Invalid or unexpected token。

顺序是倒的。第 249 行 if (!keepComment) 决定要不要删注释,删的动作在第 250–251 行:先去成对的块注释,再把 // 到行尾整段抹掉;把字符串挑出来换成占位符的保护却排在第 258 行。保护还没生效,串里的双斜杠已被认成行注释起点,后半截连闭引号和分号一起删走。

这条破坏只在取消勾选「保留注释」时发生。页面默认勾选,走第 252–254 行那个空 else,同一条输入只压到 22 字符,闭引号还在、语法合法。复选框在压缩模式下没被禁用:第 67 行只把缩进那行的 opacity 设成 0.5,看着像灰了,实际仍决定走哪条分支。在 JS美化压缩 上做对照,勾选状态得算第二个变量。

三种改写,各自的字符账

输入状态输出判定
var a = 1; // note 换行 var b = 2;(29 字符 / 2 行)勾选,263 行收空白var a=1;//note var b=2;(23 字符 / 1 行)合法,第二条语句进注释
var s = "/*not a comment*/";(28 字符)取消勾选,250 行var s="";(9 字符)合法,串被清空
var z = a - -b;(15 字符)265 行吸附var z=a--b;(11 字符)Unexpected identifier 'b'

只有一行报错,其余两行判合法,改的是语义。

统计条把丢字符记成压缩率

3 条 URL 的数组 70 字符 / 2 行,取消勾选后剩 17 字符,统计条写绿色 -75.7%——省下来的正是第 2、3 个元素整条消失。统计条由 updateStat(第 300 行起)写,第 292 行传的是 input.length 与 result.length,即 UTF-16 字符数,跟字节无关。

一条 URL 两种勾选下的输出输入24勾选22 合法取消13 残缺判语法错误

图 1:同一条 24 字符输入在两种勾选状态下的输出长度。

两套口径的分差是实测的:带中文注释的样例页面报 57.9%、按 UTF-8 字节算是 71.4%;本页脚本当输入,字符口径 28.8%、字节口径 37.6%。一样长时显示 +0%,只有一行注释又取消保留时显示 -100.0%、输出框是空的。

265 行的吸附,谁被并谁不动

第 265 行的字符类是单字符集合,里面有 + - * / % = & | ! ? < >,唯独没有 .、^、~。一元负号被并进前一个减号,成后缀自减。类外的符号两侧空格留着,var x = ~ y; var z = a ^ b; 27 字符只到 22 字符;点号同理,var x = a . b; 14 字符到 12 字符。它也不是逢符号就坏,var f = (a, b) => a + b; 24 字符压成 17 字符合法——一次只吃一个字符,箭头拆不散。

占位符回收不认来源

占位符在第 258 行的字符串保护里生成、第 267 行用 ___STR_(\d+)___ 回收,回调却不检查这串是不是原文自带。整段没有字符串时数组为空,替换写回字面量,var p = ___STR_0___; 20 字符变成 16 字符的 var p=undefined;;前面恰好有一条字符串时,var s = "abc"; var p = ___STR_0___; 35 字符压成 24 字符,两条串说了话。

词法阶段就没有正则字面量

美化先走 tokenize(第 161 行起),里面没有正则字面量这一支。var re = /a+/g; 切成 8 个记号,斜杠落在运算符字符集里,第 225 行贪婪地把 + 与 / 并成一个运算符,26 字符的整行美化成 30 字符、输出形如 var re = / a +/ g;,正则报废。

换行与缩进是从 79 行来的

beautifyJS(第 79 行起)的换行只认几类记号:{ 与 [ 让层级加一,} 与 ] 让层级减一,分号与逗号各自补换行;圆括号既不增也不减,f(a,b,c); 9 字符拆成 3 行、缩进仍是 0。第 143 行的逗号判断不排除 ),for(var i=0;i<10;i++){sum+=i;} 30 字符摊成 43 字符 / 5 行,for 头分 3 行。闭方括号那条 else(第 108–110 行)是空的,var a=[1,2,3]; 14 字符到 25 字符 / 5 行,三个 ] 顶格而闭 } 有缩进。case 没有专门分支,标签与函数体同级。

默认样例连续美化两次的字符数原始112一次151 字符11 行二次134 字符6 行第二次反而短

图 2:默认样例连续美化两次的字符数与行数。

美化不幂等,压缩才幂等

10 组样本里 7 组美化两次结果不同。默认样例 112 字符 → 151 字符 / 11 行 → 134 字符 / 6 行,第二次反倒吃掉 17 字符、砍掉 5 行。本页自己的脚本段更能说明问题:8,629 字符 / 287 行过一遍得 8,540 字符 / 277 行并判 SyntaxError: Invalid regular expression: missing /,再过一遍变 8,819 字符 / 335 行。压缩侧相反,3 组样本二次相同(35 → 13 → 13);两步串起来,beautify(minify(x)) == x 成立 0 组。

平方级缩进与那个打不到的 catch

缩进按层级复制空格,一深就是平方:200 层花括号 402 字符 → 80,802 字符、3,294 ms;500 层 1,002 字符 → 502,002 字符、120,055 ms,最长一行前面 1,000 个空格,而输入框没有长度上限,页面上的表现就是标签页像卡住了。压缩也不读缩进控件,42 字符的样本在 2 / 4 / Tab 三档下输出同为 42 字符。报错出口是空的:第 293–295 行的 catch 与「格式化出错:」文案,在 72 组畸形输入里触发 0 次——唯一的自然抛点是第 75 行 unit.repeat(level),被第 105 行 Math.max(0, indent - 1) 兜在 0 以上。分支代码确实在,只是这 72 组构造不出触发它的那一串。

常见问答

Q:勾掉「保留注释」只是删注释吗?

不是。删注释排在字符串保护之前,串内部的 // 与 /* 同样被当注释起点,24 字符才会只剩 13 字符。

Q:绿色的负百分比等于省了多少体积?

不等于。统计按字符记账,17 字符那次省下的全是消失的元素;页面 57.9% 对字节 71.4%,本来就差一截。

Q:压缩坏了再美化能补回来吗?

补不回来,3 组交叉样本成立 0 组,URL 组取消勾选后 35 → 13 → 15 字符仍是残缺码。至于压缩后的代码怎么读、各留哪个版本,另有一篇 JS 美化与压缩的旧文 讲过,这里只量这一页自己的实现。

📎 相关文章

16 组开关实测 HTML 去标签:报 19 个标签,实际删掉 148 个字符 2026-10-30 SVG 转 PNG 后外链图丢了一半:同一份 594 B 文件预览有 3 张图、成品只剩 1 张 2026-10-30 二进制转 Base64 这一页:前缀只有 5 档,换行能解、连字符和下划线直接报错 2026-10-30 给应用做商店介绍图,点导出拿到的却是 1242×2688:8 档画布预设的像素账、噪点 24.83 倍体积与默认被裁掉 134.93 px 的手机框 2026-10-30 JSON 转表格的表头到底从哪来:键并集、点号展平与 CSV 引号的真实规则 2026-10-30