• PhoneGap源码分析6——正则表达式


      正则表达式的使用非常广泛,用法也比较灵活,不过平常遇到的都是正则表达式的简单用法,在这篇文章里,将尽量简单的梳理一下正则表达式,进而分析上一篇里面遗留的问题。

    一、正则表达式基础

    1、普通字符:字母、数字、下划线、汉字以及所有没有特殊意义的字符,如ABC123。在匹配时,匹配与之相同的字符。

    2、特殊字符:(需要时,使用反斜杠“\”进行转义)

    字符 含义 字符 含义 字符 含义 字符 含义
    \a 响铃符 = \x07 ^ 匹配字符串的开始位置 \b 匹配单词的开始或结束 {n} 匹配n次
    \f 换页符 = \x0C $ 匹配字符串的结束位置 \B 匹配不是单词开始和结束的位置 {n,} 匹配至少n次
    \n 换行符 = \x0A () 标记一个子表达式的开始和结束 \d 匹配数字 {n,m} 匹配n到m次
    \r 回车符 = \x0D [] 自定义字符组合匹配 \D 匹配任意不是数字的字符 [0-9] 匹配0到9中任意一个数字
    \t 制表符 = \x09 {} 修饰匹配次数的符号 \s 匹配任意空白字符 [f-m] 匹配f到m中任意一个字母
    \v 垂直制表符 = \x0B . 匹配除换行符外的字符 \S 匹配任意非空白字符    
    \e ESC符 = \x1B ? 匹配0或1次 \w 匹配字母或数字或下划线或汉字    
    \xXX 使用两位十六进制表示形式,可与该编号的字符匹配 + 匹配1或多次 \W 匹配任意不是字母、数字、下划线和汉字的字符    
    \uXXXX 用四位十六进制表示形式,可与该编号的字符匹配 * 匹配0或多次 [^x] 匹配除x外的所有字符    
    \x{XXXXXX} 使用任意位十六进制表示形式,可与该编号的字符匹配 | 左右两边表达式之间“或”关系 [^aeiou] 匹配除aeiou外的所有字符    

    上面列举的这些特殊字符,可以大致的分为: 

    (1)不便书写字符:如响铃符(\a)、换页符(\f)、换行符(\n)、回车符(\r)、制表符(\t)、ESC符(\e)

    (2)十六进制字符:如两位(\x02)、四位(\x012B)、任意位(\x{A34D1})

    (3)表示位置字符:如字符串开始(^)、字符串结束($)、单词开始和结束(\b)、单词中间(\B)

    (4)表示次数字符:如0或1次(?)、1或多次(+)、0或多次(*)、n次({n})、至少n次({n,})、n到m次({n,m})

    (5)修饰字符:如修饰次数({})、自定义组合匹配([])、子表达式(())

    (6)反义字符:

      (A)通过大小写反义:如\b和\B、\d和\D、\s和\S、\w和\W

      (B)通过[^]反义:如[^x]、[^aeiou]

      (C)其它特例:如\n和.也构成反义

    (7)范围字符:如数字范围([0-9])、字母范围([f-m])

    (8)逻辑字符:如表示或(|)

    3、转义

    (1)使用反斜杠“\”转义单个字符

    (2)使用“\Q...\E”转义,将表达式中间出现的字符全部作为普通字符

    (3)使用“\U...\E”转义,将表达式中间出现的字符全部作为普通字符,并且将小写字母转换成大写匹配

    (4)使用“\L...\E”转义,将表达式中间出现的字符全部作为普通字符,并且将大写字母转换为小写匹配

    4、贪婪模式与懒惰模式

      如果正则表达式中含有次数字符时,一般情况下,会尽可能匹配更多的字符,比如用l*n来匹配linjisong的话,会匹配linjison,而不是lin,这种模式也就是正则表达式的贪婪模式;相对应的,可以通过添加字符“?”来设置为懒惰模式,也即尽可能匹配更少字符。如*?表示重复0次或多次,但尽可能少重复。

    5、分组和反向引用

    (1)用小括号(())将表达式包含,可以使得表达式作为一个整体来处理,从而达到分组的目的。

    (2)默认情况下,每个分组会自动获取一个组号,按照左括号的顺序,从1向后编号。

    (3)引擎在处理时,会将小括号内部表达式匹配的内容保存下来,以方便在匹配过程中或匹配结束后进一步处理,可以使用反斜杠和组号来引用这个内容,如\1表示第一个分组匹配的文本。

    (4)也可以自定义组名,语法是(?<name>exp),这个时候反向引用时,还可以使用\k<name>。

    (5)也可以不保存匹配内容,也不分配组号,语法是(?:exp)。

    (6)小括号有一些其他特殊语法,这里列举几种,不再深入讨论:

    分类代码/语法说明
    捕获 (exp) 匹配exp,并捕获文本到自动命名的组里
    (?<name>exp) 匹配exp,并捕获文本到名称为name的组里,也可以写成(?'name'exp)
    (?:exp) 匹配exp,不捕获匹配的文本,也不给此分组分配组号
    零宽断言 (?=exp) 匹配exp前面的位置
    (?<=exp) 匹配exp后面的位置
    (?!exp) 匹配后面跟的不是exp的位置
    (?<!exp) 匹配前面不是exp的位置
    注释 (?#comment) 这种类型的分组不对正则表达式的处理产生任何影响,用于提供注释让人阅读

    到此,对于理解上一篇出现的正则表达式已经足够了,若想继续学习正则表达式的,可以参考正则表达式30分钟入门教程。下面再熟悉一下Javascript中的正则表达式实现。

    二、Javascript中的正则表达式

    1、创建正则表达式

    (1)使用字面量:语法 var exp = /pattern/flags;

    A、pattern是任何正则表达式

    B、flags有三种:g表示全局模式、i表示忽略大小写、m表示多行模式

    (2)使用RegExp内置构造函数:语法 var exp = new RegExp(pattern, flags);

    A、使用构造函数时,pattern和flags都是字符串形式,所以对于转义字符需要双重转义,例如:

    字面量 构造函数
    /\[bc\]at/ "\\[bc\\]at"
    /\.at/ "\\.at"
    /name\/age/ "name\\/age"
    /\d.\d{1,2}/ "\\d.\\d{1,2}"
    /\w\\helllo\\123/ "\\w\\\\hello\\\\123"

    说明:ECMAScript 3使用字面量时会共享一个RegExp实例,使用new RegExp(pattern,flags)会为每个正则表达式创建一个实例;ECMAScript 5规定每次都创建新实例。

    2、实例属性

    (1)global:布尔值,表示是否设置了g标志。

    (2)ignoreCase:布尔值,表示是否设置了i标志。

    (3)multiline:布尔值,表示是否设置了m标志。

    (4)lastIndex:整数,表示开始搜索下一次匹配项的字符位置,从0算起。

    (5)source:字符串,表示按照字面量形式创建的字符串模式,即便实例使用构造函数创建,存储的也是字面量形式的字符串模式。

    3、实例方法

    (1)exec方法

    A、一个参数,即要应用模式的字符串,返回第一个匹配项信息的数组,没有匹配时返回null。

    B、返回的数组是Array实例,但还额外保护input和index属性,分别表示应用正则表达式的字符串和匹配项在字符串中的位置。

    C、匹配时,在返回的数组中,第1项是与整个模式匹配的字符串,其他项是与模式中的分组匹配的字符串(如果没有分组,则返回数组只有1项)。

    D、对于exec(),即使设置了g,每次返回的也是一个匹配项,不同的是,设置了g,多次调用exec的开始搜索位置不同,没有设置g,每次都从开始搜索。

    2、test()方法

      接受一个字符串参数,匹配返回true,不匹配返回false。

    三、cordova/utils中vformat方法解析

      有了上面的基础,再来看vformat的源码,详细说明在代码注释中:

     1 utils.vformat = function(formatString, args) {
     2     if (formatString === null || formatString === undefined) return "";//源对象为null或undefined时,直接返回空字符串
     3     if (arguments.length == 1) return formatString.toString();//如果args未传入,直接返回源对象的字符串表示
     4     if (typeof formatString != "string") return formatString.toString();//源对象不是字符串时,直接返回字符串表示
     5     /*
     6     *    1.首先,这是一个正则表达式字面量,没有全局标志,也就是说只匹配第一次。
     7     *    2.其次,这里有三个分组,分别是(.*?)、(.)和(.*)
     8     *        2.1其中第二个分组最简单,匹配任意一个非换行符
     9     *        2.2第三个分组尽可能多(贪婪模式)的匹配任意多个(也可以是0个)非换行符
    10     *        2.3第一个分组尽可能少(懒惰模式)的匹配任意多个(也可以是0个)非换行符
    11     */
    12     var pattern = /(.*?)%(.)(.*)/;
    13     var rest    = formatString;
    14     var result  = [];//中间数组
    15 
    16     while (args.length) {//循环处理,每处理一次,args会减少一项,直至args.length为0,这里个人并不推荐这种用法,因为每次循环都要计算长度
    17         var arg   = args.shift();//移除数组的第一项,返回被移除的项,修改数组长度
    18     /*
    19     *    执行模式匹配,返回第一个匹配项信息的数组,无匹配则返回null
    20     *    match[0]为整个匹配字符串
    21     *    match[1]为第一个分组的匹配字符串,即%前面的字符
    22     *    match[2]为第二个分组的匹配字符串,即%后面的第一个字符
    23     *    match[3]为第三个分组的匹配字符串,即%后面的第二个字符开始到结束,也是下次循环要匹配的字符串
    24     */
    25         var match = pattern.exec(rest);
    26         if (!match) break;//无匹配,直接跳出循环
    27         rest = match[3];
    28         result.push(match[1]);
    29         if (match[2] == '%') {//当两个%%时,在结果中保留一个%,回复前一个参数数组状态,并继续下面的匹配
    30             result.push('%');
    31             args.unshift(arg);//将参数数组还原
    32             continue;
    33         }
    34         result.push(formatted(arg, match[2]));//按%后面的第一个字符,将参数格式化
    35     }
    36     result.push(rest);//向中间数组压入其余未匹配内容
    37     return result.join('');//将中间数组转换为字符串返回
    38 };

    补充一点,第一个分组为什么要使用懒惰模式呢?假设不使用懒惰模式,遇到...%j...%o...,第一次循环就会就会匹配到后面的%o了,可能有人会问,不是没有设置g标志,只匹配第1次出现的吗?没有设置g,只匹配第1次出现是没错,但是如果不是懒惰模式(.*)可以一直匹配到...%j...,然后%o就是第1次的匹配项了。
    cordova.utils中的format和vformat有什么用呢?从代码可以看出,这实际上就类似于C中的printf函数,这个也被吸收到JDK1.5中了,见System.out.printf(format, args)。

    拣尽寒枝不肯栖,寂寞沙洲冷。
    郴江幸自绕郴山,为谁流下潇湘去?
    欲将心事付瑶琴,知音少,弦断有谁听?
    倩何人,唤取红巾翠袖,揾英雄泪!
    零落成泥碾作尘,只有香如故!
  • 相关阅读:
    权限设计
    ts infer关键字
    Array初始化 以及 Array.prototype.map()的一些问题
    同步、异步、事件循环
    Spring学习笔记(一)
    【面试】关于get和post两种方法的不同。
    【算法】背包问题
    当你在浏览器输入一个网址(如http://www.taobao.com),按回车之后发生了什么?
    数据库语句复习笔记
    【算法】雀魂启动(笔试题)
  • 原文地址:https://www.cnblogs.com/linjisong/p/2630607.html
Copyright © 2020-2023  润新知