一行 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 字元數,跟位元組無關。
圖 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 沒有專門分支,標籤與函數體同級。
圖 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 美化與壓縮的舊文 講過,這裡只量這一頁自己的實作。