正则表达式

48 阅读5分钟

正则表达式完全指南

在线测试工具

1. 正则表达式基础

1.1 什么是正则表达式

正则表达式是一种用于匹配字符串中字符组合的模式。在 JavaScript 和其他编程语言中,正则表达式也是对象。

1.2 创建正则表达式

在 JavaScript 中,有两种方法可以创建正则表达式:

  1. 字面量方式

    const regex = /pattern/flags;
    
  2. 构造函数方式

    const regex = new RegExp('pattern', 'flags');
    

1.3 正则表达式标志

常用的正则表达式标志:

  • g:全局匹配(找到所有匹配项,而不是在第一个匹配后停止)
  • i:忽略大小写
  • m:多行模式(^$ 匹配每一行的开头和结尾)
  • s:让 . 匹配任何字符,包括换行符
  • u:Unicode 模式,正确处理 Unicode 字符
  • y:粘性匹配,从目标字符串的当前位置开始匹配

2. 字符类

2.1 基本字符类(元字符)

基本字符类属于正则表达式中的元字符,它们在正则表达式中具有特殊含义:

  • \d:匹配一个数字字符,等价于 [0-9]

    const numRegex = /\d+/;
    console.log(numRegex.test('123')); // true
    console.log(numRegex.test('abc')); // false
    console.log('价格是100元'.match(/\d+/)[0]); // '100'
    
  • \D:匹配一个非数字字符,等价于 [^0-9]

    const nonNumRegex = /\D+/;
    console.log(nonNumRegex.test('abc')); // true
    console.log(nonNumRegex.test('123')); // false
    console.log('价格是100元'.match(/\D+/)[0]); // '价格是'
    
  • \w:匹配字母、数字、下划线,等价于 [A-Za-z0-9_]

    const wordRegex = /\w+/;
    console.log(wordRegex.test('hello_world123')); // true
    console.log(wordRegex.test('你好!')); // false
    console.log('user_name_123'.match(/\w+/)[0]); // 'user_name_123'
    
  • \W:匹配非字母、数字、下划线,等价于 [^A-Za-z0-9_]

    const nonWordRegex = /\W+/;
    console.log(nonWordRegex.test('!@#$')); // true
    console.log(nonWordRegex.test('abc123')); // false
    console.log('hello-world@example.com'.match(/\W+/g)); // ['-', '@', '.']
    
  • \s:匹配任何空白字符,包括空格、制表符、换页符等

    const spaceRegex = /\s+/;
    console.log(spaceRegex.test('   ')); // true(空格)
    console.log(spaceRegex.test('\t')); // true(制表符)
    console.log('hello\tworld'.match(/\s/)[0]); // '\t'
    
  • \S:匹配任何非空白字符,等价于 [^ \f\n\r\t\v]

    const nonSpaceRegex = /\S+/;
    console.log(nonSpaceRegex.test('hello')); // true
    console.log(nonSpaceRegex.test('   ')); // false
    console.log('  hello world  '.match(/\S+/g)); // ['hello', 'world']
    
  • .:匹配除换行符(\n\r)之外的任何单个字符

    const dotRegex = /./;
    console.log(dotRegex.test('a')); // true
    console.log(dotRegex.test('5')); // true
    console.log(dotRegex.test('\n')); // false
    console.log('abc.def'.match(/./g)); // ['a', 'b', 'c', '.', 'd', 'e', 'f']
    

2.2 空白字符匹配(元字符)

  • \f:匹配一个换页符

    const formFeedRegex = /\f/;
    console.log(formFeedRegex.test('\f')); // true
    console.log('line1\fline2'.split(/\f/)); // ['line1', 'line2']
    
  • \n:匹配一个换行符

    const newlineRegex = /\n/;
    console.log(newlineRegex.test('\n')); // true
    console.log('line1\nline2'.split(/\n/)); // ['line1', 'line2']
    
  • \r:匹配一个回车符

    const carriageReturnRegex = /\r/;
    console.log(carriageReturnRegex.test('\r')); // true
    console.log('line1\rline2'.split(/\r/)); // ['line1', 'line2']
    
  • \t:匹配一个制表符

    const tabRegex = /\t/;
    console.log(tabRegex.test('\t')); // true
    console.log('col1\tcol2\tcol3'.split(/\t/)); // ['col1', 'col2', 'col3']
    
  • \v:匹配一个垂直制表符

    const verticalTabRegex = /\v/;
    console.log(verticalTabRegex.test('\v')); // true
    console.log('line1\vline2'.split(/\v/)); // ['line1', 'line2']
    

