正则表达式的基本使用
一、什么是正则表达式?
按照某种规则去匹配符条件的字符串。把它叫成规则表达式或许更好理解一些。
二、正则表达式由两种基本字符组成:字面字符(Literal Characters)和元字符(Metacharacters)
- 字面字符:字面字符是指通常以字面形式匹配文本的字符,例如字母、数字、标点符号等。当一个字面字符在正则表达式中出现时,它会尝试与文本中的相应字符进行精确匹配。
- 元字符:元字符是具有特殊含义的字符,用于表示一种或多种字符的模式。它们在正则表达式中具有特殊的语义和功能。一些常见的元字符包括:
^
、$
、.
、*
、+
、?
、|
、()
、[]
等。它们用于表示字符串的起始、结束、任意字符、重复次数、选择、分组等。 - 每个元字符的含义不是唯一的。例如:
^
,在字符类或范围类中它代表取反,用于边界的时候,它代表以 xxx 开始
三、元字符的应用
1. 字符类与字符类取反
正则表达式中的字符串类(Character Classes)用于匹配特定类型的字符。它们表示一组字符中的任意一个字符。
类的符号: []
'abc123'.replace(/[abc]/g, '0') // 000123
正则表达式 /[abc]/g
将匹配字符串中的任何一个字符'a'、'b'或'c'。在 'abc123'
中,它将匹配到'abc'这个子串的每个字符,即'a'、'b'和'c'。然后,使用 '0'
替换所有匹配到的字符,所以最终结果为 '000123'
。
类取反的符号: [^]
'abc123'.replace(/[^abc]/g, '0') // abc000
该正则表达式 [^abc]
使用了否定字符类,它将匹配除了字母 'a'、'b' 和 'c' 以外的任意一个字符。在字符串 'abc123'
中,它会匹配到数字 '1'
、'2'
和 '3'
。然后,使用 '0'
替换所有匹配到的字符,最终结果为 'abc000'
。
2. 范围类
正则表达式中的范围类(Range Classes)用于匹配指定范围内的字符。通过指定字符的起始和结束范围,可以方便地匹配特定范围内的字符。以下是一些常见的范围类及其表示方式:
[a-z]
:匹配从小写字母 'a' 到小写字母 'z' 之间的任意一个字符。[A-Z]
:匹配从大写字母 'A' 到大写字母 'Z' 之间的任意一个字符。[0-9]
:匹配从数字 '0' 到数字 '9' 之间的任意一个字符。[a-zA-Z]
:匹配从小写字母 'a' 到小写字母 'z' 或从大写字母 'A' 到大写字母 'Z' 之间的任意一个字符。- 对于 Unicode 字符,可以使用 Unicode 转义序列来表示范围类。例如,
\u4e00-\u9fff
匹配汉字的范围。
3. 预定义类
正则表达式中的预定义类(Predefined Classes)是一组已经定义好的特殊字符类别,用于匹配特定类型的字符。它们提供了常见的字符类别的快捷方式,以下是一些常见的预定义类及其表示方式:
.
:除了换行符和回车符之外的所有字符,相当于[^\n\r]
\d
:匹配任意一个数字字符,相当于[0-9]
。\D
:匹配任意一个非数字字符,相当于[^0-9]
。\w
:匹配任意一个字母或数字字符,相当于[a-zA-Z0-9]
。\W
:匹配任意一个非字母或非数字字符,相当于[^a-zA-Z0-9]
。\s
:匹配任意一个空白字符,包括空格、制表符、换行符等。\S
:匹配任意一个非空白字符。
4. 边界
正则表达式中的边界(Boundary)用于指定匹配的位置,而不是具体的字符。常见的边界元字符有:
-
^
:匹配输入字符串的开头。例如,正则表达式^abc
可以匹配以 "abc" 开头的字符串。 -
$
:匹配输入字符串的结尾。例如,正则表达式xyz$
可以匹配以 "xyz" 结尾的字符串。 -
\b
:匹配单词边界,即单词和非单词字符之间的位置。例如,正则表达式\bfoo\b
可以匹配 "foo" 这个独立的单词,而不是包含它的字符串。 -
\B
:匹配非单词边界,即非单词字符和单词字符之间的位置。
这些边界元字符可以用于限定匹配的位置,从而更准确地匹配需要的内容。需要注意的是,边界元字符只匹配位置,不匹配具体的字符。在某些特定的情况下,可能需要结合其他元字符或修饰符来实现更复杂的匹配要求。
希望这个解答对你有帮助。如果你有更多关于正则表达式边界的问题,请随时提问。
^:以 xxx 开始,$:以 xxx 结束
'@123@qwe@'.replace(/@/g, 'K') // "K123KqweK"
// 以 @ 开始
'@123@qwe@'.replace(/^@/g, 'K') // "K123@qwe@"
// 以 @ 结束
'@123@qwe@'.replace(/@$/g, 'K') // "@123@qweK"
\b:单词边界、\B:非单词边界
'This is a pen'.replace(/is/g, 'AA') // "ThAA AA a pen"
// 单词边界
'This is a pen'.replace(/\bis\b/g, 'AA') // This AA a pen"
// 非单词边界
'This is a pen'.replace(/\Bis\b/g, 'AA') // "ThAA is a pen"
5. 量词
正则表达式中的量词用于指定模式的重复次数。下面是一些常见的正则表达式量词:
-
*
:匹配前面的元素零次或多次。例如,ab*c
可以匹配 "ac"、"abc"、"abbc" 等字符串。 -
+
:匹配前面的元素一次或多次。例如,ab+c
可以匹配 "abc"、"abbc"、"abbbc" 等字符串,但无法匹配 "ac"。 -
?
:匹配前面的元素零次或一次。例如,ab?c
可以匹配 "ac" 或 "abc",但无法匹配 "abbc"。 -
{n}
:精确匹配前面的元素出现 n 次。例如,a{3}
可以匹配 "aaa",但无法匹配 "aa" 或 "aaaa"。 -
{n,}
:匹配前面的元素至少出现 n 次。例如,a{3,}
可以匹配 "aaa"、"aaaa"、"aaaaaaaa" 等字符串。 -
{n,m}
:匹配前面的元素至少出现 n 次,但不超过 m 次。例如,a{2,4}
可以匹配 "aa"、"aaa"、"aaaa",但无法匹配 "a" 或 "aaaaa"。
需要注意的是,量词默认是贪婪的,即它们会尽可能多地匹配字符。如果想要非贪婪匹配,可以在量词后加上 ?
。例如,a+?
表示非贪婪地匹配一个或多个 "a"。
贪婪模式和非贪婪模式
// 默认贪婪模式
'12345678'.replace(/\d{3,6}/g, '@') // "@78"
// 量词后面加 ?代表非贪婪模式
'12345678'.replace(/\d{3,6}?/g, '@') // "@@78"
6. 分组
- 捕获分组:
使用括号
( )
来创建捕获分组,并将其中的匹配结果保存以便后续引用。例如:
const regex = /(ab)c/
const str = 'abc'
const match = str.match(regex)
console.log(match[0]) // 完整匹配结果 "abc"
console.log(match[1]) // 第一个捕获分组的匹配结果 "ab"
// 分组与替换
// 其中 $1、$2、$3 对应正则表达式中的第一个、第二个、第三个捕获分组。
'2022/01/01'.replace(/(\d{4})\/(\d{2})\/(\d{2})/, '$1-$2-$3') // "2022-01-01"
- 反向引用:
使用
\数字
来引用先前捕获的分组结果,其中数字
是分组的索引。例如:
const regex = /(ab)c\1/
const str = 'abcab'
console.log(regex.test(str)) // 输出 true,因为 "abcab" 匹配 "(ab)c\1",其中 \1 引用了前面捕获的 "ab"
- 非捕获分组:
使用
(?: )
来创建非捕获分组,它可以进行分组但不捕获结果。例如:
const regex = /(?:ab)+c/
const str = 'abababc'
console.log(regex.test(str)) // 输出 true,因为 "abababc" 匹配 "(?:ab)+c",(?:ab) 表示匹配 "ab" 的重复,但不进行捕获
// 第一个分组(?:\d{4})是非捕获分组要忽略,所以只剩下两个分组了,$3已经不存在
'2022/01/01'.replace(/(?:\d{4})\/(\d{2})\/(\d{2})/, '$1-$2-$3') // "01-01-$3"
7. 前瞻后顾断言
- 正则表达式从文本头部向尾部解析,文本尾部是前进方向,所以尾部方向是“前”。
- 前瞻就是正则表达式向前匹配到合适的字符的时候,向前检查是否符合断言。
- 正向前瞻(Positive Lookahead):
正向前瞻用于查找在指定位置之后的内容。它的语法是
(?=pattern)
,其中pattern
是要匹配的模式。例如:
const regex = /abc(?=123)/
const str = 'abc123'
console.log(regex.test(str)) // 输出 true,因为 "abc" 后面跟着 "123",正则表达式匹配成功
- 负向前瞻(Negative Lookahead):
负向前瞻用于查找在指定位置之后 不是 特定内容的情况。它的语法是
(?!pattern)
,其中pattern
是要排除的模式。例如:
const regex = /abc(?!123)/
const str = 'abc456'
console.log(regex.test(str)) // 输出 true,因为 "abc" 后面不是 "123",正则表达式匹配成功
-
后顾(lookbehind assertion)
后顾断言用于匹配某个位置之前的内容,具体使用方法是在正则表达式中使用
(?<=pattern)
的语法,其中pattern
是要匹配的模式。
const regex = /(?<=abc)def/
const str = 'abcdef'
console.log(regex.test(str)) // 输出 true,因为 "def" 前面是 "abc",正则表达式匹配成功
注意,后顾断言只会匹配位置之前的内容,并不会将匹配的内容包含在结果中。在上述的例子中,后顾断言 (?<=abc)
匹配的是字符串中的位置,而不是匹配实际的字符。
后顾断言需要一些版本较新的浏览器才支持,具体情况以下:
-
Google Chrome:Chrome 62 及以上版本开始支持后顾断言。
-
Mozilla Firefox:Firefox 78 及以上版本开始支持后顾断言。
-
Microsoft Edge:Edge 79 及以上版本开始支持后顾断言。
-
Safari:Safari 14 及以上版本开始支持后顾断言。在 iOS 上,后顾断言从 iOS 14 及以上版本开始支持。
四、正则表达式对象的属性
-
source
:返回正则表达式的文本字符串模式。 -
global
:返回一个布尔值,表示正则表达式是否具有全局标志(g)。 -
ignoreCase
:返回一个布尔值,表示正则表达式是否具有忽略大小写标志(i)。 -
multiline
:返回一个布尔值,表示正则表达式是否具有多行标志(m)。 -
sticky
:返回一个布尔值,表示正则表达式是否具有粘滞标志(y)。 -
unicode
:返回一个布尔值,表示正则表达式是否具有 Unicode 标志(u)。
这些属性可以通过正则表达式对象的属性访问符(.)来访问,例如 regex.source
、regex.global
等。
const regex = /abc/gi
console.log(regex.source) // 输出 "abc"
console.log(regex.global) // 输出 true
console.log(regex.ignoreCase) // 输出 true
console.log(regex.multiline) // 输出 false
console.log(regex.sticky) // 输出 false
console.log(regex.unicode) // 输出 false
五、正则表达式对象的方法
1. test()
- 在给定的字符串中测试正则表达式的匹配,并返回一个布尔值,表示是否找到了匹配项。
- 需要注意的是:正则表达式对象的
lastIndex
属性。test()
会让lastIndex
不断向下推移,直至最后一个字符的下一个字符为空才重置 lastIndex 为 0
const reg = /\w/g
// 第一次执行
console.log(reg.test('ab')) // true :校验 a 得出的结果是 true
console.log(reg.lastIndex) // 1 :a 的下一位 b 的 lastIndex 是 1
// 第二次执行
console.log(reg.test('ab')) // true :校验 b 得出的结果是 true
console.log(reg.lastIndex) // 2 :b 的下一位是空它的 lastIndex 是 2
// 第三次执行
console.log(reg.test('ab')) // false :校验 空 得出的结果是 false
console.log(reg.lastIndex) // 0 :重置 lastIndex 为 0。又重新从 a 开始检验
// 第四次执行
console.log(reg.test('ab')) // true :校验 a 得出的结果是 true
console.log(reg.lastIndex) // 1 :a 的下一位 b 的 lastIndex 是 1
// 继续执行,会继续循环这个过程...
2. exec()
exec()
用于在给定的字符串中执行正则表达式的匹配操作,并返回一个数组,其中包含找到的首个匹配项的相关信息。如果没有找到匹配项,则返回 null。
exec()
方法在执行匹配时,会返回一个数组,其中包含以下信息:
- 索引 0 处的元素是整个匹配的文本。
- 从索引 1 开始的元素是与正则表达式中的捕获组匹配的文本(如果正则表达式中包含捕获组)。
index
属性表示匹配的起始位置。input
属性表示原始输入字符串。
const regex = /abc/gi
const str = 'ABC abc'
console.log(regex.exec(str)) // ['ABC', index: 0, input: 'ABC abc', groups: undefined]
console.log(regex.lastIndex) // 3
console.log(regex.exec(str)) // ['abc', index: 4, input: 'ABC abc', groups: undefined]
console.log(regex.lastIndex) // 7
console.log(regex.exec(str)) // null
在上面的示例中,首先执行了一次 exec()
方法,找到了第一个匹配项 "ABC"。然后,lastIndex
属性被设置为 3,表示下一次匹配的开始位置。再次执行 exec()
方法时,找到了第二个匹配项 "abc",lastIndex
也相应地更新为 7。最后一次执行 exec()
方法时,没有找到匹配项,返回了 null。
当使用 g
全局标志进行匹配时,exec()
方法会在每次匹配时更新 lastIndex
属性。
const reg = /\w/g
const str = 'ABCD'
while ((match = reg.exec(str)) !== null) {
console.log('匹配到的字符串是:' + match[0])
console.dir('lastIndex更新为:' + reg.lastIndex)
}
打印结果:
匹配到的字符串是:A
lastIndex更新为:1
匹配到的字符串是:B
lastIndex更新为:2
匹配到的字符串是:C
lastIndex更新为:3
匹配到的字符串是:D
lastIndex更新为:4
不使用全局标志,每次调用 exec()
方法都会返回相同的匹配项。
const reg = /\w/
const str = 'ABCD'
while ((match = reg.exec(str)) !== null) {
console.log('匹配到的字符串是:' + match[0])
console.dir('lastIndex更新为:' + reg.lastIndex)
}
不使用全局标志 每次都是匹配到 A 打印结果会一直循环打印:
匹配到的字符串是:A
lastIndex更新为:1
3. match()
match()
用于在给定的字符串中查找与正则表达式匹配的部分,并返回一个数组,其中包含所有找到的匹配项。如果没有找到匹配项,则返回 null。
const regex = /ab(c+)/gi
const str = 'ABC abccc DEF abcc GHI'
const result = str.match(regex)
console.log(result) // ['ABC', 'abccc', 'abcc']
需要注意的是,若正则表达式具有全局标志(g
),match()
方法将返回所有找到的匹配项的数组。如果正则表达式没有全局标志,match()
方法将只返回第一个匹配项的数组。
除了返回匹配项的数组外,match()
方法还支持使用捕获组。如果正则表达式中有捕获组,match()
将在数组中包含匹配项和捕获组的值。
const regex = /(\w+)\s(\w+)/
const str = 'John Doe'
const result = str.match(regex)
console.log(result) // ['John Doe', 'John', 'Doe', index: 0, input: 'John Doe', groups: undefined]
在上述示例中,第一个元素是完整的匹配项,即 "John Doe"。后续元素是与每个捕获组 (w+) 相匹配的文本,分别是 "John" 和 "Doe"。
4. search()
search()
用于在给定的字符串中搜索与正则表达式匹配的部分,并返回匹配的起始位置。如果未找到匹配项,则返回 -1。
const regex = /abc/i
const str = 'ABC abccc'
const result = str.search(regex)
console.log(result) // 输出 0
在上述示例中,我们使用正则表达式 /abc/i
搜索字符串 "ABC abccc"
。
search()
方法找到了匹配项 "ABC" 并返回其起始位置,即 0。
如果未找到匹配项,search()
方法将返回 -1。
const regex = /def/i
const str = 'ABC abccc'
const result = str.search(regex)
console.log(result) // -1
在上例中,正则表达式 /def/i
在字符串中未找到匹配项,因此 search()
方法返回 -1。
需要注意的是当使用 search()
方法时,它将只返回第一个匹配项的起始位置。如果您需要获取所有匹配项的位置,可以使用带有全局标志 (g
) 的正则表达式,并结合 exec()
和循环来获取每个匹配项的起始位置。
const regex = /abc/gi
const str = 'ABC abccc ABC abc'
let matches = []
let match
while ((match = regex.exec(str)) !== null) {
matches.push(match.index)
}
console.log(matches) // 0, 4, 10, 14]
5. replace()
replace()
用于替换字符串中与正则表达式匹配的部分。
const regex = /world/i
const str = 'Hello, world!'
const replacement = 'John'
const result = str.replace(regex, replacement)
console.log(result) // "Hello, John!"
除了字符串,还可以使用函数作为替换项,该函数将根据匹配的内容动态生成替换项。
const regex = /(\w+)\s(\w+)/
const str = 'John Doe'
const result = str.replace(regex, (match, p1, p2) => {
return p2 + ', ' + p1
})
console.log(result) // 输出 "Doe, John"
在上述示例中,我们使用正则表达式 /(\w+)\s(\w+)/
在字符串 "John Doe"
中匹配名字和姓氏。然后我们使用一个函数进行替换,将姓氏和名字颠倒位置,即 "Doe, John"。
replace()
方法只会替换第一个匹配项。如果要替换所有匹配项,请使用全局标志 (g
)。
replace()
方法的第二个参数是一个函数,该函数将在每次匹配到字符串时被调用,并且它可以接收的参数有:
match
:匹配到的字符串。p1, p2, ...
:每个捕获组对应的值。这些参数的数量取决于正则表达式中的捕获组数量。
6. split()
split()
用于将字符串分割为一个字符串数组,基于正则表达式匹配的位置进行分割。
split()
方法的语法如下:
str.split(regexp, limit)
其中,str
是要进行分割的字符串,regexp
是一个正则表达式对象,limit
是一个可选参数,用于限制返回的数组的最大长度。
const regex = /[\s,]+/
// 示例1
const str1 = 'Hello, World!'
const result1 = str1.split(regex)
console.log(result1) // ["Hello", "World!"]
// 示例2
const str2 = 'apple,banana,orange'
const result2 = str2.split(regex)
console.log(result2) // ["apple", "banana", "orange"]
// 示例3(限制返回的数组长度)
const str3 = 'one two three four'
const result3 = str3.split(regex, 2)
console.log(result3) // ["one", "two"]
// 示例4(使用特殊字符作为分割点)
const str4 = 'Hello*World|Javascript'
const regex2 = /[*|]/ // 使用 * 或 | 作为分割点
const result4 = str4.split(regex2)
console.log(result4) // ["Hello", "World", "Javascript"]
六、其他
字符 | 含义 |
---|---|
\t | 水平制表符 |
\v | 垂直制表符 |
\n | 换行符 |
\r | 回车符 |
\0 | 空字符 |
\f | 换页符 |
\cX | 与 X 对应的控制字符(Ctrl+X) |