一文了解js 的正则

ops/2024/11/22 23:23:44/

文章目录

    • 正则基础
    • 正则应用
    • 正则性能优化
    • 特殊含义
      • ?.
      • ?=

正则基础

一、正则表达式基础知识

  1. 什么是正则表达式?

    • 正则表达式是一种用于匹配字符串中字符组合的模式。在JavaScript中,正则表达式是对象。它就像一个模板,可以帮助你在文本中查找、替换或验证符合特定规则的字符串内容。例如,你可以用正则表达式来检查一个字符串是否是有效的电子邮件地址、电话号码或者网址等。
  2. 正则表达式的语法组成

    • 字符类(Character Classes)
      • 方括号[]用于定义字符类。例如,[abc]匹配字符abc中的任意一个。还可以使用范围,如[a - z]匹配所有小写字母,[A - Z]匹配所有大写字母,[0 - 9]匹配所有数字。
      • 有一些预定义的字符类:
        • \d等价于[0 - 9],表示数字。
        • \D等价于[^0 - 9],表示非数字。
        • \w等价于[a - zA - Z0 - 9_],表示单词字符(字母、数字、下划线)。
        • \W等价于[^a - zA - Z0 - 9_],表示非单词字符。
        • \s表示空白字符(空格、制表符、换行符等),\S表示非空白字符。
    • 量词(Quantifiers)
      • *:匹配前面的元素零次或多次。例如,a*可以匹配空字符串、aaaaaa等。
      • +:匹配前面的元素一次或多次。例如,a+可以匹配aaaaaa等,但不能匹配空字符串。
      • ?:匹配前面的元素零次或一次。例如,a?可以匹配空字符串或a
      • {n}:匹配前面的元素恰好n次。例如,a{3}匹配aaa
      • {n,}:匹配前面的元素至少n次。例如,a{2,}匹配aaaaaaaaa等。
      • {n,m}:匹配前面的元素至少n次且最多m次。例如,a{1,3}匹配aaaaaa
    • 锚点(Anchors)
      • ^:匹配字符串的开头。例如,^abc只匹配以abc开头的字符串。
      • $:匹配字符串的结尾。例如,xyz$只匹配以xyz结尾的字符串。
    • 分组(Grouping)
      • 圆括号()用于分组。例如,(ab)+匹配abababababab等。分组还可以用于捕获匹配的内容,方便后续提取和处理。
    • 或操作(Alternation)
      • 竖线|用于表示或操作。例如,a|b匹配a或者b。可以结合分组来构建更复杂的或关系,如(abc|def)匹配abc或者def
  3. 创建正则表达式对象

    • 在JavaScript中,可以使用两种方式创建正则表达式对象。
    • 字面量方式
      • 例如,/abc/是一个匹配字符串abc的正则表达式。可以将它赋值给一个变量,如let reg = /abc/;
    • 构造函数方式
      • 例如,let reg = new RegExp("abc");。这种方式在需要动态构建正则表达式时很有用,比如根据用户输入来生成匹配模式。

二、理解正则表达式

  1. 匹配过程
    • 当使用正则表达式进行匹配时,引擎会从字符串的开头(如果没有使用m(多行)模式)开始,按照正则表达式的模式逐个字符进行比较。例如,对于正则表达式/abc/和字符串abcdef,引擎首先比较第一个字符a,匹配成功后继续比较第二个字符b,再比较第三个字符c,全部匹配成功后就找到了一个匹配项。
    • 如果在匹配过程中某个字符不符合正则表达式的模式,引擎会尝试从下一个字符位置重新开始匹配(这取决于正则表达式的具体设置,如是否使用了g(全局)模式)。
  2. 贪婪模式与非贪婪模式
    • 量词默认是贪婪模式。这意味着它们会尽可能多地匹配字符。例如,对于正则表达式/a.*b/和字符串aaabbb,它会匹配整个aaabbb,因为.*会一直匹配直到最后一个b
    • 非贪婪模式可以通过在量词后面添加?来实现。例如,/a.*?b/对于字符串aaabbb会匹配aab,因为.*?会尽可能少地匹配字符,只要能满足后面的b被匹配即可。

