《高性能javascript》读书笔记:第五章 字符串和正则表达式

字符串连接

1,字符串合并的方法

a,+

str="a"+"b"+"c";



b,+=

str="a";
str+="b";
str+="c";



c,array.join()

str=["a","b","c"].join("");



d,string.concat()

str="a";
str=str.concat("b","c");



2,数组项连接 Array.prototype.join

当连接数量巨大或尺寸巨大的字符串时,数组项连接是唯一在IE7及更早版本中性能合理的方法。

如果不考虑IE7及更早版本的性能,数组项连接是最慢的字符串连接方法之一。推荐使用简单的+和+=

3,String.prototype.concat  能接收任意数量的参数。最灵活的字符串合并方法。比使用+和+=稍慢,在IE、Opera和Chrome中慢得更明显。

 

正则表达式优化

 1,正则表达式工作原理 

第一步:编译

当创建一个正则表达式对象(使用正则直接量或RegExp构造函数),浏览器会验证你的模式,然后把它转化为一个原生代码程序,用于执行匹配工作。如果把正则对象赋值给一个变量,就能避免重复执行此步骤。

第二步:设置起始位置

目标字符串的起始搜索位置,是字符串的起始字符,或由正则表达式的lastIndex属性指定,或尝试匹配失败返回到这里时此位置则在最后一次匹配的起始位置的下一位字符的位置上。

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

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

第四步:匹配成功或失败

如果在字符串当前的位置发现了一个完全匹配,则宣布匹配成功。如果正则表达式所有的可能路径都没有匹配到,正则表达式引擎会回退到第二步,然后从下一个字符重新尝试。只有当字符串的每个字符(以及最后一个字符串后面的位置)都经历这个过程后还没有匹配,那么宣布失败。

2,回溯

 正则表达式扫描目标注字符串时,从左到右逐个测试其组成部分,看是否能找到匹配项。对每个量词(诸如*,+?或{2,})和分支都必须决定接下来如何处理。每当正则表达式做决定时,如果有必要的话,都会记住其他选择,以备返回时使用。如果当前选项找不到匹配值,或后面的部分匹配失败,那么正则表达式会回溯到最后一个决策点,然后在剩余的选项中选择一个。这个过程直到最终匹配成功或匹配失败。

示例1【分支与回溯】:(匹配步骤中用大写字母代替字母上面语句中的部分)

