# JavaScript正则表达式详解

正则表达式(Regular Expression)是一种用于匹配字符串中字符组合的模式。在JavaScript中,正则表达式是一个强大的工具,可以用于验证表单、处理文本等场景。

# 1. 元字符

元字符是正则表达式中最基本的构建块,它们具有特殊含义。

# 1.1 边界字符

字符 描述
\ 转义符,将下一个字符标记为特殊字符或原义字符。例如,'n' 匹配字符 "n",而 '\n' 匹配一个换行符
^ 匹配输入字符串的开始位置。如果设置了 RegExp 对象的 Multiline 属性,^ 也匹配 '\n''\r' 之后的位置
$ 匹配输入字符串的结束位置。如果设置了 RegExp 对象的 Multiline 属性,$ 也匹配 '\n''\r' 之前的位置
\b 匹配一个单词边界,即单词和空格之间的位置。例如,'er\b' 可以匹配 "never" 中的 'er',但不能匹配 "verb" 中的 'er'
\B 匹配非单词边界

# 1.2 字符组

字符 描述
[] 字符集合。匹配所包含的任意一个字符。例如,'[abc]' 可以匹配 "plain" 中的 'a'
() 捕获组(子表达式)。所获取的匹配可以从产生的 Matches 集合得到
x\|y 匹配 x 或 y。例如,'z\|food' 能匹配 "z" 或 "food"。'(z\|f)ood' 则匹配 "zood" 或 "food"

# 1.3 量词

字符 描述
* 0到无数次
+ 1到无数次
? 0 或者1 次(非贪婪模式后面讲)
{n} 重复N次
{n,} 重复至少N次
{n,m} n到m次

# 1.4 特殊字符

字符 描述
. 匹配除换行符(\n\r)之外的任何单个字符
\s 匹配任何空白字符,包括空格、制表符、换页符等。等价于 [\f\n\r\t\v]
\S 匹配任何非空白字符。等价于 [^ \f\n\r\t\v]
\w 匹配字母、数字、下划线。等价于 [A-Za-z0-9_]
\W 匹配非字母、数字、下划线。等价于 [^A-Za-z0-9_]
\d 匹配一个数字字符。等价于 [0-9]
\D 匹配一个非数字字符。等价于 [^0-9]

# 1.5 转义

在字符串中使用正则表达式时,需要注意转义字符的使用。正则表达式本身也需要转义。

// 使用字符串创建正则表达式时,需要双重转义
new RegExp('\\d+').exec('abc123de'); // 123
new RegExp('d+').exec('abc123dde'); // dd

new RegExp(/\d+/i); // ES5
new RegExp(/\d+/, 'i'); // ES6写法 ES5报错

/\d+/.exec('abc123de'); // 123
1
2
3
4
5
6
7
8

# 2. 修饰符

修饰符用于指定正则表达式的匹配行为。

字符 描述
i 执行对大小写不敏感的匹配 (case-insensitive)
g 执行全局匹配(查找所有匹配而非在找到第一个匹配后停止)(global)
m 执行多行匹配。改变 ^$ 的行为,使它们分别匹配每一行的开头和结尾,而不是整个字符串的开头和结尾 (multiline)
s 允许 . 匹配换行符 (dotAll)
y 执行“粘性”搜索,匹配从目标字符串的当前位置开始的匹配项 (sticky)
(/yu[a-z]+/i).test('Yuhoo') // true
// eslint-disable-next-line
/yu[a-z]+/.test('Yuhoo') // false

'yuhoo Yuki'.match(/yu[a-z]+/ig); // ["yuhoo", "Yuki"]
'1a2b3c4d'.match(/\d[a-z]\d/g); // ["1a2", "3c4"]
// 从上次匹配结尾再次进行匹配

'yuhoo\nYuki'.match(/^yu[a-z]+/ig); // ["yuhoo"]
'yuhoo\nYuki'.match(/^yu[a-z]+/igm); // ["yuhoo", "Yuki"]

'yuhoo\nYuki'.match(/yu(.)+/ig); // ["yuhoo", "Yuki"]
'yuhoo\nYuki'.match(/yu(.)+/isg); // ['yuhoo\nYuki']

'1a2b3c4e5f'.match(/\d[a-z]\d/g); // ["1a2", "3c4"]
'1a2b3c4e5f'.match(/\d[a-z]\d/yg); // ["1a2"]
'1a22b33c44e5f'.match(/\d[a-z]\d/yg); // ["1a2", "2b3", "3c4", "4e5"]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 3. JavaScript中的正则表达式方法

JavaScript 提供了多种使用正则表达式的方法。

