杂记 › 技术笔记 › 八个框的界面顺序和代码执行顺序是反的:文本清理页硬编码八步顺序、逐对换序真值表与统计码元口径实测

八个框的界面顺序和代码执行顺序是反的:文本清理页硬编码八步顺序、逐对换序真值表与统计码元口径实测

粘出来的文本一堆空白,勾了半天越勾越乱?文本清理页把八步执行顺序写死在代码里:界面顺序与代码顺序完全反序,勾选先后不影响结果;再实测逐对换序真值表、去空行与合并空行的互斥抵消、统计码元口径与 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 取色器只收六位数:三位短写摘出能算、页面永远走不到,全空间恒等率 10.5813% 2026-11-08 改了盐值不点按钮,四个框一动不动:MD5 页的三段拼接与多行输入实测 2026-11-08 60 位时间戳在这一页只剩 28 个位:UUID 生成器 v1 分支的移位与掩码逐毫秒实测 2026-11-08