本文探讨 ECMAScript 5 规范对正则表达式字面量的核心变更:从 ES3 的共享单实例改为每次求值生成独立对象。这解决了多正则共享 lastIndex 导致的副作用问题,并改进了错误报告机制及字符类中转义规则。文章对比了 IE、Firefox、Chrome 等浏览器的实现差异,指出新版规范更符合开发者预期。
ECMAScript 5 正则字面量行为变更
《JavaScript语言精粹》第72页曾指出,通过正则表达式字面量创建的 RegExp 对象会共享同一个实例。
具体表现为:
示例代码如下:
function make_a_matcher( ) {
return /a/gi;
}
var x = make_a_matcher( );
var y = make_a_matcher( );
// 注意:x 和 y 是同一个对象!
x.lastIndex = 10;
document.writeln(y.lastIndex); // 10在主流浏览器如 IE6-IE9、FireFox4、Chrome10、Safari5 中运行上述代码,输出结果均为 0;而在 Firefox 3.6.16 中输出为 10。这一差异源于 ECMAScript 5 规范第24页和第247页的规定: A regular expression literal is an input element that is converted to a RegExp object (see 15.10) each time the literal is evaluated. Two regular expression literals in a program evaluate to regular expression objects that never compare as === to each other even if the two literals' contents are identical. A RegExp object may also be created at runtime by new RegExp (see 15.10.4) or calling the RegExp constructor as a function (15.10.3).
7.8.5: Regular expression literals now return a unique object each time the literal is evaluated. This change is detectable by any programs that test the object identity of such literal values or that are sensitive to the shared side effects.
由此可见,ECMAScript 3 规范中,正则表达式字面量创建的 RegExp 对象共享同一实例;而 ECMAScript 5 规范中,每次求值均生成独立实例。《JavaScript语言精粹》出版时 ECMAScript 5 尚未发布,因此书中内容与 ECMAScript 3 标准保持一致。Firefox 3.6 遵循 ECMAScript 3 标准,故输出与书中一致;而较新的 Firefox4、Chrome 和 Safari5 遵循 ECMAScript 5 标准。值得注意的是,IE6-IE8 虽未完全遵循 ECMAScript 3 标准,但在此问题上处理正确。显然,ECMAScript 5 规范更符合开发者期望:相同的正则表达式字面量应创建独立的 RegExp 对象,拥有不同的 lastIndex,从而便于分别处理。
规范补充说明与错误处理
ECMAScript 5 规范第247页还补充了两条关于正则表达式字面量在 ECMAScript 5 与 ECMAScript 3 之间变化的说明:
7.8.5: Edition 5 requires early reporting of any possible RegExp constructor errors that would be produced when converting a RegularExpressionLiteral to a RegExp object. Prior to Edition 5 implementations were permitted to defer the reporting of such errors until the actual execution time creation of the object.
7.8.5: In Edition 5 unescaped “/” characters may appear as a CharacterClass in a regular expression literal. In Edition 3 such a character would have been interpreted as the final character of the literal.
第一点指出,在 ECMAScript 5 中,将正则表达式字面量转换为 RegExp 对象时,任何 RegExp 构造器的错误都会尽早报告;而在之前的规范中,此类错误可推迟至对象实际创建执行时再报错。
第二点说明,在 ECMAScript 5 的正则表达式字面量中,未转义的正斜杠“/”可直接出现在正则表达式字符类中;而在 ECMAScript 3 中,该字符会被解释为字面量的结束符。目前 IE6-IE9、Firefox3.6-Firefox4.0、Chrome 和 Safari 均支持在正则表达式字符类中直接使用未转义的正斜杠“/”。例如:
示例代码如下:
var my_regexp = /([8/5+4]*).{3}/g;
var str = '8/5+4 is what!';
var result = my_regexp.exec(str); // 在 IE6-9, FF3.6-4.0, Chrome, Safari 中结果一致
for(var i = 0,n = result.length; i < n; ++i){
document.writeln(result[i]);
}
result[0] = 8/5+4 is
result[1] = 8/5+4 兼容性建议与工具推荐
《JavaScript语言精粹》第76页也指出,在正则表达式字符类中使用正斜杠“/”需要转义,这是基于 ECMAScript 3 规范。由于正则表达式中需转义的特殊字符较多,若心存疑虑,可对任何特殊字符使用反斜杠“\”使其字面化以确保安全,但该规则不适用于字母和数字。
正则表达式字面量从 ECMAScript 3 到 ECMAScript 5 的变更,也符合 HTML5 设计原理中的两条原则:一是“一旦遇到冲突,最终用户优先,其次是作者,其次是实现者,其次标准制定者,最后才是理论上的完满”;二是“支持已有内容”。
最后推荐 XRegExp,这是一个优秀的正则表达式 JavaScript 库,兼容多个主流浏览器、ECMAScript 3 和 ECMAScript 5。