雜記 › 技術筆記 › 介面上八個框的先後和程式碼執行順序是反的:文字清理頁硬編碼八步順序、逐對換序真值表與統計碼元口徑實測

介面上八個框的先後和程式碼執行順序是反的:文字清理頁硬編碼八步順序、逐對換序真值表與統計碼元口徑實測

本頁八個核取方塊的介面先後與程式碼執行順序幾乎完全反序,勾選次序不影響結果;再逐字實測換序真值表、去空行與合併空行的互斥抵消、統計碼元口徑與 256 種組合的結果分布。

前陣子從 Excel 裡複製幾行資料,貼出來的文字把半形空格、全形空格、Tab 與空行混在一處。我打開工具箱裡的文字清理頁,頂端一排八個核取方塊,勾一個刷新一次結果。麻煩出在我想要一個特定次序:先去空格,再把全形空格轉成半形。

介面上八個框的先後不是執行順序

本篇的主角是文字清理頁,檔案為 application/index/view/tools/text-trim.html,模板共 144 行 / 5885 位元組:八枚 opt-* 核取方塊寫在 L12–L33,process() 的清理邏輯在 L92–L130,正好八步。介面由上而下與程式碼裡的先後,兩張順序表對照如下。我把 process() 逐字摘進純 Node 探針 tmp/tt_probe.js——不改寫一個字、不修任何 bug、用 ids 物件餵勾選態——跑出來的真實執行順序是:fullwidth → html → tab → all-space → trim-line → empty-line → merge-empty → no-newline。

介面(markup)順序程式碼執行順序
trim-linefullwidth
all-spacehtml
empty-line(預設勾選)tab
merge-emptyall-space
no-newlinetrim-line
html-tagempty-line
tabmerge-empty
fullwidth-spaceno-newline

兩張表幾乎完全反序:介面上排在最後的 fullwidth-space 永遠第一個跑,no-newline 永遠最後跑。換句話說,你在頁面上按什麼順序勾選,對結果不產生任何影響,執行順序是寫死在程式碼裡的。

想換序也換不了

我做了一張逐對換序真值表:兩枚同時啟用,再在探針裡手動交換兩步的先後,各跑一遍:

  • fullwidth ↔ all-space:輸入 a\u3000b\u3000c,依頁面順序輸出 abc(3 個碼元);換序後輸出 a b c(5 個碼元),碼元差異 = 4,不可交換。all-space 恆排在 fullwidth 之後,所以「先去空格再把全形轉半形」在本頁沒有操作路徑。
  • html-tag ↔ trim-line:輸入 <b>hello </b>,頁面順序得 hello(5 個碼元),換序得 hello (6 個碼元),碼元差異 = 1,同樣不可交換。
  • 可交換的三對:trim-line ↔ empty-line、merge-empty ↔ empty-line、tab ↔ all-space,碼元差異都是 0。

至於給每行加序號、加前綴這類需求,那是另一個頁面幹的事,我用文字行操作那頁完成。

兩個框勾在一起等於沒勾

程式碼裡 empty-line 在 L118,merge-empty 在 L123:empty-line 先跑,把所有 l.trim()==="" 的行剔掉,進到 merge-empty 的串裡已經沒有 \n{3,} 連續段,merge-empty 的命中次數恆為 0。實測三組輸入命中全是 0:A\n\n\nB → A\nB;A\n\n\n\n\nB\n\n\nC → A\nB\nC;預設樣本 → 去空行之後的串。空行這件事我在那篇筆記裡細講過,這裡不復述。

只勾 merge-empty 時判據是 \n{3,} → \n\n:連續段 3、4、5 個換行都塌成 2 個換行,恰好 2 個換行(也就是 1 個空行)紋絲不動。

統計那四個數各自是什麼口徑

origLen 取 raw.length,是 UTF-16 碼元;同一張表裡的位元組列是另用 Buffer.byteLength 的 utf8 口徑量的:

中文文本 碼元 4 位元組 12

圖 1:同一段文字的碼元與位元組兩把尺。

reduced 的程式碼寫了負值分支 reduced>=0?reduced:0。預設樣本 origLen=56,在全部 2^8=256 種勾選組合上 reduced 落在 [0,16],負值命中 0/256。行數按 split("\n").length(L139/L140)計:尾隨換行的 a\n 原始行數 = 2;a\r\nb 原始行數 = 2 且 \r 留在行內;\n\n 原始行數 = 3;預設樣本原始行數 = 4。僅勾 empty-line 時預設樣本結果行數 = 3(刪掉中間那個空行);\n\n 結果行數 = 1,結果為空字串。

預設樣本在 256 種勾選下的結果分布

把預設樣本的 256 種組合窮舉一遍,輸出串去重後的不同結果數 = 8,各組組合數 64/64/32/32/16/16/16/16,合計 256。四個最大組的代表逐字:

  • 64 組:Hello World Hello JavaScript Hello Everyone
  • 64 組:HelloWorld HelloJavaScript HelloEveryone
  • 32 組:HelloWorld\nHelloJavaScript\n\nHelloEveryone
  • 16 組: Hello World \n Hello JavaScript \n Hello Everyone 。
64 64 32 32 16 16 16 16

圖 2:去重後的 8 種結果各自對應的組合數。

Q:我勾了「去除所有空格」,結果裡怎麼還有空格?

all-space 只執行 replace(/ /g,""),刪的是半形空格。對 a b\u00A0c\u3000d\te 它只刪掉那個半形空格,U+00A0、U+3000、Tab 全留著。

Q:只勾「去除空行」,為什麼只有空格的行也被刪了?

empty-line 的判據是 l.trim()!===""。僅空格、僅 Tab、僅 U+00A0、僅 U+3000 的行,只勾 empty-line 一步就刪得掉:在 [empty-line]、[trim-line+empty-line]、[fullwidth+trim-line+empty-line]、[fullwidth+empty-line] 四種組合下結果均為 X\nY。

Q:為什麼打開頁面時統計是空的,輸入字之後才有?

process 只在核取方塊 onchange、textarea oninput 與 selectAll 裡被呼叫,模板裡 grep process( 命中 11 處,沒有一處是載入即執行,打開頁面時結果框與統計區都是空的——這是字串層判斷,本批未開瀏覽器,DOM 層未實測。

Q:勾了「合併空行」,結果一點變化都沒有,為什麼?

同時勾了 empty-line 時,empty-line 先把空行全刪光,merge-empty 命中 0,等於沒勾;只勾 merge-empty 時它只把 \n{3,} 塌成 \n\n,只有 1 個空行的連續段本來就不滿足 3 個換行,保持原樣。

📎 相關文章

解碼是四步連做:1194 條語料在 unicode-convert 頁上只有 798 條能原樣回來 2026-11-09 保留集縮成 66 個字元:這一頁三檔編碼在 128 個碼位上的逐字元實測與 9 格編解碼矩陣 2026-11-09 取色器的解析門只收六位十六進位:三位短寫摘出能算、頁面永遠走不到 2026-11-08 改了鹽值不點按鈕,四個框一動不動:MD5 頁的三段拼接與多行輸入實測 2026-11-08 60 位時間戳在這一頁只剩 28 個位置:UUID 產生器 v1 分支的移位與掩碼逐毫秒實測 2026-11-08