同一条规则,「替换次数」这一格有两套量法
输入 cat cat cat cat cat,丢一条把 cat 换成 dog 的规则,统计区报 5;勾上「仅替换第一个匹配」再跑,报 1,输出里也确实只改掉最前面那一处。把替换词写回 cat 本身,两个数字变成 5 和 0——整串一个字没动,全量档照报 5。这页的「替换次数」不是一把尺子,是三套算法共用的一格。
这一篇只测 批量查找替换 的三个开关各自改了哪一段:模板实测 6240 字节、158 行,三个 checkbox 都没有 checked 属性,默认全不勾。规则行排队的先后、替换串里美元符号那一类展开,另一篇已经量过,本篇不碰。
字面档那张转义表点名 14 个元字符
「使用正则表达式」不勾时,查找词先过一遍字符类 /[.*+?^${}()|[\]\\]/g:命中的每一个前面插一根反斜杠,再交给 new RegExp。类里逐字点名的元字符共 14 个——. * + ? ^ $ { } ( ) | [ ] \。{ 与 } 都在表内,实测 a{2} 出来是 a\{2\};而 -、/、空格三样不在表内,a-b、a/b、a b 进去都原样出来。
替换词统一填 X、另两个开关关着,同一串查找词两档各跑一次:
| 查找词 | 输入文本 | 字面档 | 正则档 |
|---|---|---|---|
a.b | a.b aXb Y.b a.b | 2 | 3 |
a(b) | a(b) a(b)x ab | 2 | 1 |
a{2} | aa a{2} aaa | 1 | 2 |
a|b | a|b a b c | 1 | 4 |
a?b | a?b ab b | 1 | 3 |
字面档只认逐字符相同的那两处 a.b,输出 X aXb Y.b X;正则档的点吃任意字符,连 aXb 也算命中。差得最狠的是 a|b,1 对 4:竖线在字面档只是串里的一个符号,在正则档成了或运算。
区分大小写只增删 flags 里的一个字母
模板逐字写的是 caseSensitive ? 'g' : 'gi':不勾(默认)拿到带 i 的 gi,勾上才是 g。输入 Hello hello HELLO heLLo、查找 hello、替换 X:不勾报 4,原始字符 23 变结果字符 7;勾上报 1,只有纯小写那个被换掉,23 变 19,两档差 3 次。
「仅替换第一个匹配」把 g 摘掉,顺手换了尺子
这一档做两件事:先用 flags = flags.replace('g', '') 把 g 摘掉,让替换只落在第一处;计数分支则从「命中几条」换成比较替换前后的整串——if (before !== result) 才加一,于是每条规则在这一档最多贡献 1,加不加看的是这串变没变。
图 1:同一批样本下「替换次数」的读数与真实改动处数
零宽匹配:统计那一格与落地那一格分家
非 firstOnly 档的那个数不来自替换的返回值:先用 pattern.source 带 g 重建一个正则去 match() 取长度,再另跑一次替换落地,两个对象互不携带状态。命中长度为零的正则,从这里开始和「改了几处」分道:
x*于abc(串里没有 x)统计 4 次;替换词填X得XaXbXcX,结果字符从 3 涨到 7;填空串则输出abc一字不差,那一格仍报 4。\b于a bc统计 4 次,得XaX XbcX,4 涨到 8。(?=a)于aba统计 2 次,得XabXa,3 涨到 5。- 锚点
^与$于abc各统计 1 次,各只多插 1 个字符。 a*于baa统计 3 次,替换词 A 得AbAA,3 变 4。
空位为什么这么多:带 g 的匹配遇到零宽命中时靠 lastIndex 加一强制推进,/x*/g 打在 abx 上能收出 4 项(三个空串加一个 x)。这一档里统计次数与替换实际走的编辑次数逐例相等,真正对不上的是它与结果字符增减。
空查找词整条丢弃,坏正则一声不响
规则收集那行是 if (inputs[0].value):查找框留空的规则在收集阶段就被整条丢掉,「规则数量」显示的是过滤之后的条数,不等于屏幕上的行数。三行里第 2 行查找留空,规则数量报 2、与行数差 1;查找填 0 属真值字符串照样保留;三行查找全空则规则数量 0、替换次数 0、输出原样。
编译不过的正则走的是另一条:new RegExp 抛错被 catch (e) {} 这个空分支吞掉,没有提示文案,那条规则进了列表却没执行。三条规则 a→X、b→Y、一个左括号→Z 打在 a b c 上,第三条抛错,输出 X Y c 保留前两条结果、替换次数 2、规则数量照报 3;同样这三串在字面档会被转义成字面量、不抛错,次数变 3。唯一的线索就是次数比预期少。
「原始字符 / 结果字符」数的是 UTF-16 码元
那两格取的是 raw.length 与 result.length,既不是码点也不是字节:一枚 emoji 报 2,一个汉字「中」报 1,一个增平面汉字(U+20000)报 2;中文测试 报 4,UTF-8 编码是 12 字节。首屏预置的三行 Hello 是纯 ASCII,43 码元、43 字节,init() 塞的那条 Hello→Hi 跑完输出三行 Hi、结果字符 34——每处省 3 码元,三处共省 9。
规则行那个属性值是怎么拼出来的
addRule(find, replace) 生成规则行时把查找词直接拼进 HTML 属性,第 71 行逐字是 'value="' + (find || '') + '" oninput="process()"',两侧没有做引号处理。字符串桩里逐字比对拼出的片段与属性读回的值:查找词 Hello 拼出 value="Hello"、原样读回;含单引号的 a'b 拼出 value="a'b",单引号在双引号属性里不需要转义,也原样读回;含两个裸双引号的 say "hi" now 拼出的片段逐字是 value="say "hi" now",而属性读取从 value=" 起读到下一个裸双引号为止,实际读回停在 say ,后面那截落到属性之外。替换词那一格用的是同一行写法。
FAQ
Q:替换次数能当成「改了多少处」吗?
不能直接用。非 firstOnly 档它等于各条规则命中数的累加,零宽空位也进账;firstOnly 档换成整串变没变,每条最多 1。同一份输入两档读数是 5 和 1,自等替换时一个报 5、一个报 0。
Q:勾上正则后命中变多,是哪里坏了?
不是。字面档走那张点名 14 个元字符的转义表,正则档把同一串按语法解释,撞上的位置本来就不同。想先确认写法本身,另一篇记过单验的路子,这里不重复。
Q:统计区整块不见了,是不是没跑?
输入为空时四格连标签一起被置空,那是分支本身的行为;输入一串空格则照常显示。数字看着不对,先分辨它属于哪一档口径,再看有没有哪一行的查找框留空。