JavaScript正则表达式

191 阅读6分钟

概述

正则表达式是用于匹配字符串中字符组合的模式。在 JavaScript 中,正则表达式也是对象。
新建正则表达式可以使用字面量或者RegExp构造函数。

let regex = /xyz/i;
// 等价于
// 有两种方法可以创建一个 RegExp 对象:一种是字面量,另一种是字符串参数。
// 使用字符串参数注意转义特殊字符
let regex = new RegExp(/xyz/, "i");
let regex = new RegExp("xyz", "i");

字符串特殊字符转义:

字符转义
单引号\'
双引号\"
反斜杠\\
换行\n
回车\r
tab(制表符)\t
退格符\b
换页符\f

正则对象生成以后,有两种使用方式:

  • 使用正则对象本身的方法,将字符串作为参数,比如regex.test(string)。
  • 使用字符串对象的方法,将正则对象作为参数,比如string.match(regex)。

下面逐一介绍这两种使用方式。

正则对象属性方法

属性

正则对象的属性主要如下:

属性描述
ignoreCase表示是否设置i修饰符,该属性只读。i修饰符表示不区分大小写。
global表示是否设置g修饰符,该属性只读。g修饰符表示全局匹配。
lastIndex返回下一次开始搜索的位置,该属性可读写。
source返回正则表达式的字符串形式(不包括反斜杠),该属性只读。
multiline表示是否设置m修饰符,该属性只读。m修饰符表示多行匹配。

方法

正则对象的方法如下:

  • test

test方法返回布尔值,用来验证字符串是否符合正则表达式。

/cat/.test('cats and dogs'); // true
  • exec

exec方法返回匹配结果(返回一个数组,在未匹配到时会返回 null)。

/cat/.exec('cats and dogs');	// [cat]

/cat/.exec('birds'); // null

exec方法的返回数组还包含以下三个属性:

  • input:整个原字符串。
  • index:整个模式匹配成功的开始位置。
  • group:一个命名捕获组对象,其键是名称,值是捕获组。若没有定义命名捕获组,则 groups 的值为 undefined。

正则表达式中括号()中的内容就是捕获组,括号中的内容如果是(?<捕获组的名字>捕获组对应的规则)格式就是命名捕获组,例如/(?\d)(?\d)/。对捕获组进行命名,可以更方便地获取某个捕获组的数据。

字符串对象的方法

字符串对象和正则相关的方法如下:

  • match

match方法返回匹配结果(返回一个数组,在未匹配到时会返回 null)。字符串的match方法与正则对象的exec方法非常类似。

'cats and dogs'.match(/cat/); //[cat]

'birds'.match(/cat/); //null
  • matchAll

matchAll() 方法返回一个包含所有匹配正则表达式的结果及分组捕获组的迭代器。

const regexp = /t(e)(st(\d?))/g;
const str = 'test1test2';

const array = [...str.matchAll(regexp)];

console.log(array[0]);
// Expected output: Array ["test1", "e", "st1", "1"]

console.log(array[1]);
// Expected output: Array ["test2", "e", "st2", "2"]

  • search

search方法返回第一个满足条件的匹配结果在整个字符串中的位置位置索引,或者在失败时返回 -1。

'cats and dogs'.search(/cat/);	// 0

'birds'.search(/cat/); // -1
  • replace

replace方法可以替换匹配的值。

// replace接受两个参数,第一个参数是正则表达式,第二个参数是替换的内容。
// 第二个参数可以是字符串,也可以是函数,通常情况下第二参数传入字符串。
let str = 'cats and dogs';
str.replace(/a/, 'b');	// 'cbts and dogs'
str.replace(/a/g, 'b'); //'cbts bnd dogs'

正则表达式如果不加g修饰符,就替换第一个匹配成功的值,否则替换所有匹配成功的值。

// replace接受两个参数,第一个是搜索模式,第二个是替换的内容。
let str = 'cats and dogs';
str.replace(/a/g, 'b'); //'cbts bnd dogs'
  • split

