正则表达式的全局搜索标志g导致表单验证反复成功失败
in 前端 with 0 comment

正则表达式的全局搜索标志g导致表单验证反复成功失败

in 前端 with 0 comment

0x01 遇到了一个BUG

这周遇到了一个很有意思的问题,记录一下。

背景是项目编写完成后,我着手开始优化form表单的正则验证。当使用封装好的正则验证类验证手机号时,在change中会出现一次成功一次失败的问题。

这里上一下我复盘时写的demo代码来做展示:

<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>
export const RegExpTable = {
  numRegex: /^[0-9]+$/g
}

num1的输入框中输入数字1验证

image-20211017151341807.png

就会出现一次成功一次失败的问题,而在num输入框中则不会遇到问题。

0x02 修复BUG

发现这个问题的时候项目已经临近deadline了,没有足够的时间让我找出具体原因来对症下药,只能尽快求稳的修复这个BUG。

明面上的解决办法很简单,既然num输入框设置正则表达式能正常使用,那说明我在自定义验证方法中直接使用正则表达式(而非引用)应该也能使用,于是我把代码改成这样:

// App.vue
// 把RegExpTable.numRegex直接替换为正则表达式
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()
}

运行测试,果然可以使用:

image-20211017154609890.png

改到这里时我在想,那是不是可以使用new RegExp来构造正则表达式,在RegExpTable.js中存储每个正则表达式的字符串呢?

于是我又做出了一些改动:

// RegExpTable.js
// 改成 正则字符串
export const RegExpTable = {
  numRegex: '^[0-9]+$'
}

// App.vue
// 在num1的自定义方法中使用RegExp构造正则表达式
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()
}

测试了下,方法可行。

image-20211017155310272.png

0x03 为什么这样写会出BUG

虽然赶在deadline前准时完工了,但这个问题依旧困扰着我。

这周末有空就翻阅了下MDN,终于知道了为什么。

在MDN关于RegExp.prototype.test()的文档中,有这么一段话

如果正则表达式设置了全局标志,test()的执行会改变正则表达式lastIndex属性。连续的执行test()方法,后续的执行将会从 lastIndex 处开始匹配字符串

同时也给出了一个例子

var regex = /foo/g;

// regex.lastIndex is at 0
regex.test('foo'); // true

// regex.lastIndex is now at 3
regex.test('foo'); // false

!!!,这与我遇到的问题正好一致啊

RegExp.lastIndex文档中提到lastIndex 是正则表达式的一个可读可写的整型属性,用来指定下一次匹配的起始索引。且只有在正则表达式使用了表示全局检索的 "g" 标志时,该属性才会起作用。

关于它的规则是这样的:

所以正是因为lastIndex属性值的存在,导致每次验证不一定是从第一个字符开始匹配的而出现的问题。
所以当我每次都构建正则表达式后再验证时,就不会遇到验证失败的问题了。

因为全局搜索标志的作用是从上一个匹配的位置继续进行搜索,而我这里执行的是一次性的格式验证。

此时,这个问题的最优解就成了:去掉全局搜索标志g

代码如下:

// RegExpTable.js
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()
  }
}

image-20211017165019915.png

Comments are closed.