工作中突然想给姓名输入框加入正则匹配的验证,以防止用户输入的姓名比网络昵称还奇葩,结果还真费了不少功夫。
首先我们确认规则:
①中英文文字不能混杂
②汉字不能夹杂任何特殊字符和空格(如果要兼容少数民族的姓名的话可以参考英文的匹配写法)
③英文只能夹杂空格,且不能在开头结尾
中文的名称规范很简单:
let reg = /^[\u4e00-\u9fa5]+$/;
英文的名称规范就相对复杂了,因为空格不能在开头结尾,且不能连续。所以我们可以换个角度思考:
英文名称就是一个存储着多个纯字母字符串的数组,然后用空格连起来。因此,除最后一段字母以外,其他所有字母串都默认紧随一个空格,最后必须以字母结尾。因此思路为:
①匹配多个字母,并以空格结尾,匹配0次或多次
②结尾匹配多个字母
代码如下:
let reg = /^([a-zA-Z]+\s)*[a-zA-Z]+$/;
当然,我们也可以要求用户的姓名必须以大写字母开头:
let reg = /^(\b[A-Z][a-z]*\s)*\b[A-Z][a-z]*$/;
考虑到本人作为一名中国人,对老外的起名习惯不太了解,所以“限制大写开头”的规则并没有实际使用,还是使用了比较稳妥的“不限制大小写”的版本,没准用户也是个痴迷驼峰命名法的程序员呢(笑)。
综合两个正则规则,我们的用户姓名限制条件写为:
let reg = /(^([a-zA-Z]+\s)*[a-zA-Z]+$)|(^[\u4e00-\u9fa5]+$)/;
另:这里标记我发现的一个坑。
我曾设想英文的匹配规则如下:
“匹配多个字母串,以字母开头,结尾的空格可有可无,整个字符串最后必须是一个字母。”
写法如下:
let reg = /(^([a-zA-Z]+\s?)*[a-zA-Z]$)/; // 存在缺陷,请勿使用
进行测试:
console.log(reg.test('Ahsflu Aaif Adfssafea'));
// true
测试结果证明,该匹配规则可以验证英文姓名。
那么问题来了,缺陷在哪里呢?我当时自信地以为找到了不错的匹配规则,但是进行进一步测试的时候发现,这个正则匹配偶尔会卡住!
我们来测试一下:
let reg = /(^([a-zA-Z]+\s?)*[a-zA-Z]$)/;
console.time('time');
console.log(reg.test('Ahsflu Aaif Adfssafea'));
console.timeEnd('time');
console.time('time');
console.log(reg.test('Ahsflu Aaif Adfssadsaffea'));
console.timeEnd('time');
console.time('time');
console.log(reg.test('Ahsflu Aaif Adfssadsdsfaaffea'));
console.timeEnd('time');
console.time('time');
console.log(reg.test('Ahsflu Aaif Adfsaffea '));
console.timeEnd('time');
console.time('time');
console.log(reg.test('Ahsflu Aaif Adfsadsfsdafasdffea '));
console.timeEnd('time');
显示结果如下:
true
time: 8.811ms
true
time: 0.694ms
true
time: 0.707ms
false
time: 1.921ms
false
time: 1192.601ms
注意到了吗?当测试的字符串结尾有空格的时候,整个正则匹配需要的时间随着字符串长度增加而呈现爆炸式增长。很明显这与正则表达式中的'\s?'这个表示可有可无的空格有关。
我的猜测是:由于正则匹配默认采用“贪心”模式,当结尾为空格时,正则匹配根据第一项' ([a-zA-Z]+\s?)* '可以正确的匹配到结尾,但是却发现下一项' [a-zA-Z] '匹配失败了,由于第一项最后的*(匹配1至多个)原则,匹配存在多种可能,程序尚不能判定结果为true还是false,所以必须推倒重来,进行多次尝试。在程序尝试完所有可能的情况之后,程序终于判定字符串不合法,才输出false。
实际情况是:只要我们删掉'\s?'中的'?'指令,这个bug就不复存在了。
这件事情告诉我们在正则匹配中要谨慎使用'?',正如它的含义“可有可无”一样,被'?'形容的规则可以存在于范围内任何可能的地方,这对于死板的程序来说无疑是一个噩梦。
另外,连续使用console.time()方法统计时间的时候,第一项往往会需要更长的时间(分配内存什么的),console.time()统计的时间包括自身。所以即便是统计一个空的语句段,第一次console.time()方法也会比后续方法多出几倍甚至几十倍的时间。