split方法按照正则规则分割字符串,返回一个由分割后的各个部分组成的数组。

// split接受两个参数,第一个参数是分隔规则,第二个参数是返回数组的最大长度。
'cats and dogs'.split(/a/);	// ['c', 'ts ', 'nd dogs']

匹配规则

字面量字符

大部分字符在正则表达式中,就是字面的含义,比如/a/匹配a,/b/匹配b。它们都叫做“字面量字符”。

元字符

除了字面量字符以外,还有一部分字符有特殊含义,不代表字面的意思。它们叫做“元字符”,常用的有以下几个。

字符描述
^匹配输入字符串的开始位置。
$匹配输入字符串的结束位置。
*匹配前一个表达式 0 次或多次。等价于{0,}。
+匹配前一个表达式 1次或多次。等价于{1,}。
?匹配前一个表达式 0 次或1次。等价于{0,1}。
.默认匹配除换行符(\n, \r)之外的任何单个字符。相当于[^\n\r]。
|
{n}n 是一个非负整数,表示前面一个字符精确匹配 n 次。
{n,}n 是一个非负整数,表示前面一个字符至少匹配 n 次。
{n,m}n,m 是一个非负整数,其中 n <= m。表示前面一个字符至少匹配 n 次,至多匹配m次。
[ABC]字符合集,匹配[]中所包含的任意一个字符。。
[A-Z]字符合集,未匹配[]中的任意字符。连字符-表示字符的连续范围。
[^ABC]反向字符合集,匹配除了[]中字符的所有字符。
\b匹配一个单词边界,也就是指单词和空格间的位置。例如, 'er\b' 可以匹配"never" 中的 'er',但不能匹配 "verb" 中的 'er'。
\B匹配非单词边界。'er\B' 能匹配 "verb" 中的 'er',但不能匹配 "never" 中的 'er'。
\d匹配一个数字。等价于 [0-9]。
\D匹配一个非数字字符。等价于 [^0-9]。
\f匹配一个换页符。
\n匹配一个换行符。
\r匹配一个回车符。
\t匹配一个制表符。
\v匹配一个垂直制表符。
\s匹配一个空白字符,包括空格、制表符、换页符和换行符。等价于 [ \f\n\r\t\v]。
\S匹配一个非空白字符。等价于 [^ \f\n\r\t\v]。
\w匹配字母、数字、下划线。等价于[A-Za-z0-9_]。
\W匹配非字母、数字、下划线。等价于[^A-Za-z0-9_]。

进阶使用:

字符描述
(pattern)匹配 pattern 并获取这一匹配。要匹配圆括号字符,请使用 '\(' 或 '\)'。
(?:pattern)匹配 pattern 但不获取匹配结果,也就是说这是一个非获取匹配,不进行存储供以后使用。这在使用 "或" 字符 (|) 来组合一个模式的各个部分是很有用。例如, 'industr(?:y|ies) 就是一个比 'industry|industries' 更简略的表达式。
(?=pattern)正向肯定预查(look ahead positive assert),在任何匹配pattern的字符串开始处匹配查找字符串。这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。例如,"Windows(?=95|98|NT|2000)"能匹配"Windows2000"中的"Windows",但不能匹配"Windows3.1"中的"Windows"。预查不消耗字符,也就是说,在一个匹配发生后,在最后一次匹配之后立即开始下一次匹配的搜索,而不是从包含预查的字符之后开始。
(?!pattern)正向否定预查(negative assert),在任何不匹配pattern的字符串开始处匹配查找字符串。这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。例如"Windows(?!95|98|NT|2000)"能匹配"Windows3.1"中的"Windows",但不能匹配"Windows2000"中的"Windows"。预查不消耗字符,也就是说,在一个匹配发生后,在最后一次匹配之后立即开始下一次匹配的搜索,而不是从包含预查的字符之后开始。
(?<=pattern)反向(look behind)肯定预查,与正向肯定预查类似,只是方向相反。例如,"(?<=95|98|NT|2000)Windows"能匹配"2000Windows"中的"Windows",但不能匹配"3.1Windows"中的"Windows"。
(?<!pattern)反向否定预查,与正向否定预查类似,只是方向相反。例如"(?<!95|98|NT|2000)Windows"能匹配"3.1Windows"中的"Windows",但不能匹配"2000Windows"中的"Windows"。

