正则表达式进阶--玩转分组、前瞻和后顾

前言

正则表达式是一种强大的模式匹配工具,可以用于在字符串中查找、替换和提取特定的文本。

分组是一项非常重要的功能,它允许我们将模式进行分组,并在匹配过程中对分组进行操作。除了基本的分组语法外,正则表达式还提供了前瞻和后顾这两种高级分组技术,它们能够在匹配过程中对分组进行更精确的控制。

前瞻和后顾是一种零宽度断言,它们用于在匹配过程中指定一个位置,而不是实际匹配的内容。前瞻和后顾可以帮助我们在匹配特定模式时,确保其前后满足一定的条件。

本文将详细介绍正则表达式中的分组,前瞻和后顾,通过简单清晰的JavaScript示例,带领大家彻底搞明白这几个让人难以理解的概念。

正则基础

先来简单介绍一下正则表达式的基础语法,如果你已经是老手,基础比较好,这些对你来说肯定是小菜一碟,那么可以直接跳过这一部分~

正则表达式由一个模式和一些可选的修饰符组成。模式是由字符和特殊字符组成的字符串,它定义了我们要匹配的模式。特殊字符具有特殊的含义,例如元字符(如\d、\w、\s等)表示特定的字符类别,字符类(如[abc])表示字符的集合,量词(如*、+、?等)表示匹配次数等。

下面是一些常用的正则表达式元字符和字符类别:

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

除了元字符和字符类别,我们还可以使用量词来指定匹配次数:

  • *:匹配前面的元素零次或多次。
  • +:匹配前面的元素一次或多次。
  • ?:匹配前面的元素零次或一次。
  • {n}:匹配前面的元素恰好 n 次。
  • {n,}:匹配前面的元素至少 n 次。
  • {n,m}:匹配前面的元素至少 n 次,但不超过 m 次。

修饰符是可选的,用于指定匹配的方式。常见的修饰符包括:

  • g:全局匹配,找到所有的匹配项而不仅仅是第一个。
  • i:忽略大小写,不区分大小写地匹配。
  • m:多行匹配,将 ^$ 匹配行的开始和结束,而不仅仅是整个字符串的开始和结束。

在JavaScript中,我们可以使用正则表达式的方法来执行各种操作。以下是一些常用的方法:

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

分组

分组可以将正则表达式中的一部分内容视为一个整体,并对其进行操作。通过使用括号将需要分组的内容括起来,我们可以创建一个分组。分组可以用于多种用途,包括匹配、替换和提取信息。

在匹配方面,分组可以帮助我们将匹配的内容进行分类。例如,我们可以使用分组来匹配一个特定的模式,并将匹配的结果保存在一个分组中。这样,我们就可以更方便地提取出我们所需的信息。

在替换方面,分组可以帮助我们在替换过程中保留一部分内容。通过使用分组,我们可以将匹配的内容进行捕获,并在替换的过程中使用这些捕获的内容。这样,我们可以实现更加复杂和灵活的替换操作。

在提取信息方面,分组可以帮助我们从文本中提取出我们感兴趣的内容。通过使用分组,我们可以将需要提取的内容进行捕获,并将其保存在一个分组中。这样,我们就可以轻松地获取到我们所需的信息。

接下来通过部分示例的应用,深入探讨正则表达式中的分组,希望能够帮助大家更好地理解和应用这一功能。

1.基本分组语法

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

javascript 复制代码
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.捕获分组

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

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

javascript 复制代码
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+) 创建了一个分组,它匹配了电子邮件地址,并将其作为捕获组返回。

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

javascript 复制代码
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使用

javascript 复制代码
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),其中,pattern 是我们要匹配的模式

示例一:

javascript 复制代码
const regex = /(?:abc)/g;

在这个例子中,(?:abc) 是一个非捕获分组。这意味着正则表达式会尝试匹配 "abc",但不会将这个匹配结果保存下来。因此,当我们使用 match()search() 方法时,返回的结果将不包含这个非捕获分组的匹配结果。

然而,非捕获分组仍然可以影响正则表达式的行为。例如,它们可以用于改变匹配的优先级,改变单词边界的行为,或者简单地创建一种不需要被捕获的模式。

示例二:匹配域名

javascript 复制代码
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,但并不需要捕获这些前缀。因此,这些前缀被放在了一个非捕获分组中。同样,这个正则表达式也试图匹配一种形式的 URL,这个 URL的前缀是一种由字母组成的子域名字(例如 "google."),后面跟着的是顶级域名(例如 "com")。这些都被放在了非捕获分组中。这样做可以让正则表达式更加灵活,同时又不会影响到需要捕获的 URL 部分。

4.反向引用分组

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

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

javascript 复制代码
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的数字

javascript 复制代码
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是全局匹配

前瞻

