JavaScript 字符串和正则表达式

第五章 Strings and Regular Expressions 字符串和正则表达式

  • 字符串的连接尽量使用简单的+和+=,这样的性能更高。
str += "one" + "two";
//下面的方式将比上面的快。因为少了一步建立临时字符串。
str += "one";
str += "two";
//下面的代码也可以优化
str = str + "one" + "two";
//但是下面的代码还是要建立临时字符串
str = "one" + str + "two";
  • 在IE中,以上的技术并不适用。

  在 IE8中,连接字符串只是记录下构成新字符串的各部分字符串的引用。在最后时刻(当你真正使用连接后的字符串时),各部分字符串才被逐个拷贝到一个新的“真正的”字符串中,然后用它取代先前的字符串引用,所以并非每次使用字符串时都发生合并操作。
  在 IE8 以前的版本更慢,它每连接一堆字符串都要复制到新分配的内存中。所以str = str + “one” + “two”;这段代码要拷贝两次。
  在IE中使用数组联结方法效率更高。

// 优化前
var str = "I'm a thirty-five character string.",
newStr = "",
appends = 5000;
while (appends--) {
    newStr += str;
}
// 优化后
var str = "I'm a thirty-five character string.",
strs = [],
newStr,
appends = 5000;
while (appends--) {
    strs[strs.length] = str;
}
newStr = strs.join("");
  • concat方法也可以连接字符串,但是比直接+或+=要慢一些。

  • 正则表达式的工作过程。

编译—> 设置起始位置—> 匹配每个正则表达式的字元—>匹配成功或失败

  • 正则的回溯,在这里不详细写了,内容太多,感兴趣的可以去网上查写资料看一下。

  • 回溯失控解决办法:具体化。

  此类问题的解决办法在于尽可能具体地指出分隔符之间的字符匹配形式。例如”.?”用于匹配双引号包围的一个字符串。用更具体的[^”\rn]取代过于宽泛的.*?,就去除了回溯时可能发生的几种情况,如尝试
用点号匹配引号,或者扩展搜索超出预期范围。
  如果没有办法替换更具体的,如[^<]替代[\s\S]因为在搜索过程
中可能会遇到其他类型的标签,你可以通过重复一个非捕获组来达到同样效果, 它包含一个回顾 (阻塞下一个所需的标签)和[\s\S](任意字符)元序列。这确保中间位置上你查找的每个标签都会失败,然后,
更重要的是,[\s\S]模板在你在回顾过程中阻塞的标签被发现之前不能被扩展。

  • 修剪字符串。去除字符串首尾的空格是一个简单而常见的任务。

  可以用正则表达式来进行修剪。

if (!String.prototype.trim) {
    String.prototype.trim = function() {
        return this.replace(/^\s+/, "").replace(/\s+$/, "");
    }
}
// test the new method...
// tab (\t) and line feed (\n) characters are
// included in the leading whitespace.
var str = " \t\n test string ".trim();
alert(str == "test string"); // alerts "true"

if 语句避免覆盖 trim 函数如果它已经存在,因为原生函数进行了优化,通常远远快于你用 JavaScript 自己写的函数。
下面是几种正则修剪的代码:

// trim 2 
String.prototype.trim = function() {
return this.replace(/^\s+|\s+$/g, "");
}

这可能是最通常的解决方案。它通过分支功能合并了两个简单的正则表达式,并使用/g(全局)标记替换所有匹配,而不只是第一个(当目标字符串首尾都有空格时它将匹配两次)。这并不是一个可怕的方法,但是对长字符串操作时,它比使用两个简单的子表达式要慢,因为两个分支选项都要测试每个字符位置。

// trim 3
String.prototype.trim = function() {
    return this.replace(/^\s*([\s\S]*?)\s*$/, "$1");
}

这个正则表达式的工作原理是匹配整个字符串,捕获从第一个到最后一个非空格字符之间的序列,记入后向引用 1。然后使用后向引用 1 替代整个字符串,就留下了这个字符串的修剪版本。

// trim 4
String.prototype.trim = function() {
    return this.replace(/^\s*([\s\S]*\S)?\s*$/, "$1");
}

这个表达式与上一个很像,但出于性能原因以贪婪量词取代了懒惰量词。为确保捕获组只匹配到最后一个非空格字符,必需尾随一个\S。然而,由于正则表达式必需能够匹配全部由空格组成的字符串,整个捕获组通过尾随一个?量词而成为可选组。

// trim 5
String.prototype.trim = function() {
    return this.replace(/^\s*(\S*(\s+\S+)*)\s*$/, "$1");
}

下面代码是不用正则表达式写的修剪字符串,虽然性能不如正则表达式,但是还是值得学习的。

// trim 6
String.prototype.trim = function() {
    var start = 0,
    end = this.length - 1,
    ws = " \n\r\t\f\x0b\xa0\u1680\u180e\u2000\u2001\u2002\u2003
    \u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u2028\u2029\u202f
    \u205f\u3000\ufeff";
    while (ws.indexOf(this.charAt(start)) > -1) {
        start++;
    }
    while (end > start && ws.indexOf(this.charAt(end)) > -1) {
        end--;
    }
    return this.slice(start, end + 1);
}

此代码中的 ws 变量包括 ECMAScript 5 中定义的所有空白字符。 出于效率原因,在得到修剪区的起始和终止位置之前避免拷贝字符串的任何部分。


Summary 总结


密集的字符串操作和粗浅地编写正则表达式可能是主要性能障碍,但本章中的建议可帮助您避免常见缺陷。

  • 当连接数量巨大或尺寸巨大的字符串时,数组联合是 IE7 和它的早期版本上唯一具有合理性能的方法
  • 如果你不关心 IE7 和它的早期版本, 数组联合是连接字符串最慢的方法之一。使用简单的+和+=取而代之,可避免(产生)不必要的中间字符串。
  • 回溯既是正则表达式匹配功能基本的组成部分,又是正则表达式影响效率的常见原因。
  • 回溯失控发生在正则表达式本应很快发现匹配的地方,因为某些特殊的匹配字符串动作,导致运行缓慢甚至浏览器崩溃。避免此问题的技术包括:使相邻字元互斥,避免嵌套量词对一个字符串的相同部分多次匹配,通过重复利用前瞻操作的原子特性去除不必要的回溯。
  • 提高正则表达式效率的各种技术手段,帮助正则表达式更快地找到匹配,以及在非匹配位置上花费更少时间(见《更多提高正则表达式效率的方法》)。
  • 正则表达式并不总是完成工作的最佳工具,尤其当你只是搜索一个文本字符串时。
  • 虽然有很多方法来修整一个字符串,使用两个简单的正则表达式(一个用于去除头部空格,另一个用于去除尾部空格)提供了一个简洁、跨浏览器的方法,适用于不同内容和长度的字符串。从字符串末尾开始循环查找第一个非空格字符,或者在一个混合应用中将此技术与正则表达式结合起来,提供了一个很好的替代方案,它很少受到字符串整体长度的影响。