此章正则表达式还在处理当中,有些缺少和混乱,可以先不阅读
##1. 字符串
本章的字符串链接建议,对IE8及以前性能可能不增反退.
str += "one" + "two"
字符串链接顺序及原理
- 在内存中创建一个临时字符串
- 连接后的"onetwo"被赋值给该临时字符串
- 临时字符串与str当前的值连接
- 结果付给str
优化
str += "one";
str += "two";
or
str =str + "one" +"two";
//str = "one" + str + "two"; 这样优化无作用.
这两种都可以避免临时字符串的创建.
为什么str = "one" + str + "two";
这样的优化会失效呢?
因为除了IE外,其他浏览器都会给最左边的字符串分配更多的内存空间,这样后面字符串要加进来,就可以减少内存的扩增时间.
如果把one
放在第一个,str
一般比它要长的多,所以还是要分配额外的内存空间,时间操作上和新增临时字符串无区别.
IE8实现字符串链接,只是记录现在字符串的引用,当你要用它的时候,才把他们连接起来.,代替之前的记录的字符串引用.
IE7更加糟糕.每连接一对字符串都要把他复制
到一个新内存中.
###1.1 Firefox的编译期合并.
Firefox在赋值便打算中所有要链接的字符串都属于编译器常量.
function folding_demo(){
var str = "compile" + "time" + "folding";
str += "this" + "work" + "too";
str = str +"but" +"not" + "this"
}
//同效
function folding_demo(){
var str ="compiletimefolding";
str += "thisworktoo";
str = str +"but" +"not" + "this"
}
在复制语句中含有..变量时...他就歇菜了,,所以很多时候其实这种优化不起什么作用.
YUI Compressor
代码压缩也会做这样的优化.
###1.2 数组项合并
大部分浏览器的数组项合并比其他字符串连接方法要慢.
IE7除外
var str = "I'm a hansome man,ye!",
newStr ="",
appends = 5000;
while(appends--){
newStr+=str;
}
var sts = [];
while(appends--){
strs[strs.length] =str;
}
//数组内字符串连接
newStr = strs.join("");
IE7的话,后者性能提升几百倍左右..因为IE7避免了地址了重复分配.所以快,但是其他浏览器是前者更优.
###1.3 String.prototype.concat
concat
能连接任意字符串/数组/对象toString()
.
但是效率在各个浏览器都比较慢,建议不要用.
##2 正则表达式
###2.1 正则表达式原理
-
编译
创建一个正则表达式对象(使用正则直接量/RegExp构造函数),浏览器会验证表达式,然后转化为原生的代码程序,用于执行匹配.
把正则对象赋值给变量,可以避免重复执行这一步骤
. -
设置起始搜索位置
起始搜索未知是字符串的起始字符/正则表达式的lastIndex属性(lastIIndex只作为exec和test方法起始搜索未知,并要求在/g表示时),当匹配失败(步骤4),搜索起始位置改为最后一次匹配的起始位置的下一个字符.
-
匹配每个正则表达式字元
确定起始搜索位置后,开始逐个检查文本和正则表达式模式,当一个特定的字元匹配失败后,正则表达式会回溯到之前尝试匹配的位置,尝试其他可能的路径
-
匹配成功/失败
如果在字符串当前未知发现一个完全匹配,则匹配成功,如果所有路径都无法匹配,正则回退到第二步,从下一个起始搜索位置重新开始,当最后一个字符串后面的位置都经历了这个过程,还无匹配,正则表达式宣布彻底匹配失败.
###2.2 理解回溯
正则匹配时,会从左到右测试表达式的组成部分,看能否找到匹配项.当遇到量词和分支时,需要决策下一步如何处理.(类型[a-z]的字符串和类似\s或点
的速记字符集在处理过程中允许差异,所以没用到回溯,不会遇到相同的性能问题)
如果遇到量词(*,+?或{2,}),正则表达式需要觉得何时尝试更多字符.
如果遇到分支(来自|操作符),那么必须从可选项中选择一个尝试匹配.
每当正则表达式做类似决定时,如果有必要,都会记录其他选择,以备返回时使用,如果当前项匹配成功,正则继续扫描表达式,如果其他部分也匹配成功,匹配结束,如果当前选项都匹配失败,则回溯到最后一个决策点,再进行表达式匹配,如果正则表达式中所有量词和分支的所有排列组合都尝试失败,则放弃匹配,搜索开始符移到下一个字符.重复此过程.
-
分支与回溯
在一个开始搜索符,从左到右,若前面的分支匹配失败,后一个分支从同一个开始搜索符开始匹配.
-
贪婪和惰性的回溯
var str = "<p>Para 1.</p>"+ "<p>Para 2.</p>"+ "<div>DIV.</div>" /<p>.*<\/p>/i.test(str);
.
代表能匹配除换行符的任意字符,*
代表贪婪搜索,表示可以重复0~多次.所以这个正则在一开始就匹配了所有字符,但是还要匹配
<\/p>
,所以要开始要回溯.直到匹配到第二行的
</p>
表示该字符串符合正则,扫描范围为整个str
.这明显不是我们想要的结果./<p>.*?<\/p>/i.test(str);
*?
代表惰性模式,当字符串匹配.*?
的时候,他不会继续贪婪
的匹配字符,而是先跳过自己,让后面的<\/p>
先匹配.当后下后面的匹配失败后,再继续
.*?
的搜索.在这个例子里,正则扫描完第一段,就能证明
str
匹配.当如果,只有一个段落,例如
str = '<p>Para 1.</p>'
惰性和贪婪的扫描范围一样的,但是贪婪的效率要高.
因为一开始就贪婪到了最后一个字符,然后开始回溯.
而惰性,就从一开始一个一个慢慢回溯到最后,效率较低.
-
回溯失控和防止回溯失控.
下面的正则,用来匹配整个
HTML
文件/<html>[\s\S]*?<head>[\s\S]*?<title>[\s\S]*?<\/title>[\s\S]*?<\/head>[/s/S]*?<body>[\s\S]*?<\/body>[\s\S]*?<\/html>
以上匹配标签齐全的
HTML
运行正常.当比如缺少最后的
</html>
时最后一个
[/s/S]*?
将扩展到字符串的末尾,但没匹配到<\/html>
.所以回溯到倒数第二个
[/s/S]*?
,用它匹配到的</body>
标签,继续查找第二个</body>
,直到字符串末尾.倒数第二个[/s/S]*?
失败.然后再回溯到第三个
[/s/S]*?
,以此类推,直到所有[/s/S]*?
失败.防止回溯失控:具体化
如何具体化?.就是防止当后面的
[/s/S]*?
失败时,又反回来继续重复找本层的标签.<html>(?:(?!<head>))*<head>(?:(?!<title>))[\s\S]*<title>...
?!exp
: 匹配后面跟的不是exp的位置?:exp
: 匹配exp,不能捕获匹配的文本,也不给此分组分配组号.参考http://deerchao.net/tutorials/regex/regex.htm这里还把之前的
*?
换成了*
,换成了贪婪模式.但是这样允许正则表达式匹配不完整的HTML字符串失败时所需时间同字符串长度成线性关系.
-
使用预查和反向引用模拟原子组
<html>(?=([\s\S]*?<head>))\1(?=([\s\S]*?<title>))\2....
?=exp
: 匹配exp前面的位置.这里比回溯失控时多了
(?=..)\n
,?=
主要保存匹配到标签前面的未知,然后\n
定义组号,能保证后妈及时回溯失败,也不能使我之前的分组反复执行.现在,如果字符串缺少结尾的
</html>
,那么最后一个[\s\S]*?
会展开至字符串末尾,然后正则宣布失败,因为没有可返回的回溯点,
##总结
- 当连接数量很大,数组项合是IE7及更早的唯一优化方法
- 如果不考虑IE7及更早,推荐使用
+
or+=