imeay / blog

my blog
0 stars 0 forks source link

00 在 JS 正则表现【源码分析】 #40

Open imeay opened 4 years ago

imeay commented 4 years ago

Preview

帮女朋友想一个匹配数字的输入的表达式, 要求整数或者小数,小数点后有且只能2位。 一会功夫,正则就出来了

/^(0|[1-9]\d*)(\.\d{1,2})?$/

验证过程中, 发现有一个奇怪的现象,数字 00 竟然也可以通过

> reg.test(00)
true
> reg.test('00')
false

这个就让我很好奇了,为什么 数字 00 在这里竟然能通过正则表达式。 不禁想看下 RegExp.prototype.test 实现,在网上找下一下源码 https://chromium.googlesource.com/v8/v8/+/4.3.49/src/regexp.js?autodive=0%2F%2F 关键代码有2段 第一段是方法名映射

InstallFunctions(GlobalRegExp.prototype, DONT_ENUM, GlobalArray(
--
  | "exec", RegExpExecJS,
  | "test", RegExpTest,
  | "toString", RegExpToString,
  | "compile", RegExpCompileJS
  | ));

第二段则是疑问的关键啦


function RegExpTest(string) {
--
  | if (!IS_REGEXP(this)) {
  | throw MakeTypeError('incompatible_method_receiver',
  | ['RegExp.prototype.test', this]);
  | }
  | string = TO_STRING_INLINE(string);
  |  
  | var lastIndex = this.lastIndex;
  |  
  | // Conversion is required by the ES5 specification (RegExp.prototype.exec
  | // algorithm, step 5) even if the value is discarded for non-global RegExps.
  | var i = TO_INTEGER(lastIndex);
  |  
  | if (this.global \|\| (harmony_regexps && this.sticky)) {
  | if (i < 0 \|\| i > string.length) {
  | this.lastIndex = 0;
  | return false;
  | }
  | // matchIndices is either null or the $regexpLastMatchInfo array.
  | var matchIndices = %_RegExpExec(this, string, i, $regexpLastMatchInfo);
  | if (IS_NULL(matchIndices)) {
  | this.lastIndex = 0;
  | return false;
  | }
  | $regexpLastMatchInfoOverride = null;
  | this.lastIndex = $regexpLastMatchInfo[CAPTURE1];
  | return true;
  | } else {
  | // Non-global, non-sticky regexp.
  | // Remove irrelevant preceeding '.*' in a test regexp.  The expression
  | // checks whether this.source starts with '.*' and that the third char is
  | // not a '?'.  But see https://code.google.com/p/v8/issues/detail?id=3560
  | var regexp = this;
  | if (regexp.source.length >= 3 &&
  | %_StringCharCodeAt(regexp.source, 0) == 46 &&  // '.'
  | %_StringCharCodeAt(regexp.source, 1) == 42 &&  // '*'
  | %_StringCharCodeAt(regexp.source, 2) != 63) {  // '?'
  | regexp = TrimRegExp(regexp);
  | }
  | // matchIndices is either null or the $regexpLastMatchInfo array.
  | var matchIndices = %_RegExpExec(regexp, string, 0, $regexpLastMatchInfo);
  | if (IS_NULL(matchIndices)) {
  | this.lastIndex = 0;
  | return false;
  | }
  | $regexpLastMatchInfoOverride = null;
  | return true;
  | }
  | }

其中有一行代码 string = TO_STRING_INLINE(string); 引起了我的注意,这里将输入的参数在内部转成字符串, 在浏览器试了一下00转字符串

String(00)
"0"

结果输出为"0", 这也就是解释了上面验证通过的原因了 00 -> '"0" , 而字符串 0 刚好满足正则规则