前瞻是指在匹配过程中,我们可以在当前位置向前查看,以确定是否满足某种条件。通过使用前瞻,我们可以对匹配的结果进行更精确的控制,从而实现更复杂的匹配需求。

在正则表达式中,有两种常见的前瞻:正向前瞻负向前瞻。正向前瞻使用肯定的条件来匹配,即只有当某个模式的后面紧跟着满足指定条件的内容时,才认为匹配成功。而负向前瞻则使用否定的条件来匹配,即只有当某个模式的后面不满足指定条件的内容时,才认为匹配成功。

通过使用前瞻,我们可以实现一些复杂的匹配需求。例如,我们可以使用正向前瞻来匹配一个单词后面跟着某个特定字符的情况,或者使用负向前瞻来匹配一个单词后面不跟着某个特定字符的情况。这样,我们就可以更加灵活地处理匹配的结果。

接下来我们将通过实际的例子和案例,帮助大家更好地理解和应用前瞻。同时,我们也将介绍一些常见的前瞻应用场景,帮助大家更好地掌握正则表达式中的前瞻功能,提升文本处理的效率和准确性。

1. 正向前瞻

正向前瞻的语法是使用括号和问号来表示,即(?=pattern),其中pattern表示要查找的模式。当正向前瞻被使用时,它会在当前位置向前查找,如果满足指定的条件,则返回匹配成功,否则返回匹配失败。

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

javascript 复制代码
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+表示匹配一个或多个字母、数字或下划线,(?=@)表示要求在当前位置向前查找,必须紧跟着@符号。

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

假设我们有一段文本,我们想要匹配其中包含特定单词的句子。

javascript 复制代码
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);

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

css 复制代码
[ 'I love JavaScript.', ' JavaScript is amazing.' ]

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

示例三:匹配密码强度

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