三、正则表达式的应用

  1. 字符串验证
    • 验证电子邮件地址
      • 一个简单的电子邮件验证正则表达式可以是/^[a-zA-Z0 - 9_.+-]+@[a-zA-Z0 - 9 -]+\.[a-zA-Z0 - 9-.]+$/
      • 解释:
        • ^表示从字符串开头匹配。
        • [a - zA - Z0 - 9_.+-]+匹配邮件地址的用户名部分,可以包含字母、数字、点、下划线、加号和减号,并且至少出现一次。
        • @匹配@符号。
        • [a - zA - Z0 - 9 -]+匹配域名部分,可以包含字母、数字和减号,并且至少出现一次。
        • \.[a - zA - Z0 - 9-.]+匹配域名后缀,点号需要转义(\),后缀部分可以包含字母、数字、点和减号,并且至少出现一次。
        • $表示匹配到字符串结尾。
    • 验证手机号码(以中国手机号码为例)
      • 简单的正则表达式可以是/^1[3 - 9]\d{9}$/
      • 解释:
        • ^开头。
        • 1表示手机号码以1开头。
        • [3 - 9]表示第二位数字是3到9中的一个。
        • \d{9}表示后面跟着9位数字。
        • $结尾。
  2. 字符串查找和替换
    • 查找
      • 在JavaScript中,可以使用string.match(regex)方法来查找匹配正则表达式的内容。例如,let str="abcabc"; let regex = /abc/; let matches = str.match(regex);matches会是一个包含匹配结果的数组,在这个例子中,它会包含abc。如果使用g(全局)模式,如/abc/gmatches会包含所有的abc匹配项(在这个例子中是["abc","abc"])。
    • 替换
      • 可以使用string.replace(regex, replacement)方法来替换匹配正则表达式的内容。例如,let str="abcabc"; let regex = /abc/; let newStr = str.replace(regex,"def");newStr会是defabc,因为只替换了第一个匹配项。如果使用g(全局)模式,如/abc/g,则会替换所有匹配项,得到defdef
  3. 提取信息
    • 假设你有一个包含日期格式为yyyy - mm - dd的字符串,如"活动日期是2024-11-21",你可以使用正则表达式/(\d{4})-(\d{2})-(\d{2})/来提取年、月、日信息。
      • 例如,let str="活动日期是2024-11-21"; let regex = /(\d{4})-(\d{2})-(\d{2})/; let matches = str.match(regex);matches数组中,matches[0]是整个匹配的日期字符串2024 - 11 - 21matches[1]是提取的年份2024matches[2]是提取的月份11matches[3]是提取的日期21

