Skip to content

Latest commit

 

History

History
208 lines (129 loc) · 7.81 KB

zi_fu_chuan_he_zheng_ze_biao_da_shi.md

File metadata and controls

208 lines (129 loc) · 7.81 KB

字符串和正则表达式

此章正则表达式还在处理当中,有些缺少和混乱,可以先不阅读

##1. 字符串

本章的字符串链接建议,对IE8及以前性能可能不增反退.

str += "one" + "two"

字符串链接顺序及原理

  1. 在内存中创建一个临时字符串
  2. 连接后的"onetwo"被赋值给该临时字符串
  3. 临时字符串与str当前的值连接
  4. 结果付给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 正则表达式原理

  1. 编译

    创建一个正则表达式对象(使用正则直接量/RegExp构造函数),浏览器会验证表达式,然后转化为原生的代码程序,用于执行匹配.把正则对象赋值给变量,可以避免重复执行这一步骤.

  2. 设置起始搜索位置

    起始搜索未知是字符串的起始字符/正则表达式的lastIndex属性(lastIIndex只作为exec和test方法起始搜索未知,并要求在/g表示时),当匹配失败(步骤4),搜索起始位置改为最后一次匹配的起始位置的下一个字符.

  3. 匹配每个正则表达式字元

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

  4. 匹配成功/失败

    如果在字符串当前未知发现一个完全匹配,则匹配成功,如果所有路径都无法匹配,正则回退到第二步,从下一个起始搜索位置重新开始,当最后一个字符串后面的位置都经历了这个过程,还无匹配,正则表达式宣布彻底匹配失败.

###2.2 理解回溯

正则匹配时,会从左到右测试表达式的组成部分,看能否找到匹配项.当遇到量词和分支时,需要决策下一步如何处理.(类型[a-z]的字符串和类似\s或的速记字符集在处理过程中允许差异,所以没用到回溯,不会遇到相同的性能问题)

如果遇到量词(*,+?或{2,}),正则表达式需要觉得何时尝试更多字符.

如果遇到分支(来自|操作符),那么必须从可选项中选择一个尝试匹配.

每当正则表达式做类似决定时,如果有必要,都会记录其他选择,以备返回时使用,如果当前项匹配成功,正则继续扫描表达式,如果其他部分也匹配成功,匹配结束,如果当前选项都匹配失败,则回溯到最后一个决策点,再进行表达式匹配,如果正则表达式中所有量词和分支的所有排列组合都尝试失败,则放弃匹配,搜索开始符移到下一个字符.重复此过程.

  1. 分支与回溯

    在一个开始搜索符,从左到右,若前面的分支匹配失败,后一个分支从同一个开始搜索符开始匹配.

  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>'

    惰性和贪婪的扫描范围一样的,但是贪婪的效率要高.

    因为一开始就贪婪到了最后一个字符,然后开始回溯.

    而惰性,就从一开始一个一个慢慢回溯到最后,效率较低.

  3. 回溯失控和防止回溯失控.

    下面的正则,用来匹配整个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字符串失败时所需时间同字符串长度成线性关系.

  4. 使用预查和反向引用模拟原子组

    <html>(?=([\s\S]*?<head>))\1(?=([\s\S]*?<title>))\2....

    ?=exp : 匹配exp前面的位置.

    这里比回溯失控时多了(?=..)\n,?=主要保存匹配到标签前面的未知,然后\n定义组号,能保证后妈及时回溯失败,也不能使我之前的分组反复执行.

    现在,如果字符串缺少结尾的</html>,那么最后一个[\s\S]*?会展开至字符串末尾,然后正则宣布失败,因为没有可返回的回溯点,

##总结

  1. 当连接数量很大,数组项合是IE7及更早的唯一优化方法
  2. 如果不考虑IE7及更早,推荐使用+ or +=