Home

正则中的分组、前瞻和后顾

# 正则基础

# 元字符和表达式

表达式 说明
\d 匹配任意一个数字
\w 匹配任意一个字母、数字或下划线
\s 匹配任意一个空白字符(空格、制表符、换行符等)
. 匹配除换行符以外的任意一个字符
[abc] 匹配字符集合中的任意一个字符(a、b或c)
[^abc] 匹配除字符集合中的任意一个字符以外的字符
\b 匹配单词的边界

# 量词

表达式 说明
* 匹配前面的元素零次或多次
+ 匹配前面的元素一次或多次
? 匹配前面的元素零次或一次
{n} 匹配前面的元素恰好 n 次
{n,} 匹配前面的元素至少 n 次
{n,m} 匹配前面的元素至少 n 次,但不超过 m 次

# 执行方法

  • test():测试字符串是否匹配模式,返回布尔值。
  • exec():在字符串中执行匹配,返回匹配结果的详细信息。
  • match():在字符串中查找匹配,返回匹配结果的数组。
  • search():在字符串中搜索匹配,返回匹配的位置。
  • replace():替换字符串中的匹配项。
  • split():根据匹配项将字符串拆分为数组。

# 分组

通过使用括号将需要分组的内容括起来,我们可以创建一个分组,可以用于匹配、替换和提取信息

# 1.基本分组语法

使用小括号 () 来创建一个分组。分组可以包含一个或多个字符,用于表示一个特定的模式。例如,(abc) 表示一个由字符 "abc" 组成的模式。

const str = "I love cats and dogs";
const pattern = /(love cats)/;
const result = str.match(pattern);
console.log(result[0]); // 输出:"love cats"
console.log(result[1]); // 输出:"love cats"

# 2.捕获分组

它允许我们在匹配过程中获取分组的值,并进行后续的处理。捕获组可以通过索引或命名来引用。

# 示例一:在一段字符串中取出其中的电子邮件地址

const str = "My email address is john@example.com";
const pattern = /(\w+@\w+\.\w+)/;
const result = str.match(pattern);
console.log(result[0]); // 输出:"john@example.com"
console.log(result[1]); // 输出:"john@example.com"

使用 (\w+@\w+.\w+) 创建了一个分组,它匹配了电子邮件地址,并将其作为捕获组返回。

# 示例二:在一段字符串中匹配并提取出其中的电话号码

const str = "My phone number is (123) 456-7890";
const pattern = /\((\d{3})\)\s(\d{3})-(\d{4})/;
const result = str.match(pattern);
console.log(result[0]); // 输出:"(123) 456-7890"
console.log(result[1]); // 输出:"123"
console.log(result[2]); // 输出:"456"
console.log(result[3]); // 输出:"7890"

在上述示例中,使用 ((\d{3}))\s(\d{3})-(\d{4}) 创建了三个分组,分别匹配了电话号码的区号、前三位和后四位,并将它们作为捕获组返回。

# 示例三:搭配replace使用

const str = "aaa,bbb,ccc";
str.replace(/(\w+),(\w+),(\w+)/, "$3,$2,$1");//输出'ccc,bbb,aaa'
  • $1代表第一个(\w+)匹配到的内容,即aaa
  • $2代表第一个(\w+)匹配到的内容,即bbb
  • $3代表第一个(\w+)匹配到的内容,即ccc

# 3.非捕获分组

非捕获分组的语法是(?:pattern), 不影响正常匹配行为但是不作为结果返回。

# 示例一:

const str = "My phone number is (123) 456-7890";
const pattern = /(phone)/
const pattern2 = /(?:phone)/
const result = str.match(pattern);
const result2 = str.match(pattern2);
console.log(result[0], result2[0]) // phone , phone
console.log(result[1], result2[1]) // phone , undefined

# 示例二:匹配域名

const str = "https://www.doorverse.com?v=123";
const regex = /(?:http|https):\/\/(?:[a-z]+\.)+(?:[a-z]+)/i;
const result = str.match(regex);
console.log(result[0]); // 输出:"https://www.doorverse.com"

在这个例子中,正则表达式尝试匹配以 "http://" 或 "https://" 开头的 URL,但并不需要捕获这些前缀。因此,这些前缀被放在了一个非捕获分组中。

# 4.反向引用分组

反向引用使用 \数字 的形式,其中数字表示先前捕获的分组的索引。当使用反向引用时,正则表达式引擎会尝试匹配与先前捕获的内容相同的内容。

# 示例一:匹配连续重复的字符

const str = "abab";
const pattern = /(\w+)\1/;
const result = str.match(pattern);
console.log(result[0]); // 输出:"abab"
console.log(result[1]); // 输出:"ab"

在上述示例中,使用 (\w)\1 创建了一个分组,并使用 \1 后向引用来引用先前捕获的字符,从而匹配连续重复的字符。

# 示例二:匹配类型为abab的数字

var str = 'ooo1212ooo2323ooo3434ooo1234';
var reg = /(\d)(\d)\1\2/g;
var result = str.match(reg);
console.log(result); // ['1212', '2323', '3434']