个人理解预查需要预先匹配预查的正则规则后再进行后续的匹配。

转义字符

正则表达式中的元字符,如果要匹配它们本身,就需要使用转义字符\。

/1+1/.test("1+1")	// false

/1+1/.test("11")	// true

/1\+1/.test("1+1")	// true

修饰符

修饰符表示正则表达式的附加规则,放在正则模式的最尾部,主要有以下几个。

  • i修饰符

默认情况下,正则对象区分字母的大小写,i修饰符表示忽略大小写。

/cat/.test("CAT") // false
  
/cat/i.test("CAT") // true
  • g修饰符

默认情况下,第一次匹配成功后,正则对象就停止向下匹配。g修饰符表示全局匹配,使用g修饰符正则表达式每次匹配会继续向下匹配,主要用于搜索和替换。

// 如果不加g修饰符,每次匹配时都是从字符串头部开始匹配。
let regex = /a/;
let res = regex.test('cats and dogs');
console.log('res', res, regex.lastIndex)	// true,0

res = regex.test('cats and dogs');
console.log('res', res, regex.lastIndex)	// true,0

res = regex.test('cats and dogs');
console.log('res', res, regex.lastIndex)	// true,0

res = regex.test('cats and dogs');
console.log('res', res, regex.lastIndex)	// true,0
// 如果加g修饰符,每次匹配都是从上一次匹配成功处开始往后匹配。
let regex = /a/g;
let res = regex.test('cats and dogs');
console.log('res', res, regex.lastIndex)	// true,2

res = regex.test('cats and dogs');
console.log('res', res, regex.lastIndex)	// true,6

res = regex.test('cats and dogs');
console.log('res', res, regex.lastIndex)	// false,0

res = regex.test('cats and dogs');
console.log('res', res, regex.lastIndex)	// true,2
  • m修饰符

默认情况下,正则对象会将换行符算入字符串的开头或结尾。m修饰符表示多行模式,使用m修饰符正则表达式会忽略字符串头部或尾部的换行符,即^和$会忽略换行符。

// 如果不加m修饰符,认为\n是输入字符串的结束位置。
let regex = /dogs$/;
let res = regex.test('cats and dogs\n');
console.log('res', res)	// false
// 如果不加m修饰符,忽略\n,认为dogs是输入字符串的结束位置。
let regex = /dogs$/m;
let res = regex.test('cats and dogs\n');
console.log('res', res)	// true
修饰符含义描述
iignore - 不区分大小写将匹配设置为不区分大小写,搜索时不区分大小写: A 和 a 没有区别。
gglobal - 全局匹配查找所有的匹配项。
mmulti line - 多行匹配使边界字符 ^ 和 $ 匹配每一行的开头和结尾,记住是多行,而不是整个字符串的开头和结尾。

实例

ip地址、mac地址

MAC地址

规则:

MAC地址则是48位的(6个字节),通常表示为12个16进制数,每2个16进制数之间用冒号隔开,如08:00:20:0A:8C:6D。

正则规则:

let regex = /^([A-Fa-f\d]{2}:){5}[A-Fa-f\d]{2}$/
// 等价于
// let regex = new RegExp(/^([A-Fa-f\d]{2}:){5}[A-Fa-f\d]{2}$/)
// let regex = new RegExp('([A-Fa-f\\d]{2}:){5}[A-Fa-f\\d]{2}')
regex.test('08:00:20:0A:8C:6D') // true

IPV4地址

规则:

IP地址是一个32位的二进制数,通常被分割为4个“8位二进制数”(也就是4个字节)。IP地址通常用“点分十进制”表示成(a.b.c.d)的形式,其中,a,b,c,d都是0~255之间的十进制整数。例:点分十进IP地址(100.4.5.6)。

正则规则:

let regex  = /^((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.){3}(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])$/;
regex.test('100.4.5.6')	// true

IPV6地址

规则:

IPV6的地址长度为128位,是IPv4地址长度的4倍。于是IPv4点分十进制格式不再适用,采用十六进制表示。 IPV6地址通常表示为32个16进制数,每4个16进制数之间用冒号隔开,如2001:DB8:0:23:8:800:200C:417A。

正则规则:

let regex = /^([\da-fA-F]{1,4}:){7}([\da-fA-F]{1,4})$/;
regex.test('2001:DB8:0:23:8:800:200C:417A')	// true

数字类

正整数

let regex = /^(0|[1-9]\d*)$/;
regex.test('10086')	// true

金额

规则:

精确到两位小数。

正则规则:

let regex = /^(0|[1-9]\d+)(.\d{1,2})?$/;
regex.test('0.12')	// true

时间日期类

时间

规则:

匹配24小时制时间。

正则规则:

let regex = /^([01]\d|2[0-3]):([0-5]\d)$/;
regex.test('23:59')	// true

日期

规则:

匹配YYYY-MM-DD格式日期。

正则规则:

let regex = /^([0-9]{4})-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$/;
regex.test('2023-06-25')	// true

个人信息类

姓名

规则:

支持中英文。

正则规则:

let regex = /^[\u4e00-\u9fa5_a-zA-Z]+$/;
regex.test('hanhan')	// true

电话号码

规则:

一般由区号和8位数字组成,其中区号是0开头的3到4位数字,号码是非0开头的7到8位数字。如0551-88888888

正则规则:

let regex = /^(0\d{2,3}-)?([1-9]\d{6,7})$/;
regex.test('0551-88888888')	// true

手机号码

规则:

手机号码一般由11位数字组成,号段范围为13x-19x: 中国移动号段:134(0-8)、135、136、137、138、139、147、150、151、152、157、158、159、172、178、182、183、184、187、188、195、197、198 中国联通号段:130、131、132、145、155、156、166、175、176、185、186、196 中国电信号段:133、149、153 、180 、181 、189、173、177、190、191、193、199 中国广电号段:192

正则规则:

// 简单限制号段
let regex  = /^1[3-9]\d{9}$/;
regex.test('13960999999')	// true

/*
// 严格限制号段
let regex = /^1(3\d|4[479]|5[0-35-9]|6[6]|7[135678]|8\d|9\d|9[0-35-9])\d{8}$/;
*/

密码

规则:

密码长度8位,由数字、小写字符和大写字母组成,但必须至少包括2种字符。

正则规则:

let regex = /(?!^[0-9]{8}$)(?!^[a-z]{8}$)(?!^[A-Z]{8}$)^[0-9A-Za-z]{8}$/;
regex.test('1234567a')	// true
regex.test('12345678')	// false

其他

自定义规则1

规则:

  1. "_"再加匹配字符串,可包含0-9*#X.字符,其中"X"代表任意数字,"."代表不限长度字符串, 举例:
  • _9100XX:表示匹配以9100为前缀的6位号码
  • _.XXX:表示匹配以XXX为结束的任意号码
  • _.00.:表示中间包含有00的任意号码
  • 10000:表示精确匹配10000号码
  1. 允许多个,以英文逗号分隔

正则规则:

let re = /^(_?[\d\*#X\.]+,?)*$/

小结

了解正则表达式的基本语法,再根据需求拆分、实现匹配规则,即可满足日常工作中的基本使用。

参考

关于正则表达式基本语法:
JavaScript RegExp对象_w3cschool
正则表达式 - JavaScript | MDN
正则表达式 | 菜鸟教程
关于正则表达式进阶使用,推荐下面这篇文章:
JS正则表达式完整教程