正则应用

  1. 表单验证
    • 用户名验证
      • 许多网站要求用户名只能包含字母、数字和下划线,并且长度在一定范围内。例如,要求用户名长度在3到16个字符之间。可以使用正则表达式/^[a-zA-Z0 - 9_]{3,16}$/进行验证。
      • 解释:
        • ^表示从字符串开头匹配。
        • [a - zA - Z0 - 9_]定义了字符类,包含所有字母、数字和下划线。
        • {3,16}表示前面的字符类中的字符出现的次数至少为3次,最多为16次。
        • $表示匹配到字符串结尾,确保整个字符串都符合要求。
    • 密码强度验证
      • 一个简单的密码强度验证可以要求密码至少包含一个大写字母、一个小写字母、一个数字和一个特殊字符,并且长度不少于8个字符。
      • 正则表达式可以是/^(?=.*[a - z])(?=.*[A - Z])(?=.*\d)(?=.*[@#$%^&+=!]).{8,}$/
      • 解释:
        • ^开头,$结尾确保整个字符串都符合要求。
        • (?=.*[a - z])是一个正向肯定预查,确保字符串中包含至少一个小写字母。
        • (?=.*[A - Z])确保包含至少一个大写字母。
        • (?=.*\d)确保包含至少一个数字。
        • (?=.*[@#$%^&+=!])确保包含至少一个指定的特殊字符。
        • .{8,}表示除了满足前面的条件外,字符串长度至少为8个字符。
  2. 文本内容提取
    • 提取网页中的链接
      • 在处理网页内容(例如从HTML字符串中)提取链接时,可以使用正则表达式。假设网页内容是一个简单的HTML字符串,链接格式为<a href="https://example.com">链接文本</a>
      • 可以使用正则表达式/<a\s+href="([^"]*)">([^<]*)<\/a>/g来提取链接地址和链接文本。
      • 解释:
        • <a\s+href="匹配<a标签和href属性的开头部分,\s+匹配一个或多个空白字符,这是因为href属性前面可能有空格等。
        • ([^"]*)是一个捕获组,用于捕获双引号内的链接地址,[^"]*表示匹配除双引号以外的任意字符零次或多次。
        • ">匹配>符号,用于结束href属性部分。
        • ([^<]*)是另一个捕获组,用于捕获<a></a>之间的链接文本,[^<]*表示匹配除<以外的任意字符零次或多次。
        • <\/a>匹配</a>标签结束部分。
        • g(全局)模式用于匹配所有符合条件的链接。
    • 提取文章中的标签
      • 假设文章内容中有一些以#开头的标签,如这是一篇关于#JavaScript和#正则表达式的文章
      • 可以使用正则表达式/#[a-zA - Z0 - 9]+/g来提取这些标签。
      • 解释:
        • #匹配#符号。
        • [a - zA - Z0 - 9]+匹配一个或多个字母或数字,用于提取标签内容。
        • g(全局)模式用于提取所有标签。
  3. 数据清洗和格式化
    • 电话号码格式化
      • 例如,用户输入的电话号码可能有多种格式,如13812345678+8613812345678(010) - 12345678等。可以将它们统一格式化为+86 - 138 - 1234 - 5678的格式。
      • 首先使用正则表达式识别电话号码的各种格式,对于手机号码可以使用/^(\+?86)?(1[3 - 9]\d{9})$/来匹配。
      • 然后通过提取和重新拼接来格式化。代码示例如下:
      javascript">function formatPhoneNumber(phone) {let match = phone.match(/^(\+?86)?(1[3 - 9]\d{9})$/);if (match) {let countryCode = match[1]? match[1] : "+86";let number = match[2];return countryCode + " - " + number.slice(0, 3) + " - " + number.slice(3, 7) + " - " + number.slice(7);}return phone;
      }
      
    • 日期格式化
      • 假设日期数据有多种格式,如2024/11/212024 - 11 - 2111/21/2024等,要将它们统一格式化为yyyy - mm - dd
      • 可以使用正则表达式/(\d{4})[\/-](\d{2})[\/-](\d{2})/来匹配日期,然后提取并重新拼接。
      javascript">function formatDate(dateStr) {let match = dateStr.match(/(\d{4})[\/-](\d{2})[\/-](\d{2})/);if (match) {return match[1] + " - " + match[2] + " - " + match[3];}return dateStr;
      }
      
  4. 代码文本处理
    • 提取JavaScript函数名
      • 在处理JavaScript代码文本时,假设要提取代码中的所有函数名。函数定义格式可能是function functionName() {... }或者const functionName = function() {... }等。
      • 可以使用正则表达式/function\s+([a-zA - Z_$][a-zA - Z0 - 9_$]*)|\s*const\s*([a-zA - Z_$][a-zA - Z0 - 9_$]*)\s*=\s*function/g来提取函数名。
      • 解释:
        • function\s+([a - zA - Z_$][a - zA - Z0 - 9_$]*)匹配function关键字后面跟着一个函数名,函数名的命名规则是由字母、数字、下划线和美元符号组成,并且第一个字符不能是数字。([a - zA - Z_$][a - zA - Z0 - 9_$]*)是一个捕获组用于捕获函数名。
        • |\s*const\s*([a - zA - Z_$][a - zA - Z0 - 9_$]*)\s*=\s*function用于匹配通过常量定义的函数,同样([a - zA - Z_$][a - zA - Z0 - 9_$]*)捕获函数名。
        • g(全局)模式用于提取所有函数名。
    • 检查代码中的语法错误(简单示例)
      • 对于一些简单的语法错误检查,比如检查是否缺少分号。可以使用正则表达式/([^;])\s*$来检查每行代码的末尾是否缺少分号。
      • 解释:
        • ([^;])捕获一个不是分号的字符。
        • \s*$匹配零个或多个空白字符直到行尾。如果捕获到了不是分号的字符,并且后面没有分号直到行尾,就可能存在缺少分号的问题。不过这只是一个简单的初步检查,实际的语法检查工具会更复杂。

正则性能优化

  1. 避免过度使用贪婪匹配
    • 贪婪匹配会尽可能多地匹配字符,这可能导致性能问题。例如,对于正则表达式/a.*b/和一个很长的字符串,引擎会一直匹配直到最后一个b出现。
    • 优化方法是在合适的时候使用非贪婪匹配。非贪婪匹配通过在量词后添加?来实现。例如,将/a.*b/改为/a.*?b/。非贪婪匹配会尽可能少地匹配字符,只要能满足后续的匹配条件即可。这样在处理长字符串时可以减少不必要的匹配尝试,提高性能。
  2. 减少回溯次数
    • 回溯是正则表达式引擎在匹配过程中,当某个匹配分支失败时,回退到之前的状态重新尝试其他可能的匹配路径的过程。过多的回溯会导致性能下降。
    • 例如,对于正则表达式/(a|ab)+c/和字符串abc,引擎首先会匹配a,然后因为+量词会尝试再次匹配aab,当发现无法匹配时会回溯并尝试其他组合。
    • 优化可以通过优化正则表达式的结构来减少回溯。一种方法是将更具体的模式放在前面。对于上面的例子,可以改为/(ab|a)+c/,这样在匹配时可以更快地找到正确的路径,减少回溯次数。
  3. 使用合适的字符类范围
    • 在定义字符类时,尽量精确地指定范围。例如,如果只需要匹配数字,使用\d(等价于[0 - 9])比使用[0 - 9a - zA - Z]更高效。
    • 另外,避免使用不必要的复杂字符类组合。如果可以使用简单的字符类来完成匹配,就不要使用复杂的组合。例如,要匹配所有英文字母,使用[a - zA - Z]比使用([a - b]|[c - d]|[e - f]|...|[y - z]|[A - B]|[C - D]|...|[Y - Z])要高效得多。
  4. 利用锚点(Anchors)来限制匹配范围
    • 锚点^(匹配字符串开头)和$(匹配字符串结尾)可以帮助限制正则表达式的匹配范围。例如,如果要检查一个字符串是否完全由数字组成,使用/^\d+$/比使用/\d+/更好。
    • 因为/\d+/会在字符串中任何包含数字的部分匹配,而/^\d+$/会确保整个字符串都是数字,减少了不必要的匹配尝试,特别是在处理长字符串时可以提高性能。
  5. 避免在循环中重复编译正则表达式
    • 在JavaScript中,每次创建一个新的正则表达式对象(无论是通过字面量还是构造函数),引擎都会进行编译。如果在循环中频繁使用相同的正则表达式,这种编译会导致性能下降。
    • 例如:
    javascript">for (let i = 0; i < 100; i++) {let reg = /abc/;let str = "abcdef";let result = str.match(reg);
    }
    
    • 优化方法是将正则表达式对象在循环外定义,例如:
    javascript">let reg = /abc/;
    for (let i = 0; i < 100; i++) {let str = "abcdef";let result = str.match(reg);
    }
    
  6. 使用sticky模式(y模式)代替g(全局)模式(在某些情况下)
    • g(全局)模式会从上次匹配结束的位置继续匹配,这可能会导致一些不必要的回溯。而ysticky)模式会从lastIndex属性指定的位置开始匹配,并且如果匹配不成功,lastIndex会重置为0。
    • 例如,在需要精确控制匹配位置的场景下,y模式可能会更高效。但要注意y模式的兼容性,它在一些旧版本的浏览器中可能不支持。
  7. 测试和性能分析
    • 使用性能测试工具(如console.time()console.timeEnd())来比较不同正则表达式的性能。例如:
    javascript">let str = "a long string for testing";
    console.time("regex1");
    let reg1 = /a.*b/;
    str.match(reg1);
    console.timeEnd("regex1");
    console.time("regex2");
    let reg2 = /a.*?b/;
    str.match(reg2);
    console.timeEnd("regex2");
    
    • 这样可以直观地看到不同正则表达式在特定字符串上的匹配时间,从而选择性能更好的表达式。同时,对于复杂的应用场景,可以使用专业的性能分析工具来深入分析正则表达式的性能瓶颈。

