[JS]正则精简教程

554 阅读4分钟

字符

简单字符

简单字符指的是数字、字母、标点等,如1,2,3,a,b,c,用来代表一个字符

/a/ 表示匹配字母a

需要注意的是有些符号在正则中有特殊意义,已经不能算是简单字符了,比如 * 表示重复0次或多次,这时候如果我们想匹配 *就需要使用转义

/\*/  表示匹配字符*
/\\\n/ 表示匹配字符串\n

元字符

正则表达式规定的一些特殊代码,多用来代表一类字符

/\w/ 表示匹配字母或数字或下划线或汉字
字符 描述
. 匹配除换行符以外的任意字符
\w 匹配字母或数字或下划线或汉字
\s 匹配任意的空白符
\d 匹配数字
\b 匹配单词的开始或结束
^ 匹配字符串的开始
$ 匹配字符串的结束
\f 匹配一个换页符
\n 匹配一个换行符
\r 匹配一个回车符
\t 匹配一个制表符
\v 匹配一个垂直制表符

区间

字符 描述
[0-9] 匹配 0-9 之间的数字
[A-Z] 匹配 A-Z 之间的字母
[157] 匹配 1 5 7

重复

上面讲得都是一个字符,这里我们介绍一下如何重复一个字符

\b\w{6}\b  表示匹配刚好6个字符的单词
字符 描述
* 重复零次或更多次
+ 重复一次或更多次
? 重复零次或一次
{n} 重复n次
{n,} 重复n次或更多次
{n,m} 重复n到m次

反义

有时需要查找不属于某个能简单定义的字符类的字符。这时需要用到反义

字符 描述
\W 匹配非字母、数字、下划线
\S 匹配任何非空白字符
\D 匹配一个非数字字符
\B 匹配不是单词开头或结束的位置
[^x] 匹配除了x以外的任意字符
[^aeiou] 匹配除了aeiou这几个字母以外的任意字符

分支条件

其实就是用|把不同的规则分隔开

举例:\d{5}-\d{4}|\d{5}这个表达式会匹配用连字号间隔的9位数字或者5位数字

下面介绍高级用法

分组

()括起来的就是一个分组

举例 var reg=/(\w)-(\d)/

(\w)就是分组1

(\d)就是分组2

捕获与反捕获

我们可以通过\1 \2来引用搜索分组匹配的文本

\b(\w+)\b\s+\1\b  可以匹配hi hi, 或者 nice nice

如果我们不需要捕获某个分组,则可以用(?:)

var reg=/(?:\w)-(\d)/  表示不捕获(?:\w),那么(\d)就是分组1

前向查找

前向查找是用来限制后缀的

字符 描述
(?=exp) 匹配exp前面的位置
(?!exp) 匹配后面跟的不是exp的位置

举例: \b\w+(?=ing\b),匹配以ing结尾的单词的前面部分(除了ing以外的部分),如查找I'm singing while you're dancing.时,它会匹配sing和danc

\d{3}(?!\d)匹配三位数字,而且这三位数字的后面不能是数字

后向查找

后向查找与前向查找相反,是用来限制前面的

字符 描述
(?<=exp) 匹配exp后面的位置
(?<!exp) 匹配前面不是exp的位置

举例: (?<=\bre)\w+\b会匹配以re开头的单词的后半部分(除了re以外的部分),例如在查找reading a book时,它匹配ading

(?<![a-z])\d{7}匹配前面不是小写字母的七位数字

贪婪与懒惰

正则表达式通常匹配尽可能多的字符

举例:a.*b 将会匹配acbab整个字符串

有时,我们更需要匹配尽可能少的字符, 只需要加上一个问号?

举例:a.*?b,如果把它应用于acbab的话,将会匹配acb

字符 描述
*? 重复任意次,但尽可能少重复
+? 重复1次或更多次,但尽可能少重复
?? 重复0次或1次,但尽可能少重复
{n,m}? 重复n到m次,但尽可能少重复
{n,}? 重复n次以上,但尽可能少重复

具名组匹配

ES2018 引入了具名组匹配(Named Capture Groups),允许为每一个组匹配指定一个名字

const RE_DATE = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;

const matchObj = RE_DATE.exec('1999-12-31');
const year = matchObj.groups.year; // 1999
const month = matchObj.groups.month; // 12
const day = matchObj.groups.day; // 31

解构赋值写法

