【JS基础】正则表达式入门

编程故事 2019-06-26

正则表达式的语法

普通字符

字母、数字、汉字、下划线以及一些没有特殊定义的标点符号,都属于普通字符,正则表达式中的普通字符匹配字符本身,如:

var str = "abced"
console.log(str.match(/a/))  // ['a',index:0,input:'abced']

[]字符

[]可以包含一系列字符,能够匹配其中的任意一个字符,如[abc]匹配abc中的任意一个字符,使用[^abc]包含的字符能够匹配除了abc以外的任何一个字符。比如以下含义:

  1. [abc]:匹配abc中的任意一个字符
  2. [^abc]:匹配出了abc的任意一个字符
  3. [0-9]:匹配0-9中的任意一个数字
var str = 'abcde'
console.log(str.match(/[bcd][bcd]/))   // ['bc',index:1,input:'abcde']

元字符

  • .:匹配除换行符意外的任意一个字符
  • \w:匹配任意一个字母或数组或下划线,[A-Za-z0-9_]
  • \W:匹配除w以外的任意一个字符
  • \d:匹配一个数字
  • \D:匹配一个非数字字符
  • \s:匹配任意空白字符
  • \S:匹配任何非空白字符
  • $:匹配字符串的开始位置
  • ^:匹配字符串的结束位置
  • \b:匹配一个单词边界,也就是单词和空格间的位置,比如'erb'可以匹配never中的‘er’,但是不能匹配verb中的‘er’
  • \B:匹配非单词边界,比如'erb'可以匹配verb中的‘er’,但是不能匹配never中的‘er’
  • \n:匹配换行符
  • \f:匹配换页符
  • \r:匹配回车符
  • \t:匹配制表符

如果需要匹配特殊字符本身,需要在特殊字符前加字符进行转义,比如需要表示.字符需要'\.'。

量词

  • n+:匹配一个或多个n字符
  • n*:匹配零个或多个字符
  • n?:匹配零个或一个字符
  • n{x}:匹配x个n
  • n{x,}:匹配x个或更多个n
  • n{x,y}:匹配x-y个n字符
  • ?=n:匹配之后紧接着n的位置
  • ?!n:匹配之后没有紧接着n的位置
  • |:左右表达式之间或关系,匹配左边或者右边
  • ():在被修饰匹配次数的时候,()中的表达式可以作为整体被修饰,去匹配结果的时候,()中表达式匹配到的内容可以被单独得到。

贪婪模式与非贪婪模式

在正则表达式中,贪婪与非贪婪模式的区别主要是:被量词修饰的字表达式的匹配行为的不同,贪婪模式在整个表达式匹配成功的情况下尽可能多的匹配,非贪婪模式是在整个表达式匹配成功的前提下,尽可能少的匹配。

常见的修饰贪婪模式的量词如下:{x,y},{x,},?,,+。非贪婪模式就是在贪婪模式的量词后面加上一个?字符,就可以变成非贪婪模式的量词,比如:{x,y}?,{x,}?,??,?,+?。

var str = "longen<p>举杯邀明月、</p>yunxi<p>对影成三人</p>boyboy";
// 贪婪模式
console.log(str.match(/<p>.*<\/p>/));
// ['<p>举杯邀明月、</p>yunxi<p>对影成三人</p>',index:9,input:'...']

// 非贪婪模式
console.log(str.match(/<>.*?<\/p>/));
// ['<p>对影成三人</p>',index:9,input:'...']

正则表达式的匹配原理

在正则表达式匹配的过程中,如果子表达式匹配到的是字符内容,而非位置的话,并且被保存在匹配的结果中,那么就认为该子表达式是占有字符的,如果子表达式匹配的仅仅是位置,或者说匹配的内容不保存到匹配的结果中,那么该子表达式是零宽度的,比如环视。

正则表达式的匹配过程

【JS基础】正则表达式入门

