正则表达式入门
正则表达式是一种强大的文本模式匹配工具,用于搜索、验证和替换文本
在 JavaScript 中,我们可以通过 RegExp 对象使用它们,也可以与字符串方法结合使用。
创建正则表达式对象
// 方法1
regexp = new RegExp("pattern", "flags");
// 方法2
regexp = /pattern/; // 没有修饰符
regexp = /pattern/gmi; // 带有修饰符 g、m 和 i(后面会讲到)2
3
4
5
6
在这两种情况下,regexp 都会成为内建类 RegExp 的一个实例。
这两种语法之间的主要区别在于,使用斜线 /.../ 的模式不允许插入表达式(如带有 ${...} 的字符串模板)。它是完全静态的。
当我们需要从动态生成的字符串“动态”创建正则表达式时,更经常使用 new RegExp。例如:
let tag = prompt("What tag do you want to find?", "h2");
let regexp = new RegExp(`<${tag}>`);
// 如果在上方输入到 prompt 中的答案是 "h2",则与 `/<h2>/` 相同2
3
4
修饰符
正则表达式可能会有的会影响搜索结果的修饰符。
在 JavaScript 中,有 6 个修饰符:
i
使用此修饰符后,搜索时不区分大小写:A 和 a 之间没有区别(请参见下面的示例)。
g
使用此修饰符后,搜索时会寻找所有的匹配项 —— 没有它,则仅返回第一个匹配项。
m
多行模式(详见 锚点 ^ $ 的多行模式,修饰符 "m")。
s
启用 “dotall” 模式,允许点 . 匹配换行符 \n(在 字符类 中有详细介绍)。
u
开启完整的 Unicode 支持。该修饰符能够正确处理代理对。详见 Unicode:修饰符 "u" 和类 \p{...}。
y
粘滞模式,在文本中的确切位置搜索(详见 粘性修饰符 "y",在位置处搜索)
搜索:str.match
str.match(regexp) 方法在字符串 str 中寻找 regexp 的所有匹配项。
它有 3 种工作模式:
如果正则表达式具有修饰符 g,它返回一个由所有匹配项所构成的数组:
let str = "We will, we will rock you";
console.log( str.match(/we/gi) );
// We,we(由两个匹配的子字符串构成的数组)2
3
4
请注意,We 和 we 都被找到了,因为修饰符 i 使得正则表达式在进行搜索时不区分大小写。
如果没有这样的修饰符,它则会以数组形式返回第一个匹配项,索引 0 处保存着完整的匹配项,返回的结果的属性中还有一些其他详细信息:
let str = "We will, we will rock you";
let result = str.match(/we/i); // 没有修饰符 g
console.log( result[0] ); // We(第一个匹配项)
console.log( result.length ); // 1
// 详细信息:
console.log( result.index ); // 0(匹配项的位置)
console.log( result.input ); // We will, we will rock you(源字符串)2
3
4
5
6
7
8
9
10
如果正则表达式中有一部分内容被包在括号里,那么返回的数组可能会有 0 以外的索引。我们将在 捕获组 中学习这部分相关内容。
最后,如果没有匹配项,则返回 null(无论是否有修饰符 g)。
这是一个非常重要的细微差别。如果没有匹配项,我们不会收到一个空数组,而是会收到 null。忘了这一点可能会导致错误,例如:
let matches = "JavaScript".match(/HTML/); // = null
if (!matches.length) { // Error: Cannot read property 'length' of null
console.log("Error in the line above");
}2
3
4
5
如果我们希望结果始终是一个数组,我们可以这样写:
let matches = "JavaScript".match(/HTML/) || [];
if (!matches.length) {
console.log("No matches"); // 现在可以了
}2
3
4
5
替换:str.replace
str.replace(regexp, replacement) 方法使用 replacement 替换在字符串 str 中找到的 regexp 的匹配项(如果带有修饰符 g 则替换所有匹配项,否则只替换第一个)。
例如:
// 没有修饰符 g
console.log( "We will, we will".replace(/we/i, "I") ); // I will, we will
// 带有修饰符 g
console.log( "We will, we will".replace(/we/ig, "I") ); // I will, I will2
3
4
5
第二个参数是字符串 replacement。我们可以在其中使用特殊的字符组合来对匹配项进行插入: 符号 在替换字符串中的行为
$&插入整个匹配项- $` 插入字符串中匹配项之前的字符串部分
$'插入字符串中匹配项之后的字符串部分$n如果 n 是一个 1-2 位的数字,则插入第 n 个分组的内容,详见 捕获组$<name>插入带有给定 name 的括号内的内容,详见 捕获组$$插入字符 $
带有 $& 的一个示例:
console.log( "I love HTML".replace(/HTML/, "{#content}amp; and JavaScript") );
// I love HTML and JavaScript2
测试:regexp.test
regexp.test(str) 方法寻找至少一个匹配项,如果找到了,则返回 true,否则返回 false。
let str = "I love JavaScript";
let regexp = /LOVE/i;
console.log( regexp.test(str) ); // true2
3
4
总结
正则表达式由模式和可选择修饰符构成:g、i、m、u、s 和 y。
没有修饰符和特殊符号(稍后我们会学到),那么正则表达式的搜索和子字符串的搜索相同。
str.match(regexp) 方法寻找匹配项:如果带有修饰符 g,则会返回所有匹配项,否则只会返回第一个匹配项。
str.replace(regexp, replacement) 方法使用 replacement 替换 regexp 的匹配项:如果带有修饰符 g,则会替换所有匹配项,否则只会替换第一个匹配项。
regexp.test(str) 方法用于测试,如果找到至少一个匹配项则返回 true,否则返回 false。
常用字符类
| 字符 | 含义 |
|---|---|
. | 任意字符(除换行符) |
\d | 数字 [0-9] |
\D | 非数字 [^0-9] |
\w | 单词字符 [a-zA-Z0-9_] |
\W | 非单词字符 |
\s | 空白字符(空格、tab、换行) |
\S | 非空白字符 |
[abc] | 匹配 a、b 或 c 中的任意一个 |
[^abc] | 匹配除了 a、b、c 以外的字符 |
[a-z] | 匹配 a 到 z 的任意小写字母 |
锚点(位置匹配)
| 字符 | 含义 |
|---|---|
^ | 字符串开头(多行模式下匹配行首) |
$ | 字符串结尾(多行模式下匹配行尾) |
\b | 单词边界 |
\B | 非单词边界 |
(?=p) | 正向先行断言(后面跟 p) |
(?!p) | 负向先行断言(后面不跟 p) |
// 锚点示例
"hello world".match(/^hello/) // 匹配开头
"hello world".match(/world$/) // 匹配结尾
"hello world".match(/\bworld\b/) // 匹配完整单词
"hello world".match(/world(?!\s)/) // world后不是空格2
3
4
5
量词
| 量词 | 含义 |
|---|---|
* | 0 次或多次(等价于 {0,}) |
+ | 1 次或多次(等价于 {1,}) |
? | 0 次或 1 次(等价于 {0,1}) |
{n} | 恰好 n 次 |
{n,} | 至少 n 次 |
{n,m} | n 到 m 次 |
// 量词示例
"123".match(/\d+/) // "123" - 连续数字
"abc".match(/\d+/) // null - 没有数字
"010-12345678".match(/\d{3,4}/) // "010" - 3-4位数字
"color colour".match(/colou?r/) // "color" - u可选2
3
4
5
捕获组
// 简单捕获
"2024-03-15".match(/(\d{4})-(\d{2})-(\d{2})/)
// 结果: ["2024-03-15", "2024", "03", "15"]
// 命名捕获
let result = "2024-03-15".match(/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/)
console.log(result.groups.year) // "2024"
// 非捕获组
"2024-03-15".match(/(?:\d{4})-(\d{2})-(\d{2})/)
// 结果: ["2024-03-15", "03", "15"](第一组被忽略)2
3
4
5
6
7
8
9
10
11
常用正则表达式
// 手机号(中国大陆)
/^1[3-9]\d{9}$/
// 邮箱
/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/
// URL
/^https?:\/\/[\w\-]+(\.[\w\-]+)+[/#?]?.*$/
// 身份证号(18位)
/^[1-9]\d{5}(19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dXx]$/
// IP地址
/^((25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)\.){3}(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)$/
// 日期 YYYY-MM-DD
/^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$/
// 时间 HH:mm:ss
/^([01]\d|2[0-3]):([0-5]\d):([0-5]\d)$/
// 用户名(字母数字下划线,3-16位)
/^[a-zA-Z0-9_]{3,16}$/
// 密码(至少8位,包含大小写字母和数字)
/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d]{8,}$/
// HTML 标签
/<([a-z]+)([^<]+)*(?:>(.*)<\/\1>|\s+\/>)/
// 银行卡号(16-19位)
/^[1-9]\d{15,18}$/2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
JavaScript 中的字符串方法
const str = "Hello World, hello world"
// split - 用正则分割
"apple, banana; cherry".split(/[,\s;]+/)
// ["apple", "banana", "cherry"]
// search - 查找位置
str.search(/world/i) // 6(找不到返回 -1)
// test - 布尔判断
/^Hello/.test(str) // true
// match - 提取匹配
str.match(/hello/gi) // ["Hello", "hello"]
// replace - 替换
str.replace(/world/gi, "JavaScript")
// "Hello JavaScript, hello JavaScript"
// matchAll - 获取所有匹配及详情
[...str.matchAll(/hello/gi)]
// [{0: "Hello", index: 0, ...}, {0: "hello", index: 12, ...}]2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
常用场景
去除空格
" hello world ".trim() // "hello world"
" hello ".replace(/^\s+|\s+$/g, '') // "hello"2
提取数字
"价格: 99.9元".match(/\d+(\.\d+)?/)
// ["99.9", ".9", index: 4, ...]2
敏感信息脱敏
"13812345678".replace(/(\d{3})\d{4}(\d{4})/, '$1****$2')
// "138****5678"2
驼峰转短横线
"helloWorld".replace(/([A-Z])/g, '-$1').toLowerCase()
// "hello-world"2