WinddSnow

JavaScript-Strings-and-Regular-Expressions

字数统计: 4k阅读时长: 17 min
2026/07/30

第53课:字符串与正则表达式——模板字符串、match/replace、正则语法、RegExp 对象

字符串是文本的载体,正则表达式是模式匹配的利器。在表单验证、数据提取、搜索替换、日志分析等场景中,字符串方法与正则表达式密不可分。本节课将系统讲解 ES6 模板字符串的标签函数与高级用法、字符串的原型方法(特别是与正则配合的 matchreplacesearchsplit),以及 JavaScript 正则表达式的完整语法——从元字符、量词、分组到断言——最后介绍 RegExp 构造函数和标志位的行为细节。


1. 模板字符串的进阶用法

1.1 多行字符串与表达式插值

模板字符串使用反引号 ` 定义,支持**跨行**和 ${} 表达式嵌入。

1
2
3
4
5
6
7
8
9
10
11
const name = 'Alice';
const age = 25;

const message = `姓名:${name}
年龄:${age}
明年年龄:${age + 1}`;

console.log(message);
// 姓名:Alice
// 年龄:25
// 明年年龄:26

所有 ${} 内的表达式在模板字符串定义时立即求值,并调用其 .toString() 方法转为字符串。

1.2 标签函数(Tagged Templates)

标签函数是模板字符串的高级特性:一个函数可以“解析”模板字符串的静态部分和动态插值,返回任意结果(不一定是字符串)。这对于构建安全的 HTML 模板、国际化、CSS-in-JS 等非常有用。

1
2
3
4
5
6
7
8
9
10
11
12
13
function highlight(strings, ...values) {
// strings: 静态字符串数组
// values: 插值数组
return strings.reduce((result, str, i) => {
const value = values[i] ? `<mark>${values[i]}</mark>` : '';
return result + str + value;
}, '');
}

const user = 'Bob';
const action = '登录';
const output = highlight`用户 ${user} 刚刚${action}了系统。`;
console.log(output); // '用户 <mark>Bob</mark> 刚刚<mark>登录</mark>了系统。'

标签函数的第一个参数 strings 是模板字符串被插值分割后的静态字符串数组,它还有一个 .raw 属性,包含原始未转义的字符串(反斜杠保留)。

1
2
3
4
5
function rawDemo(strings) {
console.log(strings[0]); // '路径:C:\\Users\\Alice'
console.log(strings.raw[0]); // '路径:C:\\Users\\Alice'(raw 下双反斜杠显示为单)
}
rawDemo`路径:C:\\Users\\Alice`;

1.3 模板字符串用于 HTML 转义

利用标签函数可以构建防止 XSS 的 HTML 转义功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
function safeHtml(strings, ...values) {
const escape = str => String(str)
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;');
return strings.reduce((result, str, i) =>
result + str + (i < values.length ? escape(values[i]) : ''), '');
}

const userInput = '<script>alert("xss")</script>';
const html = safeHtml`<div>${userInput}</div>`;
console.log(html); // <div>&lt;script&gt;alert(&quot;xss&quot;)&lt;/script&gt;</div>

2. 正则表达式基础语法

正则表达式是一种描述字符模式的对象,用于匹配、搜索、替换字符串中的特定子串。

2.1 创建正则的两种方式

1
2
3
4
5
// 字面量(编译时编译,性能更优,适合固定模式)
const regex1 = /hello/i;

// 构造函数(运行时编译,适合动态模式)
const regex2 = new RegExp('hello', 'i');

当模式中包含变量时,只能使用构造函数:

1
2
const searchTerm = 'hello';
const regex = new RegExp(searchTerm, 'gi');

注意:构造函数中,反斜杠需要双重转义。例如要匹配一个反斜杠 \,字面量写为 /\\/,构造函数写为 new RegExp('\\\\')

2.2 标志位(Flags)

标志 名称 说明
i ignoreCase 忽略大小写匹配。
g global 全局匹配(查找所有匹配项,而非在第一个匹配后停止)。
m multiline 多行模式:^$ 匹配一行的开始和结束,而非整个字符串。
s dotAll (ES2018) 允许 . 匹配换行符 \n
u unicode (ES6) 开启 Unicode 支持(正确处理代理对,支持 \u{...} 语法)。
y sticky 粘性匹配:仅从 lastIndex 指示的位置开始匹配,不向前搜索。

2.3 元字符速览

字符/模式 含义
. 匹配任意单个字符(默认不含换行,s 标志下含换行)。
\d 数字 [0-9]\D 为非数字。
\w 单词字符 [a-zA-Z0-9_]\W 为非单词字符。
\s 空白字符(空格、制表、换行等)。\S 为非空白。
\b 单词边界(单词与空格或标点之间)。\B 为非单词边界。
^ 匹配字符串/行的开始。
$ 匹配字符串/行的结束。
[abc] 字符集:匹配 a、b 或 c 中任意一个。
[^abc] 反向字符集:匹配不在 a、b、c 中的任意字符。
`a b`
(pattern) 捕获组:捕获匹配的子串,后续可通过 \1 引用或 match 返回。

2.4 量词

量词 含义
* 0 次或多次(贪婪)。
+ 1 次或多次(贪婪)。
? 0 次或 1 次(可选)。
{n} 恰好 n 次。
{n,} 至少 n 次。
{n,m} 至少 n 次,至多 m 次。

贪婪与懒惰:默认量词是贪婪的(尽可能多地匹配)。在量词后加 ? 变为懒惰(尽可能少地匹配)。

1
2
3
const str = '<div>content</div>';
console.log(str.match(/<.*>/)); // ['<div>content</div>'] —— 贪婪匹配整个字符串
console.log(str.match(/<.*?>/)); // ['<div>'] —— 懒惰匹配,遇到第一个 > 就停止

2.5 捕获组、非捕获组与反向引用

语法 含义
(pattern) 捕获组:匹配并记住子串,可在结果和替换中引用。
(?:pattern) 非捕获组:仅分组,不记住匹配结果(性能更好)。
(?<name>...) 命名捕获组(ES2018):给组命名,通过 groups.name 访问。
\1\2 反向引用:在正则内部引用第 1、第 2 个捕获组匹配到的文本。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 捕获组示例:提取日期各部分
const dateStr = '2026-04-15';
const regex = /(\d{4})-(\d{2})-(\d{2})/;
const match = dateStr.match(regex);
console.log(match[1]); // '2026'
console.log(match[2]); // '04'
console.log(match[3]); // '15'

// 命名捕获组
const namedRegex = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const namedMatch = dateStr.match(namedRegex);
console.log(namedMatch.groups.year); // '2026'
console.log(namedMatch.groups.month); // '04'
console.log(namedMatch.groups.day); // '15'

// 反向引用:匹配重复单词
const dupRegex = /\b(\w+)\s+\1\b/;
console.log('hello hello world'.match(dupRegex)); // 匹配 'hello hello'

2.6 环视断言(Lookaround)

断言检查当前位置前后是否符合某条件,但不消耗字符(不包含在匹配结果中)。

语法 名称 含义
(?=pattern) 正向前瞻 后面必须紧跟着 pattern
(?!pattern) 负向前瞻 后面不能紧跟着 pattern
(?<=pattern) 正向后顾(ES2018) 前面必须紧跟着 pattern
(?<!pattern) 负向后顾(ES2018) 前面不能紧跟着 pattern
1
2
3
4
5
6
7
8
// 正向前瞻:匹配后面是数字的字母
console.log('a1 b2 c3 d'.match(/\w(?=\d)/g)); // ['a', 'b', 'c']

// 正向后顾:匹配数字前面是 $ 的数字
console.log('$100 €50 $30'.match(/(?<=\$)\d+/g)); // ['100', '30']

// 负向前瞻:匹配不以 .com 结尾的域名
console.log('foo.com bar.org'.match(/\w+\.(?!com)\w+/g)); // ['bar.org']

3. 字符串与正则配合的核心方法

3.1 match

str.match(regex) 根据正则匹配字符串。

  • g 标志:返回第一个匹配结果的信息数组(包含捕获组),无匹配返回 null
  • g 标志:返回所有匹配子串组成的数组(无捕获组详情),无匹配返回 null
1
2
const str = 'The quick brown fox jumps over the lazy dog.';
console.log(str.match(/\b\w{4}\b/g)); // ['quick', 'brown', 'jumps', 'over', 'lazy']

3.2 matchAll(ES2020)

返回一个迭代器,每次迭代产生一个包含完整匹配结果和捕获组的数组(包含 groupsindex 等属性)。适用于需要获取全局匹配的完整信息。

1
2
3
4
5
const regex = /(?<word>\b\w{4}\b)/g;
const matches = str.matchAll(regex);
for (const m of matches) {
console.log(m.groups.word); // quick, brown, jumps...
}

3.3 replacereplaceAll

str.replace(regex|substr, newSubstr|function) 替换匹配项。返回新字符串,原字符串不变。

  • 字符串替换:仅替换第一个匹配。
  • 正则替换:搭配 g 标志替换所有匹配。ES2021 的 replaceAll 可用字符串进行全局替换。
1
2
3
4
5
6
7
8
9
10
const text = 'apple banana apple grape';

// 仅替换第一个
console.log(text.replace('apple', 'orange')); // 'orange banana apple grape'

// 正则全局替换
console.log(text.replace(/apple/g, 'orange')); // 'orange banana orange grape'

// replaceAll 字符串全局替换
console.log(text.replaceAll('apple', 'orange')); // 'orange banana orange grape'

替换函数:第二个参数可以是回调函数,其参数依次为 match, p1, p2, ..., offset, string。用于动态替换。

1
2
3
4
5
6
const prices = '10 USD, 20 EUR, 30 GBP';
const result = prices.replace(/(\d+) (\w+)/g, (_, amount, currency) => {
const rates = { USD: 7.2, EUR: 7.9, GBP: 9.1 };
return `${(amount * rates[currency]).toFixed(2)} CNY`;
});
console.log(result); // '72.00 CNY, 158.00 CNY, 273.00 CNY'

str.search(regex) 返回第一个匹配的索引,未找到返回 -1。它忽略 g 标志,且总是从字符串开始位置搜索(不更新 lastIndex)。

1
console.log('hello world'.search(/\bworld\b/)); // 6

3.5 split

str.split(separator, limit?) 按分隔符(字符串或正则)将字符串拆分为数组。当分隔符为正则时,捕获组的内容会被包含在结果数组中。

1
2
console.log('a1b2c3'.split(/\d/));          // ['a', 'b', 'c', '']
console.log('a1b2c3'.split(/(\d)/)); // ['a', '1', 'b', '2', 'c', '3', '']

4. RegExp 对象与动态模式

4.1 构造函数与标志位的动态传递

1
2
3
const pattern = 'hello';
const flags = 'gi';
const regex = new RegExp(pattern, flags);

当需要根据用户输入动态构建正则时,必须对特殊字符进行转义,防止注入错误的正则语法或导致匹配逻辑异常。

1
2
3
4
5
6
7
function escapeRegExp(string) {
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}

const userInput = 'hello (world)';
const safePattern = new RegExp(escapeRegExp(userInput), 'i');
console.log(safePattern); // /hello \(world\)/i

4.2 实例属性与 lastIndex

RegExp 实例有以下常用属性:

属性 说明
source 正则表达式的模式字符串(不含标志)。
flags 标志位字符串,如 'gi'
lastIndex 下一次匹配开始的索引(仅对 gy 标志有效)。
dotAll 是否设置了 s 标志。
unicode 是否设置了 u 标志。
sticky 是否设置了 y 标志。

当使用 gy 标志时,同一个正则表达式在多次调用 exectest 时会维护 lastIndex,每次匹配后该值会更新到下次匹配的起始位置。这是极易出错的点

1
2
3
4
const regex = /foo/g;
console.log(regex.test('foo bar foo')); // true, lastIndex = 3
console.log(regex.test('foo bar foo')); // true, lastIndex = 11
console.log(regex.test('foo bar foo')); // false, lastIndex 重置为 0

如果在未重置的情况下复用同一个正则对象,会得到不符合预期的结果。解决方法是每次都创建新的正则实例,或在每次循环前手动设置 regex.lastIndex = 0


5. 常见正则表达式实战

5.1 邮箱格式校验

1
2
const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
console.log(emailRegex.test('user@example.com')); // true

5.2 密码强度校验(至少包含大小写字母和数字)

1
2
const strongPwd = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$/;
console.log(strongPwd.test('Abc12345')); // true

5.3 从 URL 中提取参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const url = 'https://example.com?page=2&size=10&sort=desc';
const params = Object.fromEntries(new URL(url).searchParams); // 推荐用 URL API

// 或用正则提取
const getParams = (url) => {
const regex = /[?&]([^=&#]+)=([^&#]*)/g;
const params = {};
let match;
while ((match = regex.exec(url)) !== null) {
params[decodeURIComponent(match[1])] = decodeURIComponent(match[2]);
}
return params;
};
console.log(getParams(url)); // { page: '2', size: '10', sort: 'desc' }

课后练习

一、概念自测(选择题 / 填空题)

  1. (单选) 正则表达式 /a+/g 中,+ 表示什么含义?
    A. 匹配字母 a,0 次或 1 次
    B. 匹配字母 a,1 次或多次(贪婪)
    C. 匹配字母 a,0 次或多次(贪婪)
    D. 匹配字母 a,恰好 1 次

  2. (单选) 使用 str.replace(regex, replacement) 进行全局替换,正则表达式必须包含哪个标志?
    A. i
    B. m
    C. g
    D. u

  3. (填空) 在正则表达式中,(?<=prefix)pattern 被称为 ______ 断言(正向后顾),它要求匹配位置前面必须有 prefix,但 prefix 不包含在匹配结果中。

  4. (多选) 以下哪些字符串方法可以接受正则表达式作为参数?
    A. match
    B. replace
    C. split
    D. search

二、AI 编程任务:编写面向 AI 的提示词

场景:你需要编写一个 JavaScript 函数 formatPhoneNumber(phone),它接收一个包含数字的字符串(可能包含空格、破折号、括号等非数字字符),并返回格式为 (XXX) XXX-XXXX 的美国电话号码格式。如果输入字符串中数字不足 10 位,返回原始输入字符串。要求如下:

  • 使用正则表达式提取所有数字。
  • 判断提取的数字长度是否为 10,若不是则返回原字符串。
  • 使用捕获组和替换函数将数字重新格式化为 (xxx) xxx-xxxx(可使用 replace + 正则,或直接拼接)。
  • 使用 const 声明函数。
  • 提供多个测试用例。

任务要求:请写出一段完整的中文提示词,发送给 AI,使其生成符合上述要求的 JavaScript 代码。提示词中需明确指定正则的用法(提取数字、格式化替换)、边界情况处理。

三、面试真题与参考答案

题目(百度前端面试题):

请解释正则表达式的“贪婪匹配”与“懒惰匹配”的区别。给定字符串 '<div>hello</div><span>world</span>',如果使用正则 /<.*>//<.*?>/ 去匹配,各会得到什么结果?为什么?

参考答案

  • 贪婪匹配:量词默认尽可能多地匹配。/<.*>/ 中的 .* 会一直匹配到字符串的最后一个字符,然后因为 > 无法匹配末尾而逐步回退,直到找到最后一个 >。对于给定字符串,它匹配整个 '<div>hello</div><span>world</span>'
  • 懒惰匹配:量词后加 ? 变为懒惰,尽可能少地匹配。/<.*?>/ 中的 .*? 匹配到第一个 > 即停止。对于给定字符串,它会依次匹配 '<div>''</div>''<span>''</span>'(如果用全局匹配 g 则会得到四个结果;无 g 时只返回第一个 '<div>')。
  • 原因:正则引擎在贪婪模式下会优先匹配尽量长的子串,仅在后续部分无法匹配时才回退;懒惰模式下优先匹配最短的子串,若后续无法匹配才逐步增加。

课后练习答案

一、概念自测答案

  1. B

    • 解析:+ 表示 1 次或多次,默认贪婪。A 描述的是 ?,C 描述的是 *
  2. C

    • 解析:g 标志(global)使 replace 替换所有匹配项,而非仅第一个。
  3. 正向后顾(或 正向回顾后发

    • 解析:(?<=...) 是正向后顾断言,ES2018 引入。
  4. A、B、C、D

    • 解析:matchreplacesplitsearch 均可接受正则表达式作为参数。

二、AI 编程任务参考答案(提示词示例)

示例提示词
“请编写一个 JavaScript 函数 formatPhoneNumber(phone),将包含任意格式的美国电话号码转换为 (XXX) XXX-XXXX 格式。要求:

  • 使用 const 声明函数。
  • 先用正则 /\d/g 提取字符串中的所有数字,若长度 !== 10 则直接返回原字符串。
  • 将 10 位数字分组:前 3 位区号,中间 3 位,后 4 位。使用正则 /(\d{3})(\d{3})(\d{4})/replace 方法替换为 '($1) $2-$3'
  • 添加 JSDoc 注释。
  • 提供测试用例注释:'123-456-7890''(123) 456-7890''(987)654-3210''(987) 654-3210''abc''abc'
  • 直接输出完整代码。”
CATALOG
  1. 1. 第53课:字符串与正则表达式——模板字符串、match/replace、正则语法、RegExp 对象
    1. 1.1. 1. 模板字符串的进阶用法
      1. 1.1.1. 1.1 多行字符串与表达式插值
      2. 1.1.2. 1.2 标签函数(Tagged Templates)
      3. 1.1.3. 1.3 模板字符串用于 HTML 转义
    2. 1.2. 2. 正则表达式基础语法
      1. 1.2.1. 2.1 创建正则的两种方式
      2. 1.2.2. 2.2 标志位(Flags)
      3. 1.2.3. 2.3 元字符速览
      4. 1.2.4. 2.4 量词
      5. 1.2.5. 2.5 捕获组、非捕获组与反向引用
      6. 1.2.6. 2.6 环视断言(Lookaround)
    3. 1.3. 3. 字符串与正则配合的核心方法
      1. 1.3.1. 3.1 match
      2. 1.3.2. 3.2 matchAll(ES2020)
      3. 1.3.3. 3.3 replace 与 replaceAll
      4. 1.3.4. 3.4 search
      5. 1.3.5. 3.5 split
    4. 1.4. 4. RegExp 对象与动态模式
      1. 1.4.1. 4.1 构造函数与标志位的动态传递
      2. 1.4.2. 4.2 实例属性与 lastIndex
    5. 1.5. 5. 常见正则表达式实战
      1. 1.5.1. 5.1 邮箱格式校验
      2. 1.5.2. 5.2 密码强度校验(至少包含大小写字母和数字)
      3. 1.5.3. 5.3 从 URL 中提取参数
    6. 1.6. 课后练习
      1. 1.6.1. 一、概念自测(选择题 / 填空题)
      2. 1.6.2. 二、AI 编程任务:编写面向 AI 的提示词
      3. 1.6.3. 三、面试真题与参考答案
    7. 1.7. 课后练习答案
      1. 1.7.1. 一、概念自测答案
      2. 1.7.2. 二、AI 编程任务参考答案(提示词示例)