一次使用element-ui表单校验失效问题的排查记录

问题背景:在使用element-uiform组件时,用到了表单的validate数据规则校验能力。当在本地开发测试时,必填项没填时可以正确提示错误,而在打包发上测试环境时,却失效了,必填项没数据依然能正确的提交通过


作为一个合格的程序员,既然碰到问题,我们就要找出问题归因,并把问题解决掉。

创建问题排查环境,提高排查效率

因为项目代码比较庞大,影响因素也多,且打包构建也比较费时,严重影响调试排查效率。所以把有问题的功能抽出来,独立成demo,而其他配置则保留跟原项目一样,相当于完整项目的一个小型版本。

断点调试,直面问题源头

分析代码逻辑,使用表单校验时用的是form组件的validate方法,所以首先把问题定位在这个方法上。
复制代码

formvalidate内部实现实际上是使用的另一个库async-validator的能力。它是在这个库的基础上做了一些封装,使适用于element-ui的表单组件。

通过跟踪代码执行逻辑发现了一处可疑之处,格式化后的代码如下

function za(e, t) {
  return (
    null == e ||
    !("array" !== t || !Array.isArray(e) || e.length) ||
    !(
      !(function (e) {
        return (
          "string" === e ||
          "url" === e ||
          "hex" === e ||
          "email" === e ||
          "pattern" === e
        )
      })(t) ||
      "string" != typeof e ||
      e
    )
  )
}
复制代码

这个方法对应的未编译前源码为:

function isNativeStringType(type: string) {
  return (
    type === 'string' ||
    type === 'url' ||
    type === 'hex' ||
    type === 'email' ||
    type === 'date' ||
    type === 'pattern'
  );
}

export function isEmptyValue(value: Value, type?: string) {
  if (value === undefined || value === null) {
    return true;
  }
  if (type === 'array' && Array.isArray(value) && !value.length) {
    return true;
  }
  if (isNativeStringType(type) && typeof value === 'string' && !value) {
    return true;
  }
  return false;
}
复制代码

此方法用来判断值是否为空,为空则返回true

在chrome的开发者工具的Sources面板里面查看za()返回的值,发现为false,实际应该为true。为了进一步验证一个事实,打印出如下的结果

// t = 'string'
console.log(
    function(e) {
      console.log('e %s', e, typeof e)
      return "string" === e || "url" === e || "hex" === e || "email" === e || "pattern" === e
    }(t),
    function(e) {
        return "string" === e || "url" === e || "hex" === e || "email" === e || "pattern" === e
  }(t),
  !function(e) {
      console.log('e %s', e, typeof e)
      return "string" === e || "url" === e || "hex" === e || "email" === e || "pattern" === e
   }(t),
  !function(e) {
      return "string" === e || "url" === e || "hex" === e || "email" === e || "pattern" === e
    }(t),
    !function(e) {
      return "string" === e || "url" === e || "hex" === e || "email" === e || "pattern" === e
    }('string')
  )
复制代码

打印输出结果:true false false true false

问题就出在此处

// t = string
function(e) {
    return "string" === e || "url" === e || "hex" === e || "email" === e || "pattern" === e
}(t)
复制代码

正常执行应该是返回true的,而这里是返回false,肯定是哪里出了问题。

所以这里就已经掉进了一个陷阱了,看代码是没问题的,无论在哪里执行都可以正常返回结果,但在这里却返回异常的结果。

那么问题出在哪里呢?
复制代码

我们在开发时都有开启chrome开发者工具的sourcemap功能,在断点调试看代码时,工具给我们看到的是混淆压缩前的代码,所以所见的代码并不跟运行的代码是一模一样的,运行时的代码片段是经过压缩混淆后的代码片段。

关闭控制台的Enable JavaScript source maps功能,调试进去看实际的代码

console.log(
  (function (e) {
    return (
      console.log("e %s", e, typeof e),
      "string" === e ||
        "url" === e ||
        "hex" === e ||
        "email" === e ||
        "pattern" === e
    )
  })(t),
  !1, // 注意看这里
  !(function (e) {
    return (
      console.log("e %s", e, typeof e),
      "string" === e ||
        "url" === e ||
        "hex" === e ||
        "email" === e ||
        "pattern" === e
    )
  })(t),
  !0, // 注意看这里
  !1 // 注意看这里
)
复制代码

从以上代码发现,部分函数直接被编译成了!0 !1 !0这样的固定值。也就是说打包压缩时,如果方法里面只是返回值,没有其他操作(如console.log),那么能推测出返回结果的,会直接编译成最终的返回结果。

function(e) {
    return "string" === e || "url" === e || "hex" === e || "email" === e || "pattern" === e
  }(t)
  // 此立即执行函数会被编译成  !1
复制代码

问题总结

综上分析,问题就出在压缩工具在混淆压缩时,把部分立即执行函数编译成错误的推测出来的固定值。而我们在开发调试时又是使用source maps模式,看到的是工具编译前的源码,所以掉进了代码陷阱,没有及时觉悟到编译后的代码会修改编译前的代码逻辑这事。

一般本地环境看到的效果和测试环境看到的效果不一样时,排除掉其他外因的情况下,可以想想目前测试包和本地包的差异在哪,差异就在压缩混淆,和开启了production模式,而这两个是会改变代码的结构的,在特殊情况下可能会引起不必要的bug。

解决方案

项目打包时是使用webpack-parallel-uglify-plugin插件做压缩的,把压缩工具改成terser-webpack-plugin即可解决问题

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享