2.3 自定义字符类

  • [abc]:匹配方括号中的任意一个字符(a、b 或 c)
    const charClassRegex = /[abc]/;
    console.log(charClassRegex.test('a')); // true
    console.log(charClassRegex.test('b')); // true
    console.log(charClassRegex.test('c')); // true
    console.log(charClassRegex.test('d')); // false
    
  • [^abc]:匹配除了方括号中字符外的任意字符
    const negCharClassRegex = /[^abc]/;
    console.log(negCharClassRegex.test('a')); // false
    console.log(negCharClassRegex.test('d')); // true
    console.log(negCharClassRegex.test('1')); // true
    
  • [a-z]:匹配 a 到 z 范围内的任意字符
    const lowercaseRegex = /[a-z]/;
    console.log(lowercaseRegex.test('a')); // true
    console.log(lowercaseRegex.test('z')); // true
    console.log(lowercaseRegex.test('A')); // false
    
  • [A-Z]:匹配 A 到 Z 范围内的任意字符
    const uppercaseRegex = /[A-Z]/;
    console.log(uppercaseRegex.test('A')); // true
    console.log(uppercaseRegex.test('Z')); // true
    console.log(uppercaseRegex.test('a')); // false
    
  • [0-9]:匹配 0 到 9 范围内的任意字符
    const digitRegex = /[0-9]/;
    console.log(digitRegex.test('5')); // true
    console.log(digitRegex.test('9')); // true
    console.log(digitRegex.test('a')); // false
    
  • [a-zA-Z0-9]:匹配任意字母数字字符
    const alphanumericRegex = /[a-zA-Z0-9]/;
    console.log(alphanumericRegex.test('a')); // true
    console.log(alphanumericRegex.test('Z')); // true
    console.log(alphanumericRegex.test('5')); // true
    console.log(alphanumericRegex.test('_')); // false
    

3. 位置匹配符(元字符)

3.1 行首行尾匹配

  • ^:匹配输入字符串开始的位置

    const startRegex = /^hello/;
    console.log(startRegex.test('hello world')); // true
    console.log(startRegex.test('hi hello')); // false
    
    // 多行模式下匹配每一行的开头
    const multilineStartRegex = /^\d+/m;
    const text = '1. First\n2. Second\nThird';
    console.log([...text.matchAll(multilineStartRegex)].map(m => m[0])); // ['1', '2']
    
  • $:匹配输入字符串结尾的位置

    const endRegex = /world$/;
    console.log(endRegex.test('hello world')); // true
    console.log(endRegex.test('world hello')); // false
    
    // 多行模式下匹配每一行的结尾
    const multilineEndRegex = /\d+$/m;
    const text2 = 'First 1\nSecond 2\nThird';
    console.log([...text2.matchAll(multilineEndRegex)].map(m => m[0])); // ['1', '2']
    

3.2 单词边界匹配

  • \b:匹配一个单词边界,即单词字符(字母、数字、下划线)和非单词字符之间的位置

    // 修正:匹配完整单词"er"是不可能的,应该匹配包含er的单词边界
    const wordBoundaryRegex = /\ber/;  // 匹配以er开头的单词
    console.log(wordBoundaryRegex.test('error')); // true
    console.log(wordBoundaryRegex.test('never')); // false('er'不在单词开头)
    console.log(wordBoundaryRegex.test('verb')); // false
    
    const wordBoundaryRegex2 = /er\b/; // 匹配以er结尾的单词
    console.log(wordBoundaryRegex2.test('never')); // true
    console.log(wordBoundaryRegex2.test('verb')); // false
    console.log('never error verb'.match(/er\b/g)); // ['er']
    
  • \B:与 \b 相反,匹配非单词边界的位置

    const nonWordBoundaryRegex = /\Ber\B/; // 匹配在单词中间的er
    console.log(nonWordBoundaryRegex.test('verb')); // true('er'在单词中间)
    console.log(nonWordBoundaryRegex.test('never')); // false('er'在单词结尾)
    console.log('verb never error'.match(/\Ber\B/g)); // ['er']
    