特殊含义

?.

  1. 可选的非贪婪匹配(?.)

    • 在正则表达式中,?.用于表示可选的非贪婪匹配。其中,?在这里有两种作用组合在一起。
    • 首先,?作为量词,表示前面的元素(在这里是点.)是可选的,即可以出现0次或者1次。其次,和单独的?量词用于可选匹配不同,?.中的?还改变了点.(匹配除换行符外的任意单个字符)的匹配方式为非贪婪匹配。
    • 非贪婪匹配意味着在匹配过程中,它会尽可能少地匹配字符,只要能满足后续的正则表达式规则即可。
  2. 示例说明

    • 考虑正则表达式a(.*?)ba(.*)b,以及待匹配的字符串aabb
    • 对于a(.*)b,因为*是贪婪匹配,它会匹配最长的字符串,所以整个aabb会被(.*)匹配,得到的结果是aabb
    • 而对于a(.*?)b(.*?)是非贪婪匹配,它会尽可能少地匹配。在aabb这个字符串中,(.*?)只会匹配a和第一个b之间的a,得到的结果是a
    • 如果是a(?.b)和字符串a或者ab。对于字符串a(?.b)可以匹配成功,因为b是可选的,这里就没有匹配b;对于字符串ab(?.b)也能匹配成功,并且匹配了b
  3. 实际应用场景

    • 在处理HTML标签内容提取等场景中,可能会用到这种可选的非贪婪匹配。例如,想要提取<p>标签内可能存在也可能不存在的某个属性值。假设HTML片段是<p class="test">内容</p>或者<p>内容</p>,如果要提取class属性值(如果存在),可以使用类似class="(?.[^"]*)"的正则表达式部分(完整的正则表达式还需要考虑标签的开头和结尾等部分)。
    • 在这个例子中,(?.[^"]*)表示class属性值是可选的(如果没有class属性也能匹配),并且非贪婪地匹配双引号内的内容(如果有class属性的话),这样可以更灵活地处理不同情况的HTML标签内容。

?=

  1. 零宽度正预测先行断言(?=)的定义

    • 在正则表达式中,?=是一种零宽度正预测先行断言。它用于指定一个位置,该位置之后的字符必须满足?=后面的模式,但匹配过程中并不消耗(匹配)这些字符。
    • 简单来说,它就像一个“前瞻性检查”,只检查某个位置之后是否符合特定条件,而不把符合条件的字符包含在实际的匹配内容中。
  2. 示例解释

    • 例如,有正则表达式a(?=b)和字符串abc。当进行匹配时,引擎会先找到字符a,然后检查a之后的字符是否为b。在这个例子中,a之后是b,满足(?=b)的条件,所以a被匹配。但是,b以及后面的c不会被当作匹配结果的一部分,因为?=只是进行了位置检查,并没有真正匹配这些字符。
    • 再看另一个例子,对于正则表达式/(\w)(?=\d)/和字符串abc1(\w)会匹配第一个字母a,然后(?=\d)会检查a之后是否是数字。因为a之后是b不是数字,所以这个位置不满足。接着引擎会继续寻找下一个位置,当(\w)匹配到c时,(?=\d)检查c之后是1,满足条件,所以最终(\w)匹配的是c,但整个匹配结果只是c1不会包含在其中。
  3. 实际应用场景

    • 密码验证
      • 在验证密码强度时,如要求密码必须包含数字和字母。可以使用/^(?=.*\d)(?=.*[a - zA - Z]).+$/。其中(?=.*\d)用于检查密码字符串中(从开头位置开始检查)是否存在数字,(?=.*[a - zA - Z])用于检查是否存在字母。这两个断言只是检查密码是否符合条件,并不影响后续对整个密码字符串(.+)的匹配,而且这样可以在一个正则表达式中同时检查多个条件。
    • 提取符合特定条件的子串
      • 假设要从一个文本中提取所有以http开头且后面跟着数字的网址部分。可以使用/(http)(?=\d)/。这样就可以找到符合条件的http部分,而不包括后面用于检查条件的数字。例如对于文本http1://example.com和https://example.com,这个正则表达式只会匹配第一个http部分,因为只有它后面跟着数字。

http://www.ppmy.cn/ops/135917.html

相关文章

Python设计模式详解之5 —— 原型模式

Prototype 设计模式是一种创建型设计模式&#xff0c;它通过复制已有的实例来创建新对象&#xff0c;而不是通过从头实例化。这种模式非常适合对象的创建成本较高或者需要避免复杂的构造过程时使用。Prototype 模式提供了一种通过克隆来快速创建对象的方式。 1. Prototype 模式…

java 可以跨平台的原因是什么?

我们对比一个东西就可以了&#xff0c;那就是chrome浏览器。 MacOS/Linux/Windows上的Chrome浏览器&#xff0c;那么对于HTML/CSS/JS的渲染效果都一样的。 我们就可以认为ChromeHTML/CSS/JS是跨平台的。 这里面&#xff0c;HTML/CSS/JS是不变的的&#xff0c;对于一个网页&a…

OSG开发笔记(三十三):同时观察物体不同角度的多视图从相机技术

​若该文为原创文章&#xff0c;未经允许不得转载 本文章博客地址&#xff1a;https://blog.csdn.net/qq21497936/article/details/143932273 各位读者&#xff0c;知识无穷而人力有穷&#xff0c;要么改需求&#xff0c;要么找专业人士&#xff0c;要么自己研究 长沙红胖子Qt…

SAM阅读

文章内容&#xff1a; 介绍了 Segment Anything &#xff08;SA&#xff09; 项目&#xff1a;用于图像分割的新任务、模型和数据集。 我们构建了迄今为止&#xff08;迄今为止&#xff09;最大的分割数据集&#xff0c;在 11M 许可和尊重隐私的图像上拥有超过 10 亿个掩码。该…

038集——quadtree(CAD—C#二次开发入门)

效果如下&#xff1a; using Autodesk.AutoCAD.ApplicationServices; using Autodesk.AutoCAD.DatabaseServices; using Autodesk.AutoCAD.EditorInput; using Autodesk.AutoCAD.Geometry; using System; using System.Collections.Generic; using System.Linq; using System.T…

Spring Boot汽车资讯:科技与速度的交响

3系统分析 3.1可行性分析 通过对本汽车资讯网站实行的目的初步调查和分析&#xff0c;提出可行性方案并对其一一进行论证。我们在这里主要从技术可行性、经济可行性、操作可行性等方面进行分析。 3.1.1技术可行性 本汽车资讯网站采用SSM框架&#xff0c;JAVA作为开发语言&#…

fastify 连接 mysql

一、安装依赖 安装 fastify/mysql pnpm add fastify/mysql 二、示例 1. 创建 index.js 连接数据库&#xff0c;确保已经启动数据库&#xff0c;输入数据库的账号&#xff0c;密码&#xff0c;域名&#xff0c;端口&#xff0c;库名。 import Fastify from "fastify&…

容器运行时 AND Docker

容器运行时 and Docker 什么是Docker Docker 使用 Google 公司推出的 Go 语言 进行开发实现&#xff0c;基于 Linux 内核的 cgroup&#xff0c;namespace&#xff0c;以及 AUFS 类的 Union FS 等技术&#xff0c;对进程进行封装隔离&#xff0c;属于 操作系统层面的虚拟化技术…