一、创建正则
1 2 3 4 5
| const re1 = /pattern/flags;
const re2 = new RegExp('pattern', 'flags');
|
二、正则符号
字面字符
1
| /abc/ 匹配字符串中的 "abc"(精确匹配)
|
元字符
1 2 3 4 5 6 7 8 9
| . 匹配除换行符外的任意单个字符 \d 匹配数字 [0-9] \D 匹配非数字 \w 匹配字母、数字、下划线 [a-zA-Z0-9_] \W 匹配非单词字符 \s 匹配空白符(空格、制表符、换行) \S 匹配非空白符 \b 匹配单词边界 \B 匹配非单词边界
|
字符集合 []
1 2 3 4
| [abc] 匹配 a、b、c 中的任意一个 [a-z] 匹配小写字母 [^abc] 匹配除 a、b、c 外的任意字符 [0-9a-f] 匹配十六进制数字
|
量词
1 2 3 4 5 6
| * 零次或多次(等效 {0,}) + 一次或多次(等效 {1,}) ? 零次或一次(等效 {0,1}) {n} 恰好 n 次 {n,} 至少 n 次 {n,m} n 到 m 次
|
默认是贪婪匹配(尽可能多匹配)。量词后加 ? 变为非贪婪:
1 2
| "aaa".match(/a+/); "aaa".match(/a+?/);
|
分组与引用 ()
1 2 3
| (abc) 捕获组:匹配 abc 并捕获到组中供后续引用 (?:abc) 非捕获组:匹配 abc 但不捕获 \1, \2 反向引用:引用第 n 个捕获组匹配的内容
|
1 2 3 4 5 6 7
| /(\w+)\s+\1/.test('hello hello'); /(\w+)\s+\1/.test('hello world');
'2024-03-15'.replace(/(\d{4})-(\d{2})-(\d{2})/, '$2/$3/$1');
|
锚点
1 2 3 4 5 6
| ^ 字符串开头 $ 字符串结尾 (?=) 先行断言(后面必须跟指定模式) (?!) 先行否定断言(后面不能跟指定模式) (?<=) 后行断言(前面必须是指定模式) (?<!) 后行否定断言(前面不能是指定模式)
|
1 2 3 4 5 6 7 8 9 10 11 12
| /^hello/.test('hello world'); /world$/.test('hello world');
/\d+(?=px)/.test('16px'); /\d+(?!px)/.test('16px');
/(?<=\$)\d+/.test('$100'); // true(数字前面是 $) /(?<!\$)\d+/.test('$100'); // false(数字前面是 $,不匹配)
// 千分位 '12345678'.replace(/\d{1,3}(?=(\d{3})+$)/g, '$&,');
|
修饰符
1 2 3 4 5 6
| g 全局匹配(找到所有匹配,不 g 只找第一个) i 忽略大小写 m 多行模式(^ 和 $ 匹配每行首尾) s 点号通配(. 匹配换行符) u Unicode 模式(正确处理四字节字符) y 粘性匹配(从 lastIndex 处匹配)
|
1 2
| /hello/gi.test('HELLO'); "Line1\nLine2".match(/^\w+/gm);
|
常用正则速查
1 2 3 4 5 6 7 8
| /^[\w.-]+@[\w.-]+\.\w+$/ 邮箱 /^1[3-9]\d{9}$/ 手机号(中国大陆) /^https?:\/\/.+/ URL /^\d{17}[\dXx]$/ 身份证号(18 位) /^[a-zA-Z]\w{5,17}$/ 密码(字母开头,6-18 位) /[\u4e00-\u9fa5]/ 中文字符 /\s+/ 空白字符串 /^\s+|\s+$/g 首尾空白
|
三、JS 中的正则 API
RegExp 上的方法
1 2 3 4 5 6 7 8 9 10 11 12
| const re = /\d+/g;
re.test('abc123');
re.lastIndex; re.exec('abc123def456'); re.lastIndex; re.exec('abc123def456'); re.lastIndex; re.exec('abc123def456');
|
String 上的正则方法
1 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
| const str = '2024-03-15'; const re = /(\d{4})-(\d{2})-(\d{2})/;
str.match(re); str.match(re)[1]; str.match(/\d+/g);
const matches = str.matchAll(/(\d+)/g); [...matches].map(m => m[1]);
str.replace(re, '$2/$3/$1'); str.replace(/-/g, '/');
'abc123def456'.replace(/\d+/g, (match, index, input) => { return `[${match}]`; });
str.search(/-/);
'2024-03-15'.split(/-/); 'hello world'.split(/\s+/);
|
四、面试题
1. 千分位格式化
1 2 3 4 5 6 7
| function toThousands(num) { const parts = String(num).split('.'); parts[0] = parts[0].replace(/\d{1,3}(?=(\d{3})+$)/g, '$&,'); return parts.join('.'); }
toThousands(12345678.123);
|
2. 匹配手机号中间四位为 *
1 2 3 4 5
| function maskPhone(phone) { return String(phone).replace(/(\d{3})\d{4}(\d{4})/, '$1****$2'); }
maskPhone('13812345678');
|
3. 模板字符串解析
1 2 3 4 5 6
| function render(template, data) { return template.replace(/\{\{(\w+)\}\}/g, (match, key) => data[key] ?? match); }
render('Hello {{name}}, you are {{age}}', { name: 'Alice', age: 25 });
|
4. 驼峰与连字符互转
1 2 3 4 5 6 7 8 9 10 11
| function camelToKebab(str) { return str.replace(/([A-Z])/g, '-$1').toLowerCase(); } camelToKebab('fontSize');
function kebabToCamel(str) { return str.replace(/-(\w)/g, (_, c) => c.toUpperCase()); } kebabToCamel('background-color');
|
5. 匹配 HTML 标签
1 2 3 4 5 6 7 8
| /<(\w+)[^>]*>.*?<\/\1>/gs.test('<div>text</div>'); // true
// 去除所有 HTML 标签 function stripTags(html) { return html.replace(/<[^>]*>/g, ''); } stripTags('<p>Hello <b>World</b></p>'); // "Hello World"
|
6. 密码强度校验
1 2 3 4 5 6 7 8 9 10 11
| function checkPasswordStrength(pwd) { const rules = [ /.{8,}/, /[a-z]/, /[A-Z]/, /\d/, /[^a-zA-Z0-9]/, ]; const score = rules.filter(r => r.test(pwd)).length; return score <= 2 ? '弱' : score <= 3 ? '中' : score <= 4 ? '强' : '极强'; }
|
7. 提取 URL 查询参数
1 2 3 4 5 6 7 8 9 10
| function getQueryParams(url) { const params = {}; url.replace(/(\w+)=([^&#]*)/g, (_, key, val) => { params[key] = decodeURIComponent(val); }); return params; }
getQueryParams('https://example.com?a=1&b=hello&c=%E4%B8%AD');
|
8. 验证字符串是否回文
1 2 3 4 5 6
| function isPalindrome(str) { const clean = str.replace(/[^a-zA-Z0-9]/g, '').toLowerCase(); return clean === clean.split('').reverse().join(''); }
isPalindrome('A man, a plan, a canal: Panama');
|
9. /\d{1,3}(?=(\d{3})+$)/g 的原理
1 2
| '12345678'.replace(/\d{1,3}(?=(\d{3})+$)/g, '$&,');
|
1 2 3 4 5 6 7 8 9 10 11
| 匹配过程: \d{1,3} 匹配 1~3 个数字 (?=(\d{3})+$) 后面必须跟 1~n 组正好 3 个数字直到结尾 g 全局匹配
正向匹配(从 12345678 中向右看): "12345678": 匹配 "12"(因为后面是 "345678" = 两组三位,即 (345)(678) 正好到 $) 匹配 "345"(因为后面是 "678" = 一组三位,到 $) 最后剩下 "678" 不满足条件(后面没有三位数字到结尾) 结果替换为 "12,345,678"
|
10. 正则匹配陷阱
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| const re = /\d+/g; re.test('abc123'); re.test('abc123'); re.lastIndex = 0; re.test('abc123');
'abc123def456'.match(/\d+/);
'abc123def456'.match(/\d+/g);
'abcd'.replace(/(\w)(\w)/g, (match, $1, $2, index, input) => { return $2 + $1; });
|