创建
- 字面量创建方式
- 实例创建方式
var reg = /pattern/flags
// 字面量创建方式
var reg = new RegExp(pattern,flags);
//实例创建方式
//pattern:正则表达式
//flags:标识(修饰符)
//标识主要包括:
//1. i 忽略大小写匹配
//2. m 多行匹配,即在到达一行文本末尾时还会继续寻常下一行中是否与正则匹配的项
//3. g 全局匹配 模式应用于所有字符串,而非在找到第一个匹配项时停止
描述字符
根据正则表达式语法规则,大部分字符仅能够描述自身,这些字符被称为普通字符,如所有的字母、数字等。 可以参考MDN文档
元字符
元字符就是拥有特动功能的特殊字符,大部分需要加反斜杠进行标识,以便于普通字符进行区别,而少数元字符,需要加反斜杠,以便转译为普通字符使用。JavaScript 正则表达式支持的元字符如表所示。
元字符 | 描述 |
---|---|
. | 查找单个字符,除了换行和行结束符 |
\w | 查找单词字符(字母+数字) |
\W | 查找非单词字符 |
\d | 查找数字 |
\D | 查找非数字字符 |
\s | 查找空白字符 |
\S | 查找非空白字符 |
\b | 匹配单词边界 |
\B | 匹配非单词边界 |
\0 | 查找 NUL字符 |
\n | 查找换行符 |
\f | 查找换页符 |
\r | 查找回车符 |
\t | 查找制表符 |
\v | 查找垂直制表符 |
\xxx | 查找以八进制数 xxxx 规定的字符 |
\xdd | 查找以十六进制数 dd 规定的字符 |
\uxxxx | 查找以十六进制 xxxx规定的 Unicode 字符 |
范围
在正则表达式语法中,[]
表示字符范围。在[]
中可以包含多个字符,表示匹配其中任意一个字符。如果多个字符的编码顺序是连续的,可以仅指定开头和结尾字符,省略中间字符,仅使用连字符-
表示。如果在[]
内添加脱字符^
前缀,还可以表示范围之外的字符。例如:
[abc]
:查找方括号内任意一个字符。[^abc]
:查找不在方括号内的字符。[0-9]
:查找从 0 至 9 范围内的数字,即查找数字。[a-z]
:查找从小写 a 到小写 z 范围内的字符,即查找小写字母。[A-Z]
:查找从大写 A 到大写 Z 范围内的字符,即查找大写字母。[A-z]
:查找从大写 A 到小写 z 范围内的字符,即所有大小写的字母。
特殊的范围
-
[\u0000-\u00ff] :匹配任意ASCII字符
-
[\u0000-\u00ff]:匹配任意双字节的汉字
选择匹配
选择匹配类似于 JavaScript 的逻辑与运算,使用竖线|
描述,表示在两个子模式的匹配结果中任选一个。例如:
var r = /\w+|\d+/; // 匹配任意数字或字符
重复匹配
在正则表达式语法中,定义了一组重复类量词,如表所示。它们定义了重复匹配字符的确数或约数。
量词 | 描述 |
---|---|
n+ | 匹配任何包含至少一个 n 的字符串(>=1) |
n* | 匹配任何包含零个或多个 n 的字符串(>=0) |
n? | 匹配任何包含零个或一个 n 的字符串(0或1) 如果紧跟在任何量词 *、 +、? 或 {} 的后面,将会使量词变为非贪婪(匹配尽量少的字符),和缺省使用的贪婪模式(匹配尽可能多的字符)正好相反。例如,对 “123abc” 使用 /\d+/ 将会匹配 “123”,而使用 /\d+?/ 则只会匹配到 “1”。 |
n{x} | 匹配包含 x 个 n 的序列的字符串(x) |
n{x,y} | 匹配包含最少 x 个、最多 y 个 n 的序列的字符串(>=x&&<=y) |
n{x,} | 匹配包含至少 x 个 n 的序列的字符串(>=x) |
边界量词
边界就是确定匹配模式的位置,如字符串的头部或尾部,具体说明如表所示。
量词 | 说明 |
---|---|
^ | 匹配开头,在多行检测中,会匹配一行的开头 |
$ | 匹配结尾,在多行检测中,会匹配一行的结尾 |
方法
对象原型上的方法
exec
一个在字符串中执行查找匹配的RegExp
方法,它返回一个数组(未匹配到则返回 null
)。
属性/索引 | 描述 |
---|---|
[0] | 匹配的全部字符串 |
[1], ...[*n* ] | 括号中的分组捕获 |
index | 匹配到的字符位于原始字符串的基于0的索引值 |
input | 原始字符串 |
const myRe = /d(b+)d/g
const str = 'abc dbbbd a dbd dd'
let result = myRe.exec(str)
console.log(result)
/*输出
[
'dbbbd',
'bbb',
index: 4,
input: 'abc dbbbd a dbd dd',
groups: undefined
]
*/
注意:可以看到,就算加了globel标识符,数组第0个元素都是返回的第一次匹配到的内容
test
test()
方法执行一个检索,用来查看正则表达式与指定的字符串是否匹配。返回 true
或 false
。
没有那么多弯弯绕绕,如果匹配就返回true,否则返回false
关于全局标识符g
对两个方法的影响
RegExp
对象有一个内置属性lastindex
,当设置全局标识符g
时,开始生效,应用如下规则
- **重点:**如果
lastIndex
大于字符串的长度,则regexp.test
和regexp.exec
将会匹配失败,然后lastIndex
被设置为0
。 - 如果
lastIndex
等于字符串的长度,且该正则表达式匹配空字符串,则该正则表达式匹配从lastIndex
开始的字符串。(then the regular expression matches input starting atlastIndex
.) - 如果
lastIndex
等于字符串的长度,且该正则表达式不匹配空字符串 ,则该正则表达式不匹配字符串,lastIndex
被设置为0
.。 - 否则,
lastIndex
被设置为紧随最近一次成功匹配的下一个位置。
看下面这个例子
const myRe = /d(b+)d/g
console.log(myRe.lastIndex) // 0
let myArray = myRe.exec("caaadbbdbsbz")
console.log(myArray)
// [ 'dbbd', 'bb', index: 4, input: 'caaadbbdbsbz', groups: undefined ]
console.log(myRe.lastIndex) // 8
// 此时myRe会从传入字符串"cdbbdbsbz"的第8位开始匹配,也就是匹配“z”
myArray = myRe.exec("cdbbdbsbz")
console.log(myArray) // 匹配失败,返回null
console.log(myRe.lastIndex) // 匹配失败,所以重置为0
输出结果
0
[ 'dbbd', 'bb', index: 4, input: 'caaadbbdbsbz', groups: undefined ]
8
null
0
同理,使用test
方法,在上例中,首次调用返回true
,第二次返回false
const myRe = /d(b+)d/g
console.log(myRe.lastIndex)
let myArray = myRe.test("caaadbbddbdsbz")
console.log(myArray)
console.log(myRe.lastIndex)
myArray = myRe.test("cdbbdbsbz")
console.log(myArray)
console.log(myRe.lastIndex)
输出结果
0
true
8
false
0
这种设计方式感觉有点奇怪,考虑两种场景
1.要想获取一个字符串内的所有匹配,你得这么干
// regexp必须带有g标识符 while ((match = regexp.exec(str)) !== null) { console.log(`Found ${match[0]} start=${match.index} end=${regexp.lastIndex}.`); }
当然这种设计可以保证获取到每次匹配的字符串索引范围
实际上现在有很多其他的替代方案去解决这种问题,比如String类型的match方法,不过该方法无法获取到每次匹配的字符串索引范围
2.想使用同一种正则表达式分别去匹配两个不同的字符串,你得这么干
console.log(regexp.test(str1)) // 假设你的regexp设置了全局模式的标识符,lastIndex属性生效,那么你很有可能忽略了下面这行代码 regexp.lastIndex = 0 console.log(regexp.test(str2))
是不是很难受,如果不熟悉exec配置了全局标识符的特点,忽略了第三行代码,很大可能性第二次匹配不出结果,这种情况下连排查bug都得排半天,你可能根本不会怀疑这个代码有问题。
这种情况下,解决方案有两种,第一种,不用g标识符,第二种使用字面量形式的正则表达式,如下
console.log(/\d+/g.test(str1)) console.log(/\d+/g.test(str2))
因为通过字面量形式创建的正则表达式对象享有独立的地址空间,或者认为是不同的正则对象
String
类型方法使用正则作为参数
match
语法
str.match(regexp)
返回值
- 如果使用
g
标志,则将返回与完整正则表达式匹配的所有结果,但不会返回捕获组。 - 如果未使用
g
标志,则仅返回第一个完整匹配及其相关的捕获组(Array
)。 在这种情况下,同reg.exec(str)
const str = 'abc dbbbd a dbd dd'
console.log(str.match(/d(b+)d/))
console.log(str.match(/d(b+)d/g))
输出结果
[
'dbbbd',
'bbb',
index: 4,
input: 'abc dbbbd a dbd dd',
groups: undefined
]
[ 'dbbbd', 'dbd' ]
matchAll
该方法返回一个包含所有匹配正则表达式的结果及分组捕获组的迭代器
正则表达式作为参数时,必须设置全局模式g
,否则会抛出异常
如果使用 matchAll
,就可以不必使用 while 循环加 exec 方式(且正则表达式需使用 /g
标志)。使用 matchAll
会得到一个迭代器的返回值,配合 for...of
, array spread, 或者 Array.from()
可以更方便实现功能:
const regexp = RegExp('foo[a-z]*','g');
const str = 'table football, foosball';
const matches = str.matchAll(regexp);
for (const match of matches) {
console.log(`Found ${match[0]} start=${match.index} end=${match.index + match[0].length}.`);
}
// expected output: "Found football start=6 end=14."
// expected output: "Found foosball start=16 end=24."
// matches iterator is exhausted after the for..of iteration
// Call matchAll again to create a new iterator
Array.from(str.matchAll(regexp), m => m[0]);
// Array [ "football", "foosball" ]
matchAll
的另外一个亮点是更好地获取捕获组。因为当使用 match()
和 /g
标志方式获取匹配信息时,捕获组会被忽略,使用 matchAll
可以通过如下方式获取分组捕获:
let array = [...str.matchAll(regexp)];
array[0];
// ['test1', 'e', 'st1', '1', index: 0, input: 'test1test2', length: 4]
array[1];
// ['test2', 'e', 'st2', '2', index: 5, input: 'test1test2', length: 4]
search
执行正则表达式和 String
对象之间的一个搜索匹配。
如果匹配成功,则 search()
返回正则表达式在字符串中首次匹配项的索引;否则,返回 -1
。
当你想要知道字符串中是否存在某个模式(pattern)时可使用 search()
,类似于正则表达式的 test()
方法。当要了解更多匹配信息时,可使用 match()
(但会更慢一些),该方法类似于正则表达式的 exec() 方法。
var str = "hey JudE";
var re = /[A-Z]/g;
var re2 = /[.]/g;
console.log(str.search(re)); // returns 4, which is the index of the first capital letter "J"
console.log(str.search(re2)); // returns -1 cannot find '.' dot punctuation
replace
该方法返回一个由替换值(replacement
)替换部分或所有的模式(pattern
)匹配项后的新字符串。模式可以是一个字符串或者一个正则表达式,替换值可以是一个字符串或者一个每次匹配都要调用的回调函数。如果pattern
是字符串,则仅替换第一个匹配项。
语法
str.replace(regexp|substr, newSubStr|function)
参数
-
regexp
(pattern)一个
RegExp
对象或者其字面量。该正则所匹配的内容会被第二个参数的返回值替换掉。 -
substr
(pattern)一个将被
newSubStr
替换的字符串
。其被视为一整个字符串,而不是一个正则表达式。仅第一个匹配项会被替换。 -
newSubStr
(replacement)用于替换掉第一个参数在原字符串中的匹配部分的
字符串
。该字符串中可以内插一些特殊的变量名。 -
function
(replacement)一个用来创建新子字符串的函数,该函数的返回值将替换掉第一个参数匹配到的结果。
注意,当第二个参数出现如下几种情况时
变量名 | 代表的值 |
---|---|
$$ | 插入一个 “$”。 |
$& | 插入匹配的子串。 |
$` | 插入当前匹配的子串左边的内容。 |
$' | 插入当前匹配的子串右边的内容。 |
$n | 假如第一个参数是 RegExp 对象,并且 n 是个小于100的非负整数,那么插入第 n 个括号匹配的字符串。提示:索引是从1开始 |
(1)如何使用正则表达式进行replace
var str = 'Twas the night before Xmas...';
var newstr = str.replace(/xmas/i, 'Christmas');
console.log(newstr); // Twas the night before Christmas...
(2)使用global
和ignore
选项
var re = /apples/gi;
var str = "Apples are round, and apples are juicy.";
var newstr = str.replace(re, "oranges");
// oranges are round, and oranges are juicy.
console.log(newstr);
(3)交换字符串中的两个单词
var re = /(\w+)\s(\w+)/;
var str = "John Smith";
var newstr = str.replace(re, "$2, $1");
// Smith, John
console.log(newstr);
(3)使用回调函数来修改匹配到的字符。
小驼峰命名改连字符命名
function styleHyphenFormat(propertyName) {
function upperToHyphenLower(match) {
return '-' + match.toLowerCase();
}
return propertyName.replace(/[A-Z]/g, upperToHyphenLower);
}
console.log(styleHyphenFormat('getName')) // get-name
console.log(styleHyphenFormat('getUserName')) // get-user-name
split
使用指定的分隔符字符串将一个String对象分割成子字符串数组,以一个指定的分割字串来决定每个拆分的位置。
语法:
str.split([separator[, limit]])
参数:
-
separator
:指定表示每个拆分应发生的点的字符串。
separator
可以是一个字符串或正则表达式。 如果纯文本分隔符包含多个字符,则必须找到整个字符串来表示分割点。如果在str中省略或不出现分隔符,则返回的数组包含一个由整个字符串组成的元素。如果分隔符为空字符串,则将str原字符串中每个字符的数组形式返回。 -
limit
:一个整数,限定返回的分割片段数量。当提供此参数时,split 方法会在指定分隔符的每次出现时分割该字符串,但在限制条目已放入数组时停止。如果在达到指定限制之前达到字符串的末尾,它可能仍然包含少于限制的条目。新数组中不返回剩下的文本。
返回值:
返回源字符串以分隔符出现位置分隔而成的一个Array
(1)正则的使用
var names = "Harry Trump ;Fred Barney; Helen Rigby ; Bill Abel ;Chris Hand ";
console.log(names);
var re = /\s*(?:;|$)\s*/;
var nameList = names.split(re);
console.log(nameList);
输出结果
["Harry Trump", "Fred Barney", "Helen Rigby", "Bill Abel", "Chris Hand", ""]
(2)出现分组的情况
var myString = "Hello 1 word. Sentence number 2.";
var splits = myString.split(/(\d)/);
console.log(splits);
输出结果
[ "Hello ", "1", " word. Sentence number ", "2", "." ]
可以看到,会将分组的结果也加入到返回的数组中
正则的贪婪模式
考虑下面代码的输出
console.log('12345678'.match(/\d{3,5}/g))
结果 [123,456] ?
还是[1234,5678] ?
还是[12345] ?
结果是[12345],正则表达式默认是贪婪模式,即尽可能多地匹配,如果失败那么减一匹配直到最小值
非贪婪模式
console.log('12345678'.match(/\d{3,5}?/g))
分组
正则表达式中会出现括号(x) 它会匹配 ‘x’ 并且记住匹配项。其中括号被称为捕获括号。
(1)嵌套括号的情况,两个分组,小分组捕获的是最右边的内容
const reg = /((\w\d){3})/g
const reg1 = /(\w\d){3}/g
const str = 'a1b2c3d4'
console.log(reg.exec(str))
console.log(reg1.exec(str))
输出结果
['a1b2c3', 'a1b2c3', 'c3', index: 0, input: 'a1b2c3d4', groups: undefined]
['a1b2c3', 'c3', index: 0, input: 'a1b2c3d4', groups: undefined ]
(2)括号中有|
符号
const reg2 = /abc(dd|ee)fg/g
str = 'abcddfgabceefg'
console.log(reg2.exec(str))
console.log(reg2.exec(str))
输出结果
['abcddfg','dd',index: 0,input: 'abcddfgabceefg',groups: undefined]
['abceefg','ee',index: 7,input: 'abcddfgabceefg',groups: undefined]
有点明白为什么exec每次只执行一次匹配了,一个括号只捕获一次分组
(3)反向引用
'2015-12-25'.replace(/(\d{4})-(\d{2})-(\d{2})/g,'$2/$3/$1')
// 12/25/2015
(4)忽略分组,不希望捕获某些分组,只需要在分组内加上?:
即可
const reg2 = /abc(?:dd|ee)fg/g
str = 'abcddfgabceefg'
console.log(reg2.exec(str))
console.log(reg2.exec(str))
输出结果
[ 'abcddfg', index: 0, input: 'abcddfgabceefg', groups: undefined ]
[ 'abceefg', index: 7, input: 'abcddfgabceefg', groups: undefined ]
前瞻
(1)正则表达式从文本头部向尾部开始解析,文本尾部方向,称为“前”。
(2)前瞻就是在正则表达式匹配到规则的时候,向前检查是否符合判断,后顾/后瞻方向相反。
(3)JavaScript不支持后顾。
(4)符合和不符合特定断言称为肯定/正向匹配和否定/负向匹配。
(5)前瞻语法:
第一类,正向前瞻(又叫零宽断言)
(?=exp)
const reg = /\w(?=\d)/g
const str = 'a1b2c3d4'
console.log(str.match(reg))
执行结果
[ 'a', 'b', 'c', 'd' ]
第二类,负向前瞻(又叫负向零宽断言)
(?!exp)
const reg = /\w(?!\d)/g
const str = 'a1b2c3d4'
console.log(str.match(reg))
执行结果
[ '1', '2', '3', '4' ]