正则表达式的基本使用

974 阅读3分钟

正则表达式的基本使用

一、什么是正则表达式?

按照某种规则去匹配符条件的字符串。把它叫成规则表达式或许更好理解一些。

二、正则表达式由两种基本字符组成:字面字符(Literal Characters)和元字符(Metacharacters)

  1. 字面字符:字面字符是指通常以字面形式匹配文本的字符,例如字母、数字、标点符号等。当一个字面字符在正则表达式中出现时,它会尝试与文本中的相应字符进行精确匹配。
  2. 元字符:元字符是具有特殊含义的字符,用于表示一种或多种字符的模式。它们在正则表达式中具有特殊的语义和功能。一些常见的元字符包括:^$.*+?|()[]等。它们用于表示字符串的起始、结束、任意字符、重复次数、选择、分组等。
  3. 每个元字符的含义不是唯一的。例如:^,在字符类或范围类中它代表取反,用于边界的时候,它代表以 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)用于匹配指定范围内的字符。通过指定字符的起始和结束范围,可以方便地匹配特定范围内的字符。以下是一些常见的范围类及其表示方式:

  1. [a-z]:匹配从小写字母 'a' 到小写字母 'z' 之间的任意一个字符。
  2. [A-Z]:匹配从大写字母 'A' 到大写字母 'Z' 之间的任意一个字符。
  3. [0-9]:匹配从数字 '0' 到数字 '9' 之间的任意一个字符。
  4. [a-zA-Z]:匹配从小写字母 'a' 到小写字母 'z' 或从大写字母 'A' 到大写字母 'Z' 之间的任意一个字符。
  5. 对于 Unicode 字符,可以使用 Unicode 转义序列来表示范围类。例如,\u4e00-\u9fff 匹配汉字的范围。

3. 预定义类

正则表达式中的预定义类(Predefined Classes)是一组已经定义好的特殊字符类别,用于匹配特定类型的字符。它们提供了常见的字符类别的快捷方式,以下是一些常见的预定义类及其表示方式:

  1. .:除了换行符和回车符之外的所有字符,相当于 [^\n\r]
  2. \d:匹配任意一个数字字符,相当于 [0-9]
  3. \D:匹配任意一个非数字字符,相当于 [^0-9]
  4. \w:匹配任意一个字母或数字字符,相当于 [a-zA-Z0-9]
  5. \W:匹配任意一个非字母或非数字字符,相当于 [^a-zA-Z0-9]
  6. \s:匹配任意一个空白字符,包括空格、制表符、换行符等。
  7. \S:匹配任意一个非空白字符。

4. 边界

正则表达式中的边界(Boundary)用于指定匹配的位置,而不是具体的字符。常见的边界元字符有:

  1. ^:匹配输入字符串的开头。例如,正则表达式^abc可以匹配以 "abc" 开头的字符串。

  2. $:匹配输入字符串的结尾。例如,正则表达式xyz$可以匹配以 "xyz" 结尾的字符串。

  3. \b:匹配单词边界,即单词和非单词字符之间的位置。例如,正则表达式\bfoo\b可以匹配 "foo" 这个独立的单词,而不是包含它的字符串。

  4. \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. 量词

正则表达式中的量词用于指定模式的重复次数。下面是一些常见的正则表达式量词:

  1. *:匹配前面的元素零次或多次。例如,ab*c可以匹配 "ac"、"abc"、"abbc" 等字符串。

  2. +:匹配前面的元素一次或多次。例如,ab+c可以匹配 "abc"、"abbc"、"abbbc" 等字符串,但无法匹配 "ac"。

  3. ?:匹配前面的元素零次或一次。例如,ab?c可以匹配 "ac" 或 "abc",但无法匹配 "abbc"。

  4. {n}:精确匹配前面的元素出现 n 次。例如,a{3}可以匹配 "aaa",但无法匹配 "aa" 或 "aaaa"。

  5. {n,}:匹配前面的元素至少出现 n 次。例如,a{3,}可以匹配 "aaa"、"aaaa"、"aaaaaaaa" 等字符串。

  6. {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. 分组

  1. 捕获分组: 使用括号 ( ) 来创建捕获分组,并将其中的匹配结果保存以便后续引用。例如:
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"
  1. 反向引用: 使用 \数字 来引用先前捕获的分组结果,其中 数字 是分组的索引。例如:
const regex = /(ab)c\1/
const str = 'abcab'
console.log(regex.test(str)) // 输出 true,因为 "abcab" 匹配 "(ab)c\1",其中 \1 引用了前面捕获的 "ab"
  1. 非捕获分组: 使用 (?: ) 来创建非捕获分组,它可以进行分组但不捕获结果。例如:
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. 前瞻后顾断言

  • 正则表达式从文本头部向尾部解析,文本尾部是前进方向,所以尾部方向是“前”。
  • 前瞻就是正则表达式向前匹配到合适的字符的时候,向前检查是否符合断言。
  1. 正向前瞻(Positive Lookahead): 正向前瞻用于查找在指定位置之后的内容。它的语法是 (?=pattern),其中 pattern 是要匹配的模式。例如:
const regex = /abc(?=123)/
const str = 'abc123'
console.log(regex.test(str)) // 输出 true,因为 "abc" 后面跟着 "123",正则表达式匹配成功
  1. 负向前瞻(Negative Lookahead): 负向前瞻用于查找在指定位置之后 不是 特定内容的情况。它的语法是 (?!pattern),其中 pattern 是要排除的模式。例如:
const regex = /abc(?!123)/
const str = 'abc456'
console.log(regex.test(str)) // 输出 true,因为 "abc" 后面不是 "123",正则表达式匹配成功
  1. 后顾(lookbehind assertion)

    后顾断言用于匹配某个位置之前的内容,具体使用方法是在正则表达式中使用 (?<=pattern) 的语法,其中 pattern 是要匹配的模式。

const regex = /(?<=abc)def/
const str = 'abcdef'
console.log(regex.test(str)) // 输出 true,因为 "def" 前面是 "abc",正则表达式匹配成功

注意,后顾断言只会匹配位置之前的内容,并不会将匹配的内容包含在结果中。在上述的例子中,后顾断言 (?<=abc) 匹配的是字符串中的位置,而不是匹配实际的字符。

后顾断言需要一些版本较新的浏览器才支持,具体情况以下:

  1. Google Chrome:Chrome 62 及以上版本开始支持后顾断言。

  2. Mozilla Firefox:Firefox 78 及以上版本开始支持后顾断言。

  3. Microsoft Edge:Edge 79 及以上版本开始支持后顾断言。

  4. Safari:Safari 14 及以上版本开始支持后顾断言。在 iOS 上,后顾断言从 iOS 14 及以上版本开始支持。

四、正则表达式对象的属性

  1. source:返回正则表达式的文本字符串模式。

  2. global:返回一个布尔值,表示正则表达式是否具有全局标志(g)。

  3. ignoreCase:返回一个布尔值,表示正则表达式是否具有忽略大小写标志(i)。

  4. multiline:返回一个布尔值,表示正则表达式是否具有多行标志(m)。

  5. sticky:返回一个布尔值,表示正则表达式是否具有粘滞标志(y)。

  6. unicode:返回一个布尔值,表示正则表达式是否具有 Unicode 标志(u)。

这些属性可以通过正则表达式对象的属性访问符(.)来访问,例如 regex.sourceregex.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)

七、相关

regexper.com/