【JavaScript】正则用法详解

56 阅读3分钟

概念

如何构建一个正则表达式

  • 构造函数:new RegExp("正则主体",[修饰符])
  • 字面量:/正则主体/修饰符
const reg1 = new RegExp('.com$')
const reg2 = /^aaa/
reg2 instanceof RegExp // true

模式中使用的所有元字符都建议在之前加\转义,正则表达式中的元字符包括:

( [ { \ ^ $ | ) ? * + . ] }

修饰符

    1. i(ignore case) 不区分大小写
const pattern = /hello/i
const str = 'Hello world'
console.log(pattern.ignoreCase)  //true
console.log(pattern.test(str))   //true
    1. g(global) 全局
      RegExp.lastIndex 如果匹配模式带有g,这个属性存储在整个字符串中下一次检索的开始位置,这个属性会被exec(),test()方法调用到(不带g永远返回0)。
var pattern = /hello/ig;
var str = 'Hello helloworld';
console.log(pattern.global);  //true
console.log(pattern.test(str));  // true
console.log(pattern.lastIndex);  // 5
    1. m(multiline) 多行
      如果目标字符串中不含有换行符\n,即只有一行,那么/m修饰符没有任何意义。
      如果正则表达式中不含有^或$匹配字符串的开头或结尾,那么/m修饰符没有任何意义。
    1. u (unicode)
      加了u修饰符,会正确处理大于\uFFFF的unicode
/^\uD83D/.test('\uD83D\uDC2A')                //  true
/^\uD83D/u.test('\uD83D\uDC2A')               //  false
    1. s 默认情况下,.可以匹配任意字符,除了换行符,且.不能匹配Unicode字符,需要使用u选项启用Unicode模式才行。

ES2018引入了dotAll模式,通过s选项可以启用,这样,.就可以匹配换行符了。

/foo.bar/.test('foo\nbar'); 				// false
/foo.bar/s.test('foo\nbar'); 				// true

类使用[ ]来表达,用于查找某个范围内的字符

  • \s: 等价于[\t\n\x0B\f\r] 空格
  • \S: 等价于[^\t\n\x0B\f\r] 非空格
  • \d: 等价于[0-9]数字\D[^0-9] 非数字
  • \w: 等价于[a-zA-Z_0-9] 单词字符 ( 字母、数字、下划线)
  • \W: 等价于[^a-zA-Z_0-9] 非单词字符
  • . : 等价于[^\r\n] 任意字符,除了回车与换行外所有字符
  • \f: 等价于\x0c \cL 匹配一个换页符
  • \n: 等价于\x0a \cJ 匹配一个换行符
  • \r: 等价于\x0d \cM 匹配一个回车符
  • \t: 等价于\x09 \cI 匹配一个制表符
  • \v: 等价于\x0b \cK 匹配一个垂直制表符
  • \xxx: 查找以八进制数 xxx 规定的字符
  • \xdd: 查找以十六进制数 dd 规定的字符
  • \uxxxx: 查找以十六进制数 xxxx 规定的 Unicode 字符

量词

量词表示匹配多少个目标对象,精确匹配长度使用`{ }

  • n*{0,} 匹配零个或多个n
  • n+{1,} 匹配至少一个 n 的字符串
  • n?{0,1} 匹配零个或一个n
  • {n}: 匹配n次
  • {n,m} 匹配n到m次
  • {n,} 至少匹配n次`

分组

分组使用( ),作用是提取相匹配的字符串,使量词作用于分组 比如hehe{3}是把e匹配了3次而不是单词,如果希望作用于单词,可以使用分组(hehe){3}

分组中使用 | 可以达到或的效果 比如:T(oo|ii)m可以匹配 Toom 和 Tiim

`abToomhaTiimmm`.replace(/T(oo|ii)m/g, '-')                // ab-ha-mm

反向引用

使用( )后可以使用$1-$9等来匹配

'2022-02-11'.replace(/(\d{4})\-(\d{2})\-(\d{2})/g, '$2/$3/$1')            //  02/11/2022

后向引用

\n 表示后向引用,\1是指在正则表达式中,从左往右数第1个( )中的内容;以此类推,\2表示第2个( )\0表示整个表达式。

//匹配日期格式,表达式中的\1代表重复(\-|\/|.)
const rgx = /\d{4}(\-|\/|.)\d{1,2}\1\d{1,2}/
rgx.test("2016-03-26")             // true
rgx.test("2016-03.26")             // false

后向引用和反向引用的区别是:\n只能用在表达式中,而$n只能用在表达式之外的地方。

分组命名 (ES9)

ES2018 之前的分组是通过数字命名的:

const pattern = /(\d{4})-(\d{2})-(\d{2})/u
const result = pattern.exec('2018-10-25')
console.log(result[0]) // 2018-10-25
console.log(result[1]) // 2018
console.log(result[2]) // 10
console.log(result[3]) // 25

现在可以通过指定分组的名称,增加代码可读性,便于维护:

const pattern = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/u
const result = pattern.exec('2018-10-25')
console.log(result.groups.year)  // 2018
console.log(result.groups.month) // 10
console.log(result.groups.day)  // 25

分组命名还可以和String.prototype.replace方法结合:

const reDate = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/
const d = '2018-10-25'
const USADate = d.replace(reDate, '$<month>-$<day>-$<year>')
console.log(USADate)						// 10-25-2018

忽略分组

如果不希望捕获某些分组,在分组内加上?:即可 比如(?:tom).(ok)那么这里$1指的就是ok

前瞻

前瞻 Lookahead 是RegExp匹配到规则的时候,向后检查是否符合断言

  • 正向前瞻 (?= ) 后面要有xx
  • 负向前瞻 (?! ) 后面不能有xx
'1a2bc*456v8'.replace(/\w(?=\d)/g, '-')  //  1-2bc*--6-8    匹配后面是数字的单词字符
'1a2bc*456v8'.replace(/\w(?!\d)/g, '-')  //  -a---*45-v-    匹配后面不是数字的单词字符
 
const pattern1 = /\d+(?= dollars)/u  // 正向前瞻,匹配字符串中紧跟着是dollars的数字
const result1 = pattern1.exec('42 dollars')
console.log(result1[0]) // 42
 
const pattern2 = /\d+(?! dollars)/u // 负向前瞻,匹配字符串中紧跟着的不是dollars的数字
const result2 = pattern2.exec('42 pesos')
console.log(result2[0]) // 42

后顾 (ES9)

后顾 Lookbehind 是RegExp匹配到规则的时候,向前检查是否符合断言

  • 正向后顾 (?<= ) 前面要有xx
  • 负向后顾 (?<! ) 前面不能有xx
const pattern1 = /(?<=\$)\d+/u;  // 正向后顾,匹配字符串中前面是\$的数字
const result1 = pattern1.exec('$42')
console.log(result1[0]) // 42 
 
const pattern2 = /(?<!\$)\d+/u  // 负向后顾,匹配字符串中前面不是是\$的数字
const result2 = pattern2.exec('€42')
console.log(result2[0])  // 42

贪婪模式 与 非贪婪模式

正则表达式在匹配的时候默认会尽可能多的匹配,叫贪婪模式。通过在限定符后加?可以进行非贪婪匹配 比如\d{3,6}默认会匹配6个数字而不是3个,在量词{ }后加一个?就可以修改成非贪婪模式,匹配3次

`12345678`.replace(/\d{3,6}/, '-')   // -78
`12345678`.replace(/\d{3,6}?/, '-')  // -45678
'abbbb'.replace(/ab+?/, '-')   //  -bbb

优先级

优先级从高到低:

  1. 转义 \
  2. 括号( )、(?: )、(?= )、[ ]
  3. 字符和位置
  4. 或 |

属性和方法

实例属性

  • global:是否全文搜索,默认false,对应修饰符的g,只读
  • ignoreCase:是否大小写敏感,默认false,对应修饰符i,只读
  • multiline:是否多行搜索,默认false,对应修饰符m,只读
  • flags:返回修饰符,只读
  • lastIndex:当前表达式匹配内容的最后一个字符的下一个位置
  • source:正则表达式的文本字符串

实例方法

test()

测试字符串参数中是否存在匹配正则表达式的字符串,使用.test的时候如果修饰符有g ,那么会正则会记住lastIndex并在下一次执行的时候从lastIndex处开始检测,如果只是为了测试是否符合正则,可以不用g或者每次都重新实例化正则表达式

const reg=/\w/g
reg.test('a')  //  true
reg.test('a')  //  false

exec()

使用正则表达式对字符串执行搜索,并将更新全局RegExp对象的属性以反映匹配结果
如果匹配失败,exec() 方法返回 null
如果匹配成功,exec() 方法返回一个数组,并更新正则表达式对象的属性

  • 数组索引0:匹配的全部字符串
  • 数组索引1,2..n:括号中的分组捕获
  • index:属性是匹配文本的第一个字符的位置
  • input:存放被检索的字符串
const str="123456@qq.com"
const ret1 = /(@)(qq.com)$/.exec(str) // ['@qq.com', '@', 'qq.com', index: 6, input: '123456@qq.com', groups: undefined]
const ret2 = /163.com$/.exec(str) // null