第5章:字符串和正则表达式

字符串连接

方法 示例
array.join() str = ["a", "b", "c"].join("");
string.concat() str = "a";str = str.concat("b", "c");

+和加等+=操作符

Firefox和编译器合并

当字符串通过这种方式合并在一起时,由于运行期没有中间字符串,所以花在连接过程的时间和内存可以减少到零。

数组项合并

Array.prototype.join方法将数组的所有元素合并成一个字符串,它接收一个字符串参数作为分隔符插入每个元素的中间。如果传入的是空字符,可以简单地将数组所有元素连接起来。

String.prototype.concat

字符串的原生方法concat能接收任意数量的参数,并将每一个参数附加到所调用的字符串上。

str = String.prototype.concat.apply(str, array);

正则表达式工作原理

正则表达式处理的基本步骤:

第一步:编译

当创建一个正则表达式(正则直接量RegExp构造函数),浏览器验证表达式,然后转化为一个原生代码程序,用于执行匹配工作。

第二步:设置起始位置

当正则进入使用状态,首先要确定目标字符串的起始搜索位置。

第三步:匹配每个正则表达式字元

正则表达式知道起始位置,它会逐个检查文本和正则表达式模式。当一个特定的字元匹配失败时,正则表达式会尝试回溯到之前尝试匹配的位置上,然后尝试其他可能的路径。

第四步:匹配成功或失败

理解回溯

正则表达式匹配目标字符串时,它从左到右逐个测试表达式的组成部分,确认是否能找到匹配项。如果遇到量词*+?{2,},正则表达式需决定何时尝试匹配更多字符;如果遇到分支|,必须从可选项中选择一个尝试匹配。

分支与回溯P89

重复与回溯P90

回溯失控P91

解决方案:具体化P92

使用预查和反向引用的模拟原子组P93

原子组的写法?>...,省略号表示任意正则表达式的模式,它是一种具有特殊反转性的非捕获组。一旦原子组中存在一个正则表达式,该组的任何回溯位置都会被丢弃。这为HTML正则表达式的回溯问题提供了一个更好的解决方案。

嵌套量词与回溯失控P94

嵌套量词是指量词出现在一个自身被重复量词修饰的组中。(x+)*

变得更糟P95

基准测试的说明

更多提高正则表达式效率的方法

关注如何让匹配更快失败

正则表达式慢的原因通常是匹配失败的过程慢,而不是匹配成功地过程慢。(通过增加回溯的次数去尝试所有的排列组合)

正则表达式以简单、必需的字元开始

一个正则表达式的起始标记应当尽可能快速地测试并排除明显不匹配的位置。好的起始标记通常是一个锚(^$)、特定字符串(x\u263A)、字符类([a-z]或类似\d的速记符)和单词边界\b

避免以分组或选择字元开头,避免类似/one|two/的顶层分支,因为它强迫正则表达式识别多种起始字元。

使用量词模式,使它们后面的字元互斥

当字符与字元相邻或子表达式能够重叠匹配时,正则表达式尝试拆解文本的路径数量将会增加。为避免这种情况,尽量具体化匹配模式。比如表达[^"\s\n]时不要使用.*?(它依赖回溯)

减少分支数量,缩小分支范围

分支使用竖线|可能要求在字符串的每个位置上测试所有分支选项,通常可以通过使用字符集和选项组件来减少对分支的需求,或将分支在正则表达式上的位置推后。

字符集比分支更快,因为它使用位向量,而不是回溯。当分支必不可少时,将常用的分支放到最前面。

使用非捕获组

如果不需要一个反向引用,可使用非捕获组来避免这些开销,比如使用(?:...)来替代(...)

只捕获感兴趣的文本以减少后续处理

暴露必需的字元

使用合适的量词

把正则表达式赋值给变量并重用它们

将复杂的正则表达式拆分为简单的片段

避免在一个正则表达式中处理太多的任务,每个正则表达式只在最后的匹配结果中执行查找。

何时不用正则表达式

charAt()方法读取特定位置上的字符。字符串方法slicesubstr以及substring都可用在特定位置上提取并检查字符串的值。indexOflastIndexOf方法非常适合查找特定字符串的位置,或判断它们是否存在。

去除字符串首尾空白

使用正则表达式去除首尾空白

if (!String.prototype.trim) {
    String.prototype.trim = function () {
        return this.replace(/^\s+/, '').replace(/\s+$/, '');
    }
}

不使用正则表达式去除首尾空白P102

混合解决方案

用正则表达式方法过滤头部空白,用非正则过滤尾部字符。

String.prototype.trim = function () {
    var str = this.replace(/^\s+/, ""),
        end = str.length - 1,
        ws = /\s/;
    while (ws.test(str.charAt(end))) {
        end--;
    }
    return str.slice(0, end + 1);
};

该方案在循环中使用一个正则来检查字符串的末尾字符是否为空白。

所有的trim方法的总趋势:在基于正则的方案中,字符串的总长度比修剪掉的字符数量更影响性能;而非正则从字符串末尾反向查找,不受字符串总长度的影响,但明显受到修剪空格的数量的影响。

小结

密集的字符串操作和草率地编写正则可能产生严重的性能问题,需要避免这些常见的陷阱: