0x01 遇到了一个BUG
这周遇到了一个很有意思的问题,记录一下。
背景是项目编写完成后,我着手开始优化form表单的正则验证。当使用封装好的正则验证类验证手机号时,在change中会出现一次成功一次失败的问题。
这里上一下我复盘时写的demo代码来做展示:
App.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| <template> <div id="app"> <el-form :model="testForm" :rules="testFormRules" label-width="150px" :style="{ width: '400px', margin: '0 auto' }"> <el-form-item prop="num" label="正则验证数字"> <el-input v-model="testForm.num"></el-input> </el-form-item> <el-form-item prop="num1" label="自定义方法验证数字"> <el-input v-model="testForm.num1"></el-input> </el-form-item> </el-form> </div> </template>
<script> import { RegExpTable } from './RegExpTable' export default { name: 'App', data () { return { testForm: { num: '', num1: '' }, testFormRules: { num: [ { required: true, pattern: RegExpTable.numRegex, message: '请输入数字', trigger: 'change' } ], num1: [ { required: true, validator: (rule, value, callback) => { if (!RegExpTable.numRegex.test(value)) { console.log(`error pattern = ${RegExpTable.numRegex} value = ${value}`) return callback(new Error('请输入数字')) } else { console.log(`success pattern = ${RegExpTable.numRegex} value = ${value}`) callback() } }, trigger: 'change' } ] } } } } </script>
|
RegExpTable.js
1 2 3
| export const RegExpTable = { numRegex: /^[0-9]+$/g }
|
在num1的输入框中输入数字1验证
![]()
就会出现一次成功一次失败的问题,而在num输入框中则不会遇到问题。
0x02 修复BUG
发现这个问题的时候项目已经临近deadline了,没有足够的时间让我找出具体原因来对症下药,只能尽快求稳的修复这个BUG。
明面上的解决办法很简单,既然num输入框设置正则表达式能正常使用,那说明我在自定义验证方法中直接使用正则表达式(而非引用)应该也能使用,于是我把代码改成这样:
1 2 3 4 5 6 7 8 9
|
if (!/^[0-9]+$/g.test(value)) { console.log(`error pattern = ${RegExpTable.numRegex} value = ${value}`) return callback(new Error('请输入数字')) } else { console.log(`success pattern = ${RegExpTable.numRegex} value = ${value}`) callback() }
|
运行测试,果然可以使用:
![]()
改到这里时我在想,那是不是可以使用new RegExp
来构造正则表达式,在RegExpTable.js
中存储每个正则表达式的字符串呢?
于是我又做出了一些改动:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
export const RegExpTable = { numRegex: '^[0-9]+$' }
const regex = new RegExp(RegExpTable.numRegex, 'g') if (!regex.test(value)) { console.log(`error pattern = ${RegExpTable.numRegex} value = ${value}`) return callback(new Error('请输入数字')) } else { console.log(`success pattern = ${RegExpTable.numRegex} value = ${value}`) callback() }
|
测试了下,方法可行。
![]()
0x03 为什么这样写会出BUG
虽然赶在deadline前准时完工了,但这个问题依旧困扰着我。
这周末有空就翻阅了下MDN,终于知道了为什么。
在MDN关于 RegExp.prototype.test() 的文档中,有这么一段话
如果正则表达式设置了全局标志,test()
的执行会改变正则表达式lastIndex
属性。连续的执行test()
方法,后续的执行将会从 lastIndex 处开始匹配字符串
同时也给出了一个例子
1 2 3 4 5 6 7
| var regex = /foo/g;
regex.test('foo');
regex.test('foo');
|
!!!,这与我遇到的问题正好一致啊
在RegExp.lastIndex
文档中提到**lastIndex**
是正则表达式的一个可读可写的整型属性,用来指定下一次匹配的起始索引。且只有在正则表达式使用了表示全局检索的 “g
“ 标志时,该属性才会起作用。
关于它的规则是这样的:
- 如果
lastIndex
大于字符串的长度,则 regexp.test
和 regexp.exec
将会匹配失败,然后 lastIndex
被设置为 0。
- 如果
lastIndex
等于字符串的长度,且该正则表达式匹配空字符串,则该正则表达式匹配从 lastIndex
开始的字符串。(then the regular expression matches input starting at lastIndex
.)
- 如果
lastIndex
等于字符串的长度,且该正则表达式不匹配空字符串 ,则该正则表达式不匹配字符串,lastIndex
被设置为 0.。
- 否则,
lastIndex
被设置为紧随最近一次成功匹配的下一个位置。
所以正是因为lastIndex属性值的存在,导致每次验证不一定是从第一个字符开始匹配的而出现的问题。
所以当我每次都构建正则表达式后再验证时,就不会遇到验证失败的问题了。
因为全局搜索标志的作用是从上一个匹配的位置继续进行搜索,而我这里执行的是一次性的格式验证。
此时,这个问题的最优解就成了:去掉全局搜索标志g
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| export const RegExpTable = { numRegex: /^[0-9]+$/ }
(rule, value, callback) => { if (!RegExpTable.numRegex.test(value)) { console.log(`error pattern = ${RegExpTable.numRegex} value = ${value}`) return callback(new Error('请输入数字')) } else { console.log(`success pattern = ${RegExpTable.numRegex} value = ${value}`) callback() } }
|
![]()