在上述示例中,所定义的reg是, \d匹配数字,\1对应的是第一个表达式(\d),\2对应的是第二个表达式(\d),g是全局匹配

# 前瞻

前瞻是指在匹配过程中,我们可以在当前位置向前查看,以确定是否满足某种条件

# 正向前瞻

语法 ?= 它会在当前位置向前查找

# 示例一:匹配邮箱地址中的用户名

const emails = ['john@example.com', 'jane.doe@example.com', 'foo@bar.com'];

const usernameRegex = /\w+(?=@)/;

emails.forEach(email => {
  const match = email.match(usernameRegex);
  console.log(match[0]);
});

// john
// jane.doe
// foo

在这个例子中,我们使用了正向前瞻(?=@)来匹配@符号之前的内容。\w+表示匹配一个或多个字母、数字或下划线,(?=@)表示要求在当前位置向前查找,必须紧跟着@符号。

# 示例二:匹配包含特定单词的句子

const text = 'I love JavaScript. JavaScript is amazing. Python is also great.';

const word = 'JavaScript';
const sentenceRegex = /[^.?!]*(?=\bJavaScript\b)[^.?!]*[.?!]/g;

const matches = text.match(sentenceRegex);
console.log(matches);

运行上述代码,我们可以得到以下输出:

[ 'I love JavaScript.', ' JavaScript is amazing.' ]

在这个例子中,我们使用了正向前瞻(?=\bJavaScript\b)来匹配包含特定单词JavaScript的句子。[^.?!]表示匹配任意数量的非句子结束符(句号、问号、感叹号)的字符,\bJavaScript\b表示匹配单词JavaScript,[^.?!][.?!]表示匹配任意数量的非句子结束符后跟一个句子结束符的字符。

# 示例三:匹配密码强度

假设我们要验证用户输入的密码强度,要求密码必须包含至少8位至少一个大写字母、一个小写字母和一个数字。

const passwords = ['password123', 'Pass123', 'passWORD', '123456'];

const passwordRegex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$/;

passwords.forEach(password => {
  const isValid = passwordRegex.test(password);
  console.log(`${password}: ${isValid}`);
});

运行上述代码,我们可以得到以下输出:

password123: false
Pass123: false
passWORD: false
123456: false

在这个例子中,我们使用了正向前瞻(?=.[a-z])、(?=.[A-Z])和(?=.*\d)来分别匹配至少一个小写字母、一个大写字母和一个数字。.{8,}表示匹配至少8个字符。^和$分别表示匹配字符串的开头和结尾,.表示任何一个字符(不包括换行),*表示前一个字符可以出现零次或多次。

# 负向前瞻

语法 ?! 它会在当前位置向前查找, 例如,q(?!u) 匹配含有后面不是 'u' 的 'q' 的字符串

# 示例一:匹配不以 "abc" 开头的字符串

const regex = /^(?!abc).*/;
console.log(regex.test("def")); // true
console.log(regex.test("abcxyz")); // false

# 示例二:匹配不以特定字符结尾的字符串

const regex = /^(?!.*[x|y|z]$).*/;
console.log(regex.test("abc")); // true
console.log(regex.test("defx")); // false

# 示例三:匹配不包含特定字符的字符串

const regex = /^(?!.*[x|y|z]).*/;
console.log(regex.test("abc123")); // true
console.log(regex.test("abcx")); // false

# 后顾

后顾是指在匹配过程中,我们可以在当前位置向后查看,用来捕获后半段

# 正向后顾

正向后顾语法结构是 ?<=,表示匹配位置前面的内容必须满足指定的模式。

# 示例一:匹配 "1" 后面的所有 "a",

const str = "1a2a3a4a";
const regex = /(?<=1)a/
const matches = str.match(regex);
console.log(matches); // 输出: ['a']

这个正则表达式将只匹配第一个 "a",因为它紧跟在 "1" 的后面。

需要注意的是,正则表达式的查找模式在正向后顾中不会被包括在匹配结果中。也就是说,即使 "1" 是我们用来查找 "a" 的模式,"1" 不会被包括在匹配结果中。只有 "a" 会被返回作为匹配结果。

# 示例二:匹配以特定前缀开头的URL

const str = "Visit our website at https://example.com to learn more.";
const regex = /(?<=https:\/\/)\w+\.\w+/g;
const matches = str.match(regex);
console.log(matches); // 匹配了以 `https://` 开头的URL  输出: ["example.com"]

# 示例三:匹配某个位置之前的特定字符

const str = "I love coding, but I hate debugging.";
const regex = /(?<=love\s)\w+/g;
const matches = str.match(regex);
console.log(matches); //匹配了位于 `love` 后面的单词  输出: ["coding"]

# 负向后顾

负向后顾语法结构是?<!, 它会在当前位置向后查找, 例如,(?<!u)q 匹配不是 'u' 开头的 'q'

# 匹配不是以123开头的abc

const regex = /(?<!123)abc/g;;
console.log(regex.test("abc123")); // true
console.log(regex.test("123abc")); // false