/h(ello|appy) hippo/.test("hello there,happy hippo);

 A  B     C       E                  FG    HI      J K       

a,A匹配F,成功

b,B匹配G,成功

c,匹配空格,成功

d,E匹配H,因为字符是t,失败

e,回溯到最近的决策点(第一次匹配完h后的位置),C匹配G,因为字符是ello,所以失败

f,再没其它分支可匹配了,就认为从第一个字符无法匹配,即开始从第二个字符进行匹配,A匹配G,失败

g,再开始从第三个字符进行匹配,同样失败。直到A匹配J,成功

h,B匹配K,失败,另一分支进行匹配,C匹配K,成功

i,再继续匹配,直到最后匹配完成,成功。匹配到了happy hippo

示例2【重复与回溯】

 /<p>.*<\/p>/i.test("<p>Para.1.</p><img src='smiley.jpg'><p>Para.2.</p><div>Div.</div>");

a,<p>匹配成功,

b,.*尽可能匹配多次,所以它会匹配到最后

c,匹配<,失败,因为未必没有字符可以匹配了

d,.*回溯一位,继续匹配<,失败,直到回溯到</div>前面,才匹配<成功

e,匹配\/,成功,再匹配p,失败

f,.*继续回溯,直到</p>,匹配成功。

匹配的结果是<p>Para.1.</p><img src='smiley.jpg'><p>Para.2.</p>

其实上面的结果可能并不是我们想要的,因为我们是想匹配前面的<p>Para.1.</p>

可以通过把贪婪量词*换成非贪婪量词*?。*?与*以相反的方式进行回溯工作。当正则表达式/<p>.*?<\/p>/匹配到.*?时,它先尝试全部跳过并匹配<\/p>。

示例3【回溯失控】

/<html>[\s\S]*?<head>[\s\S]*?<title>[\s\S]*?<\/title>[\s\S]*?<\/head>[\s\S]*?<body>[\s\S]*?<\/body>[\s\S]*?<\/html>/

用上面的正则表达式匹配html,正常情况下运行良好。但当目标html中缺少一个或多个标签时,比如</html>,则最后一个[\s\S]*?将扩展到html字符串的未尾,仍然没有找到</html>,正则表达式则依次向前搜索[\s\S]*?并记住回溯的位置以便后续使用,继续查找是否还有第二个</body>,直到字符串末尾,当所有步骤失败以后,继续倒数第三个[\s\S]*?,直到最后匹配失败。

此种失控的解决方案比较:

a,具体化,比如.*?用来匹配一个由双引号包围的字符串,则通过把.*?替换成更为具体的[^"\r\n]*,就去除回溯时发生的几种情况。太过于简单,很多时候没法使用。

b,通过重复一个非捕获组。包含否定式向前查看(阻止下一个依赖标签)和[\s\S](任意)元序列。消除了潜在的回溯失控,但是每个匹配字符重复向前查看是严重缺乏效率的,所以并不能提高效率。

字符 

描述

示例

(?:pattern)

匹配pattern,但不捕获匹配结果。

'industr(?:y|ies)

匹配'industry'或'industries'。

(?=pattern)

零宽度正向预查,不捕获匹配结果。

'Windows (?=95|98|NT|2000)'

匹配 "Windows2000" 中的 "Windows"

不匹配 "Windows3.1" 中的 "Windows"。

(?!pattern)

零宽度负向预查,不捕获匹配结果。

'Windows (?!95|98|NT|2000)'

匹配 "Windows3.1" 中的 "Windows"

不匹配 "Windows2000" 中的 "Windows"。

(?<=pattern)

零宽度正向回查,不捕获匹配结果。

'2000 (?<=Office|Word|Excel)'

匹配 " Office2000" 中的 "2000"

不匹配 "Windows2000" 中的 "2000"。

(?<!pattern)

零宽度负向回查,不捕获匹配结果。

'2000 (?<!Office|Word|Excel)'

匹配 " Windows2000" 中的 "2000"

不匹配 " Office2000" 中的 "2000"。

 

 

c,使用向前查看和反向引用模拟原子组

.net,java,perl等支持的原子组的写法是(?>...),省略号表示任意正则表达式,它是一种具有特殊反转性的非捕获组。一旦原子组中存在一个正则表达式,该组的任何回溯位置都会被丢弃。

javascript中不支持原子组,但我们可以用向前查看来模拟原子组。等同原子组的语法(\1这个数字需要根据反向引用次数而改变) : (?=(pattern to make atomic))\1

向前查看在匹配的过程中不消耗任何字符作为全局匹配的一部分,而只是检查自己包含的正则符号在当前字符串位置是否匹配。

d,防止嵌套量词【例如:(x+)*】,因为内部量词和外部量词的排列组合产生了数量巨大的分支路径

e,确保存正则表达式的两个部分不能对字符串的相同部分进行匹配

 

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

a,正则表达式慢的原因通常是匹配失败的过程慢,而不是匹配成功的过程慢。

b,一个正则表达式的起始标记应当尽可能快速地测试并排除明显不匹配的位置。避免使用分支

c,尽量少使用分支(竖线|),通过使用字符集和选项组件来减少对分去的需求,或将分支在正则表达式上的位置推后。

示例

替换前              替换后

cat|bat          [cb]at

red|read        rea?d

red|raw         r(?:ed|aw)

(.|\r|\n)           [\s\S]

能匹配任意字符的字符集(如[\s\S],[\d\D],[\w\W],或者[\0-\uFFFF]实际上等同于(?:.|\r|\n|\u2028|\u2029),其中包括点匹配不到的回车、换行符、行分隔符、段分隔符

 

d,具体化,比如想表达[^"\s\n]*时不要使用.*?

e,使用非捕获组

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

g,暴露必需的字元

例如正则表达式/^(ab|cd)/暴露它的字符串起始锚,而/(^ab|^cd)/没有暴露它的锚^,IE无法应用同样的优化,最终无意义地搜索字符串并在每一个位置上匹配

h,使用合适的量词,特别是贪婪和惰性量词的匹配过程有较大区别

i,避免在循环中重复编译正则表达式

while(/regex1/.test(str1)){
/regex2/.exec(str2);
...
}



换成下面的方式

var regex1=/regex1/,regex2=/regex2/;
while(regex1.text(str1)){
regex2.exec(str2);
...
}



j,将复杂的正则表达式拆分为简单的片断

避免在一个正则表达式中处理太多任务。复杂的搜索问题需要条件逻辑,拆分成两个或多个正则表达式更容易解决。

k,何时不使用正则表达式。

例:/;$/.test(str);会检查每一个字符,直到搜索完整个字符串。此时应该用 str.charAt(str.length-1)==";";

字符串方法:charAt,slice,substr,substring,indexOf,lastIndexOf

 

 

 

 

 去除字符串首尾空白
 if(!String.prototype.trim){
  String.prototype.trim=function(){
    return this.replace(/^\s+/,"").replace(/\s+$/,"");//通过把/\s+$/替换成/\s\s*$/,在Firefox中可以提高性能35%
  }
}

或用正则去除首空白,用非正则去除尾空白,性能最优,代码如下:

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);
}



 

posted on 2012-02-10 23:42  LCM  阅读(514)  评论(0编辑  收藏  举报

导航