4. 量词

4.1 基本量词

  • *:匹配前面的表达式 0 次或多次(等价于 {0,}

    // 匹配零个或多个数字
    const numRegex = /\d*/;
    console.log(numRegex.test('')); // true
    console.log(numRegex.test('123')); // true
    console.log(numRegex.test('abc')); // true(匹配空字符串)
    
    // 匹配包含零个或多个空格的文本
    const spaceRegex = /hello\s*world/;
    console.log(spaceRegex.test('helloworld')); // true
    console.log(spaceRegex.test('hello world')); // true
    console.log(spaceRegex.test('hello   world')); // true
    
  • +:匹配前面的表达式 1 次或多次(等价于 {1,}

    // 匹配一个或多个数字
    const numPlusRegex = /\d+/;
    console.log(numPlusRegex.test('')); // false
    console.log(numPlusRegex.test('1')); // true
    console.log(numPlusRegex.test('123abc')); // true(匹配'123')
    
    // 匹配至少一个字母
    const letterRegex = /[a-zA-Z]+/;
    console.log(letterRegex.test('abc')); // true
    console.log(letterRegex.test('123a456')); // true(匹配'a')
    console.log(letterRegex.test('123')); // false
    
  • ?:匹配前面的表达式 0 次或 1 次(等价于 {0,1}

    // 匹配可选的'http://'或'https://'
    const httpRegex = /https?:\/\//;
    console.log(httpRegex.test('http://example.com')); // true
    console.log(httpRegex.test('https://example.com')); // true
    console.log(httpRegex.test('example.com')); // false
    
    // 匹配可选的'k'在'color'中
    const colorRegex = /colou?r/;
    console.log(colorRegex.test('color')); // true
    console.log(colorRegex.test('colour')); // true
    console.log(colorRegex.test('colr')); // false
    
  • {n}:匹配前面的表达式恰好 n 次

    // 匹配恰好3个数字
    const threeDigitRegex = /\d{3}/;
    console.log(threeDigitRegex.test('123')); // true
    console.log(threeDigitRegex.test('12')); // false
    console.log(threeDigitRegex.test('1234')); // true(匹配'123')
    
    // 匹配固定格式的日期
    const dateFormatRegex = /\d{4}-\d{2}-\d{2}/;
    console.log(dateFormatRegex.test('2023-05-15')); // true
    console.log(dateFormatRegex.test('23-05-15')); // false
    
  • {n,}:匹配前面的表达式至少 n 次

    // 匹配至少4个字符的用户名
    const usernameRegex = /\w{4,}/;
    console.log(usernameRegex.test('user')); // true
    console.log(usernameRegex.test('admin')); // true
    console.log(usernameRegex.test('u')); // false
    
    // 匹配至少2位的小数
    const decimalRegex = /\d+\.\d{2,}/;
    console.log(decimalRegex.test('3.1415')); // true
    console.log(decimalRegex.test('3.14')); // true
    console.log(decimalRegex.test('3.1')); // false
    
  • {n,m}:匹配前面的表达式至少 n 次,最多 m 次

    // 匹配4到6位的数字密码
    const passwordRegex = /\d{4,6}/;
    console.log(passwordRegex.test('1234')); // true
    console.log(passwordRegex.test('123456')); // true
    console.log(passwordRegex.test('123')); // false
    console.log(passwordRegex.test('1234567')); // true(匹配前6位)
    

4.2 贪婪与非贪婪匹配

  • 默认情况下,量词是贪婪的(尽可能多地匹配)
  • 在量词后添加 ? 使其变为非贪婪模式(尽可能少地匹配)
4.2.1 贪婪匹配示例
// 贪婪匹配 - 匹配整个标签内容
const greedy = /<.*>/;
const text = '<p>Hello</p><p>World</p>';
console.log(text.match(greedy)); // 匹配整个 '<p>Hello</p><p>World</p>'

// 贪婪匹配嵌套结构
const nestedHtml = '<div><span>Test</span></div>';
console.log(nestedHtml.match(/<.*>/)[0]); // 匹配整个 '<div><span>Test</span></div>'
4.2.2 非贪婪匹配示例
// 非贪婪匹配 - 匹配单个标签
const lazy = /<.*?>/g;
const text = '<p>Hello</p><p>World</p>';
console.log(text.match(lazy)); // 匹配 ['<p>', '</p>', '<p>', '</p>']

// 非贪婪匹配与捕获组
const htmlWithClass = '<div class="container"><span>Content</span></div>';
const tagRegex = /<([^>]+?)>.*?<\/\1>/g;
const matches = [...htmlWithClass.matchAll(tagRegex)];
console.log(matches[0][0]); // '<span>Content</span>'
console.log(matches[0][1]); // 'span'

4.3 或运算符

或运算符 | 允许匹配多个模式中的任意一个。它的优先级较低,通常需要与分组结合使用以获得预期的结果。

4.3.1 基本用法
// 匹配"cat"或"dog"
const petRegex = /cat|dog/;
console.log(petRegex.test('I have a cat')); // true
console.log(petRegex.test('I have a dog')); // true
console.log(petRegex.test('I have a catdog')); // true
console.log(petRegex.test('I have a bird')); // false

// 与分组结合使用 - 修正示例
const fileExtensionRegex = /\.(jpg|png|gif|svg)$/i;
console.log(fileExtensionRegex.test('image.jpg')); // true
console.log(fileExtensionRegex.test('photo.PNG')); // true
console.log(fileExtensionRegex.test('document.pdf')); // false

// 错误示范与修正
const badRegex = /http|https:\/\//; // 错误:会匹配"http"或"https://"
const correctRegex = /https?:\/\//; // 正确:使用量词?代替或运算符
console.log(badRegex.test('http')); // true - 这不是我们想要的
console.log(correctRegex.test('http')); // false - 正确行为
console.log(correctRegex.test('http://')); // true

5. 分组和引用

5.1 捕获组

  • (pattern):捕获匹配的文本并记住匹配项,可通过索引访问
5.1.1 基本捕获组示例
// 基本捕获组 - 提取日期的年月日
const regex = /(\d{4})-(\d{2})-(\d{2})/;
const date = '2023-05-15';
const match = date.match(regex);
console.log(match); // ['2023-05-15', '2023', '05', '15']
console.log(`年份: ${match[1]}, 月份: ${match[2]}, 日期: ${match[3]}`);

// 捕获组在replace方法中的使用
const formattedDate = date.replace(regex, '$3/$2/$1'); // 格式化为日/月/年
console.log(formattedDate); // '15/05/2023'

5.2 非捕获组

  • (?:pattern):匹配 pattern 但不捕获匹配项,不会在 match 结果数组中生成对应的元素
// 使用非捕获组进行选择
const fruitRegex = /(?:apple|banana|orange)\s+juice/;
const text1 = 'I love apple juice';
console.log(fruitRegex.test(text1)); // true

// 比较捕获组和非捕获组的区别
const captureRegex = /(apple|banana)\s+juice/;
const nonCaptureRegex = /(?:apple|banana)\s+juice/;

const match1 = text1.match(captureRegex);
const match2 = text1.match(nonCaptureRegex);

console.log(match1); // ['apple juice', 'apple'] - 包含捕获组
console.log(match2); // ['apple juice'] - 不包含非捕获组

5.3 回溯(反向引用)

  • \1, \2, ...:引用第 1、2、... 个捕获组匹配的内容
// 匹配重复的单词
const regex = /(\w+)\s+\1/;
console.log(regex.test('hello hello')); // true
console.log(regex.test('hello world')); // false

// 匹配HTML标签对
const htmlRegex = /<(\w+)>(.*?)<\/\1>/;
const html = '<div>Content</div>';
console.log(htmlRegex.test(html)); // true
console.log(htmlRegex.test('<div>Content</span>')); // false(标签不匹配)

// 在替换中使用反向引用
const textWithDuplicates = 'hello hello world world';
const dedupedText = textWithDuplicates.replace(/(\w+)\s+\1/g, '$1');
console.log(dedupedText); // 'hello world'

6. 零宽断言

6.1 先行断言

  • (?=pattern):正向先行断言,匹配后面跟着指定模式的位置

    const regex = /\d+(?=元)/; // 匹配后面跟着"元"的数字
    console.log('价格是100元'.match(regex)); // ['100']
    console.log('100美元'.match(regex)); // null(后面不是"元")
    
  • (?!pattern):负向先行断言,匹配后面不跟着指定模式的位置

    const regex = /\d+(?!元)/; // 匹配后面不跟着"元"的数字
    console.log('100美元'.match(regex)); // ['100']
    console.log('价格是100元'.match(regex)); // 这里会匹配'价'前面的空位置,结果可能不是预期
    
    // 更准确的负向先行断言示例
    const priceRegex = /\d+(?!元)(?!\.)/; // 匹配后面不是"元"且不是小数点的数字
    console.log('100美元'.match(priceRegex)); // ['100']
    console.log('价格是100元'.match(priceRegex)); // ['10'](后面是"元")
    

6.2 后行断言

  • (?<=pattern):正向后行断言,匹配前面是指定模式的位置

    const regex = /(?<=¥)\d+/; // 匹配前面是"¥"的数字
    console.log('价格是¥100'.match(regex)); // ['100']
    console.log('价格是$100'.match(regex)); // null(前面不是"¥")
    
  • (?<!pattern):负向后行断言,匹配前面不是指定模式的位置

    const regex = /(?<!¥)\d+/; // 匹配前面不是"¥"的数字
    console.log('100元'.match(regex)); // ['100']
    console.log('价格是¥100'.match(regex)); // ['00'](前面是"¥")
    

7. 常用正则表达式模式

7.1 验证邮箱

// 改进的邮箱验证正则表达式
const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
console.log(emailRegex.test('user@example.com')); // true
console.log(emailRegex.test('user.name+tag@example.co.uk')); // true
console.log(emailRegex.test('invalid-email@')); // false
console.log(emailRegex.test('invalid-email')); // false

7.2 验证手机号码(中国大陆)

const phoneRegex = /^1[3-9]\d{9}$/;
console.log(phoneRegex.test('13812345678')); // true
console.log(phoneRegex.test('12812345678')); // false(开头不是有效的运营商号段)
console.log(phoneRegex.test('1381234567')); // false(位数不足)

7.3 验证URL

// 改进的URL验证正则表达式
const urlRegex = /^(https?:\/\/)?([\da-z.-]+)\.([a-z.]{2,6})([\/\w .-]*)*\/?$/;
console.log(urlRegex.test('https://example.com')); // true
console.log(urlRegex.test('http://sub.example.co.uk/path?query=value')); // false
console.log(urlRegex.test('example.com')); // true(允许缺少协议)
console.log(urlRegex.test('invalid-url')); // false

7.4 验证身份证号码(中国大陆)

const idCardRegex = /^[1-9]\d{5}(18|19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dXx]$/;
console.log(idCardRegex.test('110101199001011234')); // true
console.log(idCardRegex.test('11010119900101123X')); // true
console.log(idCardRegex.test('1101011990010112')); // false(位数不足)

8. 实用示例

8.1 移除字符串两端空白

function trim(str) {
  return str.replace(/^\s+|\s+$/g, '');
}

console.log(trim('  hello world  ')); // 'hello world'
console.log(trim('\t\nhello\t\n')); // 'hello'

8.2 千位分隔符格式化数字

function formatNumber(num) {
  return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
}

console.log(formatNumber(1234567)); // '1,234,567'
console.log(formatNumber(1234)); // '1,234'
console.log(formatNumber(123)); // '123'

8.3 驼峰命名转连字符命名

function camelToKebab(str) {
  return str.replace(/[A-Z]/g, '-$&').toLowerCase();
}

console.log(camelToKebab('camelCase')); // 'camel-case'
console.log(camelToKebab('CamelCase')); // '-camel-case'

// 修正第一个字符大写的情况
function improvedCamelToKebab(str) {
  return str.replace(/^[A-Z]/, match => match.toLowerCase())
           .replace(/[A-Z]/g, '-$&').toLowerCase();
}

console.log(improvedCamelToKebab('CamelCase')); // 'camel-case'

正则表达式是一个强大的工具,通过不断练习和使用,可以极大地提高文本处理和数据验证的效率。请确保在实际应用中测试你的正则表达式,以确保它们按预期工作。