let {groups: {one, two}} = /^(?<one>.*):(?<two>.*)$/u.exec('foo:bar');
one  // foo
two  // bar

字符串替换时,使用$<组名>引用具名组

let re = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/u;

'2015-01-02'.replace(re, '$<day>/$<month>/$<year>')
// '02/01/2015'

引用

如果要在正则表达式内部引用某个“具名组匹配”,可以使用\k<组名>的写法

const RE_TWICE = /^(?<word>[a-z]+)!\k<word>$/;
RE_TWICE.test('abc!abc') // true
RE_TWICE.test('abc!ab') // false

运用

介绍常用api的注意事项

String#search

  1. 大小写敏感
  2. search() 方法不执行全局匹配,它将忽略标志 g。它同时忽略 regexp 的 lastIndex 属性,并且总是从字符串的开始进行检索,这意味着它总是返回 stringObject 的第一个匹配的位置。
var str="Well Done"
str.search(/Done/) // 5
str.search(/done/) // -1 大小写敏感
str.search(/done/i) // 5 大小写敏感

String#split

stringObject.split(separator,howmany)
var string = "2020.01.10";
string.split(".") // ["2020", "01", "10"]

var string = "html,css,javascript";
string.split(/,/, 2) // ["html", "css"]
string.split(/(,)/) // ["html", ",", "css", ",", "javascript"]

String#match

stringObject.match(regexp)

依赖regexp是否有标志g

如果 regexp 没有标志 g,那么 match() 方法就只能在 stringObject 中执行一次匹配。如果没有找到任何匹配的文本, match() 将返回 null。否则,它将返回一个数组,其中存放了与它找到的匹配文本有关的信息。该数组的第 0 个元素存放的是匹配文本,而其余的元素存放的是与正则表达式的子表达式匹配的文本。除了这些常规的数组元素之外,返回的数组还含有两个对象属性。index 属性声明的是匹配文本的起始字符在 stringObject 中的位置,input 属性声明的是对 stringObject 的引用。

var str="1 plus 2 equal 3"

str.match(/,/) // null

str.match(/\d+/) // ["1", index: 0, input: "1 plus 2 equal 3", groups: undefined]

str.match(/(\d+)/) // ["1", "1", index: 0, input: "1 plus 2 equal 3", groups: undefined]


var str = '1a1 2b c3 4e3'
var reg = /(\d(\w)\d)/
str.match(reg) // ["1a1", "1a1", "a", index: 0, input: "1a1 2b c3 4e3", groups: undefined]

如果 regexp 具有标志 g,则 match() 方法将执行全局检索,找到 stringObject 中的所有匹配子字符串。若没有找到任何匹配的子串,则返回 null。如果找到了一个或多个匹配子串,则返回一个数组。不过全局匹配返回的数组的内容与前者大不相同,它的数组元素中存放的是 stringObject 中所有的匹配子串,而且也没有 index 属性或 input 属性。

var str="1 plus 2 equal 3"

str.match(/,/g) // null

str.match(/\d+/g) // ["1", "2", "3"]

str.match(/(\d+)/g) // ["1", "2", "3"]

String#replace

stringObject.replace(regexp/substr,replacement)

// replacement 可以是字符串,也可以是函数。

replacement 中的 $ 字符具有特定的含义。如下表所示,它说明从模式匹配得到的字符串将用于替换。

字符 替换文本
$1、$2、...、$99 与 regexp 中的第 1 到第 99 个子表达式相匹配的文本。
$& 与 regexp 相匹配的子串。
$` 位于匹配子串左侧的文本。
$' 位于匹配子串右侧的文本。
? 直接量符号。

replacement为函数时,如下用法

let re = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/u;
'2015-01-02'.replace(re, (
   matched, // 整个匹配结果 2015-01-02
   capture1, // 第一个组匹配 2015
   capture2, // 第二个组匹配 01
   capture3, // 第三个组匹配 02
   position, // 匹配开始的位置 0
   S, // 原字符串 2015-01-02
   groups // 具名组构成的一个对象 {year, month, day}
 ) => {
 let {day, month, year} = groups;
 return `${day}/${month}/${year}`;
});

返回的值,整个替换matched

function converse(str) {
  str = str.replace(/([A-Za-z])/g, function(matched, capture1,position,str,groups){
    if(capture1>='A'&&capture1<='Z') return capture1.toLowerCase()
    else return capture1.toUpperCase()
  })
  return str
}

console.log(converse('aBc'))

注意:如果 regexp 具有全局标志 g,那么 replace() 方法将替换所有匹配的子串。否则,它只替换第一个匹配子串。

var string = "2020.01.10";
string.replace(".", "/") // "2020/01.10"
string.replace(/\./, "/") // "2020/01.10"
string.replace(/\./g, "/") // "2020/01/10"

string.replace(/(10)/g, "$1 00:00:00") // "2020.01.10 00:00:00"
string.replace(/(10)/g, "$` 00:00:00") // "2020.01.2020.01. 00:00:00"
string.replace(/(10)/g, "$' 00:00:00") // "2020.01. 00:00:00"
string.replace(/(10)/g, "$& 00:00:00") // "2020.01.10 00:00:00"
string.replace(/(10)/g, "? 00:00:00") // "2020.01.$ 00:00:00"