# 3.1 String对象的方法

  • 检索字符串中指定的子字符串,或检索与正则表达式相匹配的子字符串
  • 一个与 regexp 相匹配的子串的起始位置
  • 不执行全局匹配,它将忽略标志 g
  • 返回正则表达式在字符串中首次匹配项的索引,否则,返回 -1
'yuhoo\nYuki'.search(/Yu(.)+/ig); // 0
1

# 3.1.2 match()

  • 如果使用g标志,则将返回与完整正则表达式匹配的所有结果(Array),但不会返回捕获组,或者未匹配 null
  • 如果未使用g标志,则仅返回第一个完整匹配及其相关的捕获组(Array)。 在这种情况下,返回的项目将具有如下所述的其他属性,或者未匹配 null。
    • groups: 一个捕获组数组 或 undefined(如果没有定义命名捕获组)
    • index: 匹配的结果的开始位置
    • input: 搜索的字符串
'yuhoo Yuki'.match(/yu([a-z])+/i);
// [
//   0: "yuhoo"
//   1: "o"
//   groups: undefined
//   index: 0
//   input: "yuhoo Yuki"
// ]
'yuhoo Yuki'.match(/yu[a-z]+/ig); // ["yuhoo", "Yuki"]
1
2
3
4
5
6
7
8
9

# 3.1.3 replace()

用于在字符串中用一些字符替换另一些字符,或替换一个与正则表达式匹配的子串。返回替换后的新字符串。

'jarvis yuhoo tab simba'.replace(/(\w+)\s*(\w+)/, '$2 $1');
// "yuhoo jarvis tab simba"
'jarvis yuhoo tab simba'.replace(/\b(\w+)\b/g, (name) => {
  return name.substring(0, 1).toUpperCase() + name.substring(1);
});
// "Jarvis Yuhoo Tab Simba"
'jarvis yuhoo tab simba'.replace(/\b(\w)(\w+)\b/g, (name, $1, $2) => {
  console.log(name, $1, $2);
  return $1.toUpperCase() + $2;
});
// "Jarvis Yuhoo Tab Simba"
1
2
3
4
5
6
7
8
9
10
11

# 3.1.4 split()

用于把一个字符串分割成字符串数组。返回一个字符串数组。

const str = 'JavaScript is great! JavaScript is powerful!';
console.log(str.split(/\s+/)); // ['JavaScript', 'is', 'great!', 'JavaScript', 'is', 'powerful!']
console.log(str.split(/\s+/, 3)); // ['JavaScript', 'is', 'great!']
1
2
3

# 3.2 RegExp对象的方法

# 3.2.1 test()

用于检测一个字符串是否匹配某个模式。返回 true 或 false。

const str = 'JavaScript is great!';
console.log(/javascript/i.test(str)); // true
console.log(/bad/i.test(str)); // false
1
2
3

# 3.2.2 exec()

用于检索字符串中的正则表达式的匹配。返回一个数组,如果没有匹配结果则返回 null。如果使用了 g 修饰符,则每次调用 exec() 都会从 lastIndex 开始查找。

lastIndex 属性用于指定下一次匹配开始的位置。只有正则表达式使用了表示全局检索的 "g" 标志时,该属性才会起作用。

const r = new RegExp(/\d[a-z]\d/, 'g');
console.log(r.lastIndex); // 0
r.exec('1a2b3c4d5e'); // "1a2"
console.log(r.lastIndex); // 3
r.exec('1a2b3c4d5e'); // "3c4"
console.log(r.lastIndex); // 7
1
2
3
4
5
6

# 3.2.3 compile()

用于改变 RegExp 对象的匹配模式。该方法已废弃,不推荐使用。

# 4. 贪婪模式和非贪婪模式

# 4.1 贪婪匹配模式

贪婪匹配模式会尽可能多地匹配符合条件的内容。

标识符: + ? * {n} {n,} {n,m}

# 4.2 非贪婪匹配模式

非贪婪匹配模式会尽可能少地匹配符合条件的内容,一旦发现匹配符合要求,立马就匹配成功,而不会继续匹配下去(除非有g,开启下一组匹配)。

标识符: +? ?? *? {n}? {n,}? {n,m}?

# 4.3 回溯现象

'a \'jarvis\' b \'tim\' c \'yuhoo\' d'.match(/'.*'/g); // ["'jarvis' b 'tim' c 'yuhoo'"]
'a \'jarvis\' b \'tim\' c \'yuhoo\' d'.match(/'.*?'/g); // ["'jarvis'", "'tim'", "'yuhoo'"]
1
2