比如上面的正则表达式/abc/,首先a取得控制权,从位置0开始匹配,a匹配成功,接着往下匹配,把控制权交给b,从位置1开始匹配,直到整个正则表达式匹配完成。对于含有量词?正则表达式/ab?c/,量词?是贪婪模式,会优先选择尽可能多的匹配修饰的字符,所以该表达式会优先选择匹配一个b字符,当b匹配不到时再选择不匹配b字符。对于含有量词的??的正则表达式,量词??是非贪婪模式,会优先选择尽可能少的匹配修饰的字符,所以该表达式会优先不匹配b字符,比如对于字符串abc,a先匹配,控制权交给b??,该表达式先不匹配字符b,控制权交给c,c不能匹配abc中的b,匹配失败进行回溯,由b??表达式匹配字符b,再把控制权交给c,匹配成功,因此整个表达式都匹配成功。

环视

环视是只进行子表达式的匹配,不占有字符,匹配的内容不保存到最终的匹配结果,是零宽度的,它匹配的结果就是一个位置,环视的作用相当于对所在的位置加一个附加条件,只有满足了这个条件,环视表达式才能匹配成功,环视有顺序和逆序两种,顺序和逆序又分为肯定和否定,但是在JavaScript中只支持顺序环视,下面就是顺序环视的匹配过程:
【JS基础】正则表达式入门
(?=Expression)是顺序肯定环视,含义是在位置的右侧有expression。(?!Expression)是顺序否定环视,含义是在位置的右侧没有expression。如下图所示:该正则表达式的含义是:以字母或者数字组成的,并且第一个字符必须为小写字母开头。其匹配过程如下:

首先元字符^取得控制权,匹配字符串开始位置,接着将控制权交给顺序肯定环视表达式(?=[a-z]),它的含义是在位置0的右侧是有a-z的小写字母,在这里匹配成功,将控制权交给[a-z0-9]+表达式,由于(?=[a-z])是零宽度的,字符串已匹配的位置不会增加,因此对于[a-z0-9]+其匹配的起始位置仍然为0。

对于顺序否定环视,是根据其顺序肯定环视的匹配结果来推断的,当顺序肯定环视匹配成功,顺序否定就失败,顺序肯定匹配失败,顺序否定就成功。比如对于正则表达式/<(?!/?p\b)[^>]+>/,其含义是匹配所有非<p>的标签,首先顺序否定环视(?!/?p\b)要转换成(?=/?p\b),当(?=/pb)匹配失败时,子表达式(?!/?p\b)才匹配成功。

捕获组

捕获组就是把正则表达式中用()包裹的子表达式匹配的内容,保存到内存中以数字编号或显示命名的组里,方便以后使用,捕获组的编号规则,编号是按照"("出现的顺序,从左到右,从1开始进行编号,比如如下代码:

console.log(/(longen)(123)/.test("longen123longen"))
console.log(RegExp.$1,RegExp.$2)    // longen 123

// 使用replace替换,将分组内容替换
var num = "11 22"
var n = num.replace(/(\d+)\s*(\d+)/,"$1 $2")
console.log(n)    // 22 11

非捕获性分组:子表达式可以作为被整体修饰但是子表达式匹配的结果不会被存储,要创建一个非捕获性分组,只需要在“(”字符的后面加上“?:”就可以了,比如:(?:Expression)。

反向引用

捕获性分组取到的内容,不仅可以在正则表达式外部通过程序进行引用,也可以在正则表达式内部进行引用,这种引用方式叫做反向引用。捕获性分组的反向引用的写法如:number。

反向引用的匹配原理:捕获分组在匹配成功以后,会将子表达式匹配到的内容,保存到内存中一个以数字编号的组里,可以简单的认为是对一个局部变量进行了赋值,这时可以通过反向引用,引用这个局部变量的值,一个捕获分组在匹配成功之前,它的内容是不确定的,一旦匹配成功,它的内容就确定了,反向引用的内容也就确定了。比如以下内容:

var str = "longaabbcd"
console.log(str.match(/([ab])\1/))  ['aa',...]

对于表达式([ab]),可以匹配a,也可以匹配b,但是如果匹配成功,它的反向引用也就确定,如果捕获分组匹配到的是a,那么反向引用也就只能匹配a,也就是说([ab])1只能匹配aa或者bb。

相关推荐