JS正则表达式总结

41次阅读

共计 4619 个字符,预计需要花费 12 分钟才能阅读完成。

const reg =/(((^https?:(?:\/\/)?)(?:[-;:&=\+\$,\w]+@)?[A-Za-z0-9.-]+(?::\d+)?|(?:www.|[-;:&=\+\$,\w]+@)[A-Za-z0-9.-]+)((?:\/[\+~%\/.\w-_]*)?\??(?:[-\+=&;%@.\w_]*)#?(?:[\w]*))?)$/;

大家看到这段正则时这脑瓜子~ 的吧,对于正则,大都数的程序员都是度娘一波。下次遇到继续度一遍,但作为一门使用范围很广的技术,我认为深入理解一下是很有必要的。

正则表达式,又称规则表达式。(英语:Regular Expression,在代码中常简写为 regex、regexp 或 RE)
正则表达式通常被用来检索、替换那些符合某个模式 (规则) 的文本。
正则表达式的 基本组成元素 可以分为:字符 元字符 ,字符很好理解,就是基础的计算机字符编码,通常正则表达式里面使用的就是数字、英文字母。而元字符,也被称为特殊字符,是一些用来表示特殊语义的字符。如转义字符(让其后续的字符失去其本来的含义/*/)^ 表示非,| 表示或等。利用这些元字符,才能构造出强大的表达式模式(pattern)
语法

var patt=new RegExp(pattern,modifiers);
或者更简单的方式:
var patt=/pattern/modifiers;

pattern:(模式)描述了表达式的模式
modifiers:修饰符

i(ignore): 执行对大小写不敏感的匹配。g(global): 执行全局匹配
m(multiple line):执行多行匹配
单个字符

最简单的正则表达式可以由简单的数字和字母组成,没有特殊的语义,纯粹就是一一对应的关系。如想在 ’apple’ 这个单词里找到‘a’ 这个字符,就直接 ’apple’.search(/a/)

但是如果想要匹配特殊字符的话,就得请出我们第一个元字符 **\**,它是转义字符字符,顾名思义,就是让其后续的字符失去其本来的含义。举个例子:

我想匹配 * 这个符号,由于 * 这个符号本身是个特殊字符,所以我要利用转义元字符 \ 来让它失去其本来的含义:/\*/,

如果本来这个字符不是特殊字符,使用转义符号就会让它拥有特殊的含义。我们常常需要匹配一些特殊字符,比如空格,制表符,回车,换行等, 而这些就需要我们使用转义字符来匹配。为了便于记忆,我整理了下面这个表格,并附上记忆方式:

特殊字符      正则表达式        记忆方式
换行符         \n              new line
换页符         \f              form feed
回车符         \r              return
空白符         \s              space
制表符         \t              tab
垂直制表符     \v              vertical tab
回退符         [\b]           backspace, 之所以使用 [] 符号是避免和 \b 重复
多个字符

单个字符的映射关系是一对一的,即正则表达式的被用来筛选匹配的字符只有一个。而这显然是不够的,只要引入集合区间和通配符的方式就可以实现一对多的匹配了。

在正则表达式里,集合的定义方式是使用 中括号 [] 来构建一个简单的类。如 /[123]/ 这个正则就能同时匹配 1,2,3 三个字符。那如果我想匹配所有的数字怎么办呢?从 0 写到 9 显然太过低效,所以元字符 - 就可以用来表示区间范围,利用 /[0-9]/ 就能匹配所有的数字,/[a-z]/则可以匹配所有的英文小写字母。

即便有了集合和区间的定义方式,如果要同时匹配多个字符也还是要一一列举,这是低效的。所以在正则表达式里衍生了一批用来同时匹配多个字符的简便正则表达式:

##### 边界
上面我们把字符的匹配都介绍完了,接着我们还需要位置边界的匹配。在长文本字符串查找过程中,我们常常需要限制查询的位置。比如我只想在单词的开头结尾查找。
单词是构成句子和文章的基本单位,一个常见的使用场景是把文章或句子中的特定单词找出来。如:

This is all I have. 

我想找到 is 这个单词,但是如果只是使用 /is/ 这个正则,就会同时匹配到 is 和 is 这两处文本。这时候我们就需要使用边界正则表达式 b,其中 b 是 boundary 的首字母。在正则引擎里它其实匹配的是能构成单词的字符 (w) 和不能构成单词的字符 (W) 中间的那个位置。

上面的例子改写成 /\bis\b/ 这样就能匹配到 is 这个单词了。

/\Bis\b/
字符串边界

匹配完单词,我们再来看一下一整个字符串的边界怎么匹配。

'@123@123@'.replace(/@./g,'L') 
'@123@123@'.replace(/^@./g,'L') 
'@123@123@'.replace(/.@$/g,'L')
'@123@123@'.replace(/^@123@123@$/g,'L')


但是呢这里有个技巧,

 const eg='@123' + '\n' + '@456' + '\n' +'@789'+ '\n'
 eg.replace(/^@\d/g,'Q')
量词

一对一和一对多的字符匹配都讲完。接下来,就该介绍如何同时匹配多个字符。要实现多个字符的匹配我们只要多次循环,重复使用我们的之前的正则规则就可以了。那么根据循环次数的多与少,我们可以分为 0 次,1 次,多次{},特定次。
循环 20 个数字

\d\d\d\d\d\d\d\d\....

\d{20}\w\d?\w+\d*\w{3,5}\d{3,}
贪婪和非贪婪模式

了解了量词后,
贪婪:(默认的)

\d{3,6}
'12345678'.replace(/\d{3,6}/g,'x')

非贪婪:让正则表达式尽可能少的匹配,一旦匹配成功就不再继续尝试

 \d{3,6}?
'12345678'.replace(/\d{3,6}?/g,'x')



分组

我们现在想一下这样的场景,匹配一个 apple 三次,我们有木有会想到

apple{3}

这里我们使用()达到分组的功能,使量词作用于分组

(apple){3}
eg:
'a1b2c3d4'.replace(/[a-z]\d{3}/,'x') \w
反向引用
1994-07-07 ==>  07/07/1994
match: 
'1994-07-07'.replace(/\d{4}-\d{2}-\d{2}/g,'07/07/1994')
显然是无法满足我们的需求的,现在我们将引入 $
'1994-07-07'.replace(/\d{4}-\d{2}-\d{2}/g,'$1')
'1994-07-07'.replace(/(\d{4})-(\d{2})-(\d{2})/g,'$1')
'1994-07-07'.replace(/(\d{4})-(\d{2})-(\d{2})/g,'$2/$3/$1')
忽略分组
'1994-07-07'.replace(/(?:\d{4})-(\d{2})-(\d{2})/g,'$1')

正则表达式从文本头部开始向尾部开始解析,文本尾部方向,称为“前”
前瞻 : 前瞻就是正则表达式匹配到规则时,向前检查是否符合 断言
后顾 : 后顾就是正则表达式匹配到规则时,向后检查是否符合 断言

前向查找

前向查找 (lookahead) 是用来限制后缀的。例如 happy happily 这两个单词,我想获得以 happ 开头的副词,那么就可以使用 happ(?=ily)正向匹配。如果我想过滤所有以 happ 开头的副词,那么也可以采用负向匹配查找

'happy happily'.replace(/happ(?=ily)/g,'X')
'happy happily'.replace(/happ(?!ily)/g,'X')
happ 规则部分 (?=ily) 断言

后向查找

介绍完前向查找,接着我们再来介绍一下它的反向操作:后向查找(lookbehind)。举个简单的例子:apple 和 people 都包含 ple 这个后缀,那么如果我只想找到 apple 的 ple,该怎么做呢?我们可以通过限制 app 这个前缀,就能唯一确定 ple 这个单词了。

'apple people'.replace(/(?<=ap)ple/,'X')

其中 (?<=assert) 的语法就是我们这里要介绍的后向查找。regex 指代的子表达式会作为限制项进行匹配,匹配到这个子表达式后,就会继续向后查找。另外一种限制匹配是利用(?<!assert) 语法,这里称为负后向查找。与正前向查找不同的是,被指定的子表达式不能被匹配到。于是,在上面的例子中,如果想要查找 apple 的 ple 也可以这么写成

'apple people'.replace(/(?<!ap)ple/g,'X')

需要注意的,不是每种正则实现都支持后向查找。在 javascript 中是不支持的
从 es2018 之后,chrome 中的正则表达式也支持反向查找了
最后回顾一下这部分内容:

逻辑处理

与或非。
与:默认的正则规则都是 的关系所以这里不讨论。
非:而 关系,分为两种情况:一种是字符匹配,另一种是子表达式匹配。在字符匹配的时候,需要使用 ^ 这个元字符。在这里要着重记忆一下:只有在 [] 内部使用的 ^ 才表示非的关系 。子表达式匹配的非关系就要用到前面介绍的前向负查找子表达式(?!assert) 或后向负查找子表达式 (?<!assert)
或:或关系,通常给子表达式进行归类使用。比如,我同时匹配 a,b 两种情况就可以使用 (a|b) 这样的子表达式。

eg: Byron|Casper
  'ByronCasper'.replace(/Byron|Casper/g,'X')
  'ByronsperbyrCasper'.replace(/Byr(on|Ca)sper/gi,'X')
对象的属性和方法

 属性:const reg1 =/\w/
    const reg2 =/\w/gim
    reg1.sorce

方法:

一:RegExp.prototype.test(str) // true or false

reg1.test('a')
reg2.test('ab') //lastIndex
while(reg2.test('ab')){console.log('lastIndex',reg2.lastIndex)
}

二:RegExp.prototype.exec(str) //
得到更多的东西,匹配不到返回 null

  index: 声明匹配文本的第一个字符的位置
  input: 存放被检索的字符串 string
  
  const ts='$1aw2bb3cy4dd5ee'
  const reg3=/\d(\w)(\w)\d/
  const reg4=/\d(\w)(\w)\d/g
  const rt=reg3.exec(ts)
  console.log(reg3.lastIndex+'\t'+rt.index+'\t'+rt)
  while(rt2=reg4.exec(ts)){console.log(reg4.lastIndex+'\t'+rt2.input+'\t'+rt2.index+'\t'+rt2)
  }

字符串:
三:

  
    string.prototype.search(Reg)  // 忽略 g 搜不到 -1
    string.prototype.match(Reg) // 返回数组
    // 字符串分割为字符数组
    string.prototype.split(Reg)
   'a,b,c,d'.split(',')
   'a1b2c3d'.split(/\d/g)
    string.prototype.replace(Reg)
   

趣用:小问题
1. 去掉字符串左右两边的空格,” zhang san ” –>“zhang san”
2. 数字格式化问题,1234567890 –> 1,234,567,890

正文完
 0