RegExp#test

test() 方法用于检测一个字符串是否匹配某个模式

RegExpObject.test(string)
var string = '123456'
/\d/.test(string) //true

RegExp#exec

如果 exec() 找到了匹配的文本,则返回一个结果数组。否则,返回 null。此数组的第 0 个元素是与正则表达式相匹配的文本,第 1 个元素是与 RegExpObject 的第 1 个子表达式相匹配的文本(如果有的话),第 2 个元素是与 RegExpObject 的第 2 个子表达式相匹配的文本(如果有的话),以此类推。除了数组元素和 length 属性之外,exec() 方法还返回两个属性。index 属性声明的是匹配文本的第一个字符的位置。input 属性则存放的是被检索的字符串 string。我们可以看得出,在调用非全局的 RegExp 对象的 exec() 方法时,返回的数组与调用方法 String.match() 返回的数组是相同的。

有g

var string ='123'
var patt = /\d/g
patt.exec(string) // ["1", index: 0, input: "123", groups: undefined]
patt.exec(string) // ["2", index: 1, input: "123", groups: undefined]
patt.exec(string) // ["3", index: 2, input: "123", groups: undefined]
patt.exec(string) // null
patt.exec(string) // ["1", index: 0, input: "123", groups: undefined]

没有g

var string ='123'
var patt = /\d/
patt.exec(string) // ["1", index: 0, input: "123", groups: undefined]
patt.exec(string) // ["1", index: 0, input: "123", groups: undefined]

有g

var str = '1a1 2b c3 4e3'
var reg = /(\d(\w)\d)/g
reg.exec(str) // ["1a1", "1a1", "a", index: 0, input: "1a1 2b c3 4e3", groups: undefined]
reg.exec(str) // ["4e3", "4e3", "e", index: 10, input: "1a1 2b c3 4e3", groups: undefined]
reg.exec(str) // null
reg.exec(str) // ["1a1", "1a1", "a", index: 0, input: "1a1 2b c3 4e3", groups: undefined]

没有g

var str = '1a1 2b c3 4e3'
var reg = /(\d(\w)\d)/
reg.exec(str) // ["1a1", "1a1", "a", index: 0, input: "1a1 2b c3 4e3", groups: undefined]
reg.exec(str) // ["1a1", "1a1", "a", index: 0, input: "1a1 2b c3 4e3", groups: undefined]
reg.exec(str) // ["1a1", "1a1", "a", index: 0, input: "1a1 2b c3 4e3", groups: undefined]
reg.exec(str) // ["1a1", "1a1", "a", index: 0, input: "1a1 2b c3 4e3", groups: undefined]

RegExp的静态属性

字符 替换文本
$1、$2、...、$99 与 regexp 中的第 1 到第 99 个子表达式相匹配的文本。
RegExp.input / RegExp["$_"] 最近一次目标字符串
RegExp.lastMatch / RegExp["$&"] 最近一次匹配的文本
RegExp.lastParen / RegExp["$+"] 最近一次捕获的文本
RegExp.leftContext / RegExp["$`"] 目标字符串中lastMatch之前的文本
RegExp.rightContext / RegExp["$'"] 目标字符串中lastMatch之后的文本
RegExp.lastIndex 下一次匹配开始的位置
var regex = /(\d)[a-z]/g;
var string = "11a2b";
string.match(regex); // ["1a", "2b"]
RegExp.input // "11a2b"
RegExp.lastMatch // "2b"
RegExp.lastParen // "2"
RegExp.leftContext // "11a"
RegExp.rightContext // ""

参考资料

个人博客

github.com/abc-club/fr…