雜記 › 技術筆記 › 取消勾選「保留註解」後,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 16 個鍵進、4 個欄出:JSON 轉表格的表頭聯集、點號展平與 CSV 引號規則 2026-10-30