正则表达式在匹配过程中可能会发生回溯,这是匹配算法的一部分。

贪婪匹配过程分析:

  1. 从字符串开始查找匹配字符 ```
  2. 找到第3个字符发现匹配,接着看后面的匹配字符
  3. . 匹配换行以外任意字符,而其后面有连词 *,表示0次以上,所以其匹配了字符 jarvis' b 'tim' c 'yuhoo' d
  4. 然后匹配字符 \``,发现无法匹配,所以回溯,前面的.*` 回溯一次
  5. 字符d与匹配字符不匹配,接着向前回溯
  6. 最后找到倒数第3个字符匹配成功,输出匹配字符 'jarvis' b 'tim' c 'yuhoo'

非贪婪匹配过程分析:

  1. 从字符串开始查找匹配字符 ```
  2. 找到第3个字符发现匹配,接着看后面的匹配字符
  3. . 匹配换行以外任意字符,而其后面有连词 *?,所以什么都不匹配,直接匹配后面的规则
  4. 字符y与匹配字符 \`` 不匹配,进行回溯,前面的匹配规则.*?` 匹配字符y
  5. 字符u与匹配字符 \`` 不匹配,进行回溯,前面的匹配规则.*?` 匹配字符yu
  6. 直到匹配第8个字符,完成第一次匹配 'jarvis'
'jarvisTestAb'.match(/jarvis([a-zA-Z]*)/); // "jarvisTestAb", "TestAb"
'jarvisTestAb'.match(/jarvis([a-zA-Z])*/); // "jarvisTestAb", "b"
'jarvisTestAb'.match(/jarvis([a-zA-Z][a-zA-Z])*/); // "jarvisTestAb", "Ab"
'jarvisTestAbc'.match(/jarvis([a-zA-Z][a-zA-Z])*/); // "jarvisTestAb", "Ab"
1
2
3
4

根据上面例子,为什么第二个捕获组中 *在括号里面和外面不一样。我们一个个来解释

  • 第一个我们看到[a-z]*是说匹配一个或者多个字符,根据贪婪匹配规则,匹配到TestAb
  • 第二个([a-z])*是说匹配一个字母一次多次匹配,最后一次匹配字母b
  • 第三个([a-z][a-z])*是说一次或者多次匹配2个字母,最后一次啊匹配"Ab",所以被捕获到
  • 第四个([a-z][a-z])*是说一次或者多次匹配2个字母,最后一次啊匹配"Ab",而最后的字母c不能被匹配,所以被捕获到

# 4.4 捕获组中的量词

// 匹配一个或多个字符
const result1 = 'jarvisTestAb'.match(/jarvis([a-zA-Z]*)/);
console.log(result1); // ["jarvisTestAb", "TestAb", index: 0, input: 'jarvisTestAb', groups: undefined]

// 匹配一个字母,多次匹配,最后一次匹配字母b
const result2 = 'jarvisTestAb'.match(/jarvis([a-zA-Z])*/);
console.log(result2); // ["jarvisTestAb", "b", index: 0, input: 'jarvisTestAb', groups: undefined]

// 一次或多次匹配2个字母,最后一次匹配"Ab"
const result3 = 'jarvisTestAb'.match(/jarvis([a-zA-Z][a-zA-Z])*/);
console.log(result3); // ["jarvisTestAb", "Ab", index: 0, input: 'jarvisTestAb', groups: undefined]

// 一次或多次匹配2个字母,最后一次匹配"Ab",而最后的字母c不能被匹配
const result4 = 'jarvisTestAbc'.match(/jarvis([a-zA-Z][a-zA-Z])*/);
console.log(result4); // ["jarvisTestAb", "Ab", index: 0, input: 'jarvisTestAbc', groups: undefined]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

在第二个捕获组中,* 在括号里面和外面的区别:

  1. [a-z]* 表示匹配一个或多个字符,根据贪婪匹配规则,匹配到 TestAb
  2. ([a-z])* 表示匹配一个字母,多次匹配,最后一次匹配字母 b
  3. ([a-z][a-z])* 表示一次或多次匹配2个字母,最后一次匹配 "Ab",所以被捕获到
  4. ([a-z][a-z])* 表示一次或多次匹配2个字母,最后一次匹配 "Ab",而最后的字母c不能被匹配,所以被捕获到

# 5. 环视结构

环视(Lookaround)也被称为断言(Assertion)或零宽断言(Zero-width assertion)。

环视是一种非捕获型分组,即不保存匹配到的内容,只进行匹配检查。

环视匹配到的内容不包括在最终的匹配结果中,即匹配的结果不包括环视的内容。

表达式 说明
(?<=Expression) 逆序肯定环视,表示所在位置左侧能够匹配Expression
(?<!Expression) 逆序否定环视,表示所在位置左侧不能匹配Expression
(?=Expression) 顺序肯定环视,表示所在位置右侧能够匹配Expression
(?!Expression) 顺序否定环视,表示所在位置右侧不能匹配Expression
'https://xxx.com/path?id=12&chn=234'.match(/(?<=\?|#|&)id=([^&#]*)(&|#|$)/g); // ["id=12&"]
'https://xxx.com/path?id=12&chn=234'.match(/(?<=\?|#|&)id=([^&#]*)(?=&|#|$)/g); // ["id=12"]
'1234567890.12'.replace(/(?<=\d)(?=(\d{3})+(?!\d))/g, ','); // "1,234,567,890.12"
1
2
3

主要看最后一个例子

我们拆开来分析

  • (\d{3}) 匹配多个连续3个数
  • (\d{3})+(?!\d) 匹配多个连续3个数且其右边与非数字字符相连
  • (?=(\d{3})+(?!\d)) 匹配一个位置 该位置右边为多个连续3个数且其右边与非数字字符相连
  • (?<=\d)(?=(\d{3})+(?!\d))匹配一个位置 该位置左边是一个数字 该位置右边为多个连续3个数且其右边与非数字字符相连

补充:最近开发时发现微信不支持逆序环视 ?<= ?<!会报错

# 6. 换行符

# 6.1 正则相关

字符 描述
\s 匹配任何空白字符,包括空格、换页(\f)、换行(\n)、回车(\r)、水平制表符(\t)、垂直制表符(\v),等价于 [ \f\n\r\t\v]
\n 匹配一个换行符。等价于 \x0a\cJ
\r 匹配一个回车符。等价于 \x0d\cM

# 起源

在计算机还没有出现之前,有一种叫做电传打字机(Teletype Model 33)的玩意,每秒钟可以打10个字符。但是它有一个问题,就是打完一行换行的时候,要用去0.2秒,正好可以打两个字符。要是在这0.2秒里面,又有新的字符传过来,那么这个字符将丢失。

于是,研制人员想了个办法解决这个问题,就是在每行后面加两个表示结束的字符。一个叫做“回车”,告诉打字机把打印头定位在左边界;另一个叫做“换行”,告诉打字机把纸向下移一行。

# 6.2 区别

Unix系统里,每行结尾只有“<换行>”,即“\n”;Windows系统里面,每行结尾是“<回车><换行>”,即“\r\n”;Mac系统里,每行结尾是“<回车>”。一个直接后果是,Unix/Mac系统下的文件在Windows里打开的话,所有文字会变成一行;而Windows里的文件在Unix/Mac下打开的话,在每行的结尾可能会多出一个^M符号。

环境 换行
win \r\n
linux unix \n
mac os \r

# 6.3 实例分析

在linux环境 输入命令

echo -en '1\n2\r\n3' > test.txt
vim test.txt
1
2

查看到内容如下

1
2^M
3
1
2
3

文件在win环境中打开显示

12
3
1
2

如果是一个win文件在linux中打开发现没有^M标记

按理说每个换行后面应该多一个^M标记 但是因为linux系统会检测文件换行格式,如果是\r\n会自动标识为win环境进行换行。

# 6.4 js使用换行

# 6.4.1 alert 弹框换行结果

// eslint-disable-next-line no-alert
alert('你好<br/>我是yuhoo'); // 不换行打印标签</br>
// eslint-disable-next-line no-alert
alert('你好\n我是yuhoo'); // 换行
1
2
3
4

# 6.4.2 innerHTML/ html( )/v-html方法中换行结果

document.getElementsByClassName('header')[0].innerHTML = '你好<br>请换行'; // 换行
document.getElementsByClassName('header')[0].innerHTML = '你好\n请换行'; // 不换行
// 测试网址 https://www.runoob.com/regexp/regexp-tutorial.html
1
2
3

# 6.4.3 innerText/ text( )/v-text方法中使用\n换行结果

Vue项目中v-text中使用\n效果同v-html 不会换行 使用正则替换为<br> XXX.replace(/\n/gm,"<br>")

document.getElementsByClassName('header')[0].innerText = '你好<br>请换行'; // 不换行
document.getElementsByClassName('header')[0].innerText = '你好\n请换行'; // 换行
1
2

# 6.5 软回车与硬回车

软回车是用 Shift + Enter 产生的,它换行,但是并不换段,即前后两段文字在 Word 中属于同一“段”

Last Updated: 8/19/2025, 4:28:59 PM