javascript 复制代码
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}`);
});

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

yaml 复制代码
password123: false
Pass123: false
passWORD: false
123456: false

在这个例子中,我们使用了正向前瞻(?=.*[a-z])(?=.*[A-Z])(?=.*\d)来分别匹配至少一个小写字母、一个大写字母和一个数字。.{8,}表示匹配至少8个字符。^$分别表示匹配字符串的开头和结尾,确保整个字符串都满足要求。

2. 负向前瞻

负向前瞻的语法结构是 (?!pattern),它表示在当前位置向前查找,确保接下来的文本不匹配 pattern。如果匹配成功,则当前位置不会被视为匹配项,继续向后匹配。

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

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

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

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

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

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

后顾

后顾是指在匹配过程中,我们可以在当前位置向后查看,以确定是否满足某种条件。通过使用后顾,我们可以对匹配的结果进行更精确的控制,从而实现更复杂的匹配需求。这个特性已经在ECMAScript 2018(ES9)正式发布了。

在正则表达式中,有两种常见的后顾:正向后顾负向后顾。正向后顾使用肯定的条件来匹配,即只有当某个模式的前面紧跟着满足指定条件的内容时,才认为匹配成功。而负向后顾则使用否定的条件来匹配,即只有当某个模式的前面不满足指定条件的内容时,才认为匹配成功。

通过使用后顾,我们可以实现一些复杂的匹配需求。例如,我们可以使用正向后顾来匹配一个特定字符前面跟着满足某种条件的内容,或者使用负向后顾来匹配一个特定字符前面不满足某种条件的内容。这样,我们就可以更加灵活地处理匹配的结果。

1. 正向后顾

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

示例一:匹配以数字开头的单词

javascript 复制代码
const str = "The price of the product is $50. It is a good deal.";
const regex = /(?<=\b)\d+\b/g;
const matches = str.match(regex);
console.log(matches); // 输出: ["50"]

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

javascript 复制代码
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"]

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

javascript 复制代码
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"]

2. 负向后顾

负向后顾语法结构是(?<!pattern),表示匹配位置前面的内容不能满足指定的模式。

示例一

javascript 复制代码
// 匹配不是以123开头的abc
const regex = /(?<!123)abc/g;;
console.log(regex.test("abc123")); // true
console.log(regex.test("123abc")); // false

常用的正则表达式

数字校验相关

  1. 校验整数:/^-?\d+$/
  2. 校验正整数:/^\d+$/
  3. 校验负整数:/^-\d+$/
  4. n位的数字:/^\d{n}$/
  5. m-n位的数字:/^\d{m,n}$/
  6. 校验浮点数:/^-?\d+(\.\d+)?$/
  7. 校验正浮点数:/^\d+(\.\d+)?$/
  8. 校验负浮点数:/^-\d+(\.\d+)?$/
  9. 校验数字字符串:/^\d+$/
  10. 校验百分比(0-100之间的数字):/^([1-9]\d?|100)$/

字符校验相关

  1. 校验英文字母:/^[a-zA-Z]+$/
  2. 校验大写英文字母:/^[A-Z]+$/
  3. 校验小写英文字母:/^[a-z]+$/
  4. 校验数字和英文字母组合:/^[a-zA-Z0-9]+$/
  5. 校验数字、英文字母和下划线组合:/^\w+$/
  6. 校验中文字符:/^[\u4e00-\u9fa5]+$/

特殊需求

  1. 匹配手机号码:/^1[3456789]\d{9}$/
  2. 匹配邮箱地址:/^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/
  3. 匹配URL地址:/^(http|https):\/\/[^\s]+$
  4. 匹配身份证号码(18位):/^\d{17}[\dXx]$/
  5. 匹配日期(YYYY-MM-DD):/^\d{4}-\d{2}-\d{2}$/
  6. 匹配IP地址:/^((25[0-5]|2[0-4]\d|1\d{2}|[1-9]\d|[1-9])\.){3}(25[0-5]|2[0-4]\d|1\d{2}|[1-9]\d|[1-9])$/
  7. 匹配邮政编码:/^[1-9]\d{5}$/
  8. 匹配整数:/^-?\d+$/
  9. 匹配浮点数:/^-?\d+(\.\d+)?$/
  10. 匹配英文单词:/^[a-zA-Z]+$/
  11. 匹配中文字符:/^[\u4e00-\u9fa5]+$/
  12. 匹配用户名(字母开头,允许字母、数字、下划线,长度为6-16位):/^[a-zA-Z]\w{5,15}$/
  13. 匹配密码(包含大小写字母和数字,长度为6-20位):/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d]{6,20}$/
  14. 匹配HTML标签:/<\/?[^>]+>/
  15. 匹配16进制颜色值:/^#([0-9a-fA-F]{6}|[0-9a-fA-F]{3})$/
  16. 匹配QQ号码:/^[1-9]\d{4,10}$/
  17. 匹配微信号(以字母开头,允许字母、数字、下划线,长度为6-20位):/^[a-zA-Z]([-_a-zA-Z0-9]{5,19})+$/
  18. 匹配车牌号(普通车牌和新能源车牌):/^(京|津|沪|渝|冀|豫|云|辽|黑|湘|皖|鲁|新|苏|浙|赣|鄂|桂|甘|晋|蒙|陕|吉|闽|贵|粤|青|藏|川|宁|琼)([A-HJ-NP-Z]{1}(([0-9]{5}[DF])|([DF][A-HJ-NP-Z0-9][0-9]{4})))$/
  19. 匹配MAC地址:/^([0-9A-Fa-f]{2}[:-]){5}[0-9A-Fa-f]{2}$/
  20. 匹配中国大陆固定电话号码:/^0\d{2,3}-\d{7,8}$/

结语

通过本文的介绍,相信大家已经对前瞻和后顾有了更深入的理解,并能够在实际开发中灵活运用。

在实际的文本处理中,分组、前瞻和后顾经常被用于复杂的匹配和替换需求。合理地运用这些功能,可以提高我们的工作效率和准确性。同时,我们也需要注意正则表达式的语法规则和一些常见的陷阱,以避免出现错误的匹配结果。 特别是在使用后顾断言时,需要注意浏览器的兼容性,以确保代码在各种环境下都能正常运行。

希望本文能够给大家带来帮助,正则表达式是一个强大的工具,掌握好的话,将为我们的文本处理工作带来更多的便利和效益。

参考

  1. MDN JavaScript正则表达式文档:developer.mozilla.org/zh-CN/docs/...
相关推荐
前端与小赵3 小时前
关于 AJAX 与 Promise
前端·ajax·okhttp·promise
啊·贤3 小时前
展开运算符 (...):这是 JavaScript 中的一个语法,用于将数组或对象展开到另一个数组或对象中。
前端·javascript·vue.js
硬汉嵌入式3 小时前
《安富莱嵌入式周报》第345期:开源蓝牙游戏手柄,USB3.0 HUB带电压电流测量,LCR电桥前端模拟,开源微型赛车,RF信号扫描仪,开源无线电收发器
前端·游戏·开源
NiNg_1_2344 小时前
前端 Flex 布局语法详解
前端·css·html
吉吉安4 小时前
前端实现选项多选效果(三层结构)
前端·vue.js·html5
Python_trys6 小时前
Python爬虫基础-正则表达式!
开发语言·爬虫·python·正则表达式·编程
青少儿编程课堂6 小时前
2024年3月电子学会Python等级考试试卷(四级)真题,包含答案
前端·javascript·python·电子学会·电子学会2024年3月真题·电子学会真题
始终奔跑在路上6 小时前
软件测试—HTML
前端·功能测试·html·集成测试·软件工程
Moment7 小时前
在 NodeJs 中如何通过子进程与 Golang 进行 IPC 通信 🙄🙄🙄
前端·后端·go
skywalk81637 小时前
三周精通FastAPI:38 针对不同的编程语言来生成客户端
开发语言·前端·python·fastapi