【Javascript】 从零学习正则表达式

133 阅读8分钟

前言

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第2天,点击查看活动详情

两种方法定义正则表达式

  1. 字面量:与字符串字面量就是包含在一对引号("") 中的字符类似,正则表达式字面量就是包含在一对斜杠(//) 之间的字符。

    // 匹配以"a"作为最后一个字符的字符串,忽略大小写
    let pattern1 = /a$/i; 
    
  2. RegExp构造函数:它接收两个参数:模式字符串和(可选的)标记字符串

    // 跟 pattern1 一样,只不过是用构造函数创建的
    let pattern2 = new RegExp("a$", "i");
    

我们可以看到一个正则表达式是由一系列的字符组成,在这个例子里面:

正则表达式/a$/i包含三个字符

  • 第一个 “a” 匹配自身属性
  • 第二个 “$” 作为一个特殊字符,匹配字符串的末尾
  • 第三个 “i” 作为一个标志,放在第二个斜杠字符后面,表示希望匹配忽略大小写

标志

首先来解释一下第三个字符的 “标志” 是什么,它们通常放在第二个斜杠字符后面

正则表达式的 pattern(模式) 可以是任何简单或复杂的正则表达式。

而每个正则表达式可以带零个或多个标志,用于控制正则表达式的模式

  • g全局模式(global) ,表示查找字符串的全部内容,而不是找到第一个匹配的内容就结束
  • i不区分大小写模式,表示在查找匹配时忽略 pattern 和字符串的大小写
  • m多行模式(multiline) ,表示查找到一行文本末尾时会继续查找
  • y粘附模式(sticky) ,表示只查找从 lastIndex 开始及之后的字符串
  • uUnicode 模式,启用 Unicode 匹配,如果不使用这个标志,那你的正则表达式将无法识别表情符号和其他需要16位以上表示的字符(包括很多中文字符)
  • sdotAll 模式,表示元字符 “.” ,匹配任何字符(包括\n 或\r)

如果不作特殊要求,我们也可以默认选择不加标志

字符类

下面是三个在正则表达式里操作字符类的应用例子:

  • 把个别字面值字符放到方括号[ ] 中可以组合成字符类

    正则表达式/[abc]/匹配:abc中的任意一个字符。

  • 插入符号 ^ 作为方括号中的第一个字符可以组成排除性字符类

    正则表达式/[^abc]/匹配:除abc之外的任意一个字符。

  • 字符类可以使用连字符 - 表示字符范围

    正则表达式/[a-z]/匹配:拉丁字母表中az的任意一个小写字母。

JavaScript正则表达式语法中包含一些特殊字符和转义序列来表示这些常用字符类

image-20221122115112666

所有特殊字符类转义序列本身也可以自由组合出现在方括号中

比如\w匹配任意单词字符\S匹配任意非空白字符\d匹配任意数字

那么/[\w\S\d]/匹配任意单词字符或非空白字符或数字

到这里是不是觉得已经有点意思了

有一个特例,即\b转义序列如果出现在字符类中,\b表示退格字符。因此要在正则表达式中表示一个退格字符的字面值,就要使用只包含一个元素的字符类:/[\b]/

字面量字符

有一些英文标点符号在正则表达式中具有特殊含义,包括:

( ) [ ] { } \ | / ? ! ^ $ = : * + .

大部分标点符号在正则表达式中都有一种或多种特殊功能(除了""@),这些标点符号如果想要在正则表达式中使用的话,必须使用反斜杠转义

下面是两个例子:

// 匹配第一个"bat"或"cat",忽略大小写
let pattern1 = /[bc]at/i; 
// 匹配第一个"[bc]at",忽略大小写
let pattern2 = /\[bc\]at/i; 
// 匹配所有以"at"结尾的三字符组合,忽略大小写
let pattern3 = /.at/gi; 
// 匹配所有".at",忽略大小写
let pattern4 = /\.at/gi; 

特别的,一些字母和数字前加上反斜杠会有特殊的含义,所以要在正则表达式中正确使用反斜杠符号

image-20221122113511251

重复字符

正则表达式里面很重要的一种语法。

在学习完前文的知识之后会发现,如果你想要匹配两个数字,你可以使用/\d\d/的形式,那如果是五个,十个,二十个呢?

在一些复杂的描述下我们可以采取一些更高级的重复字符来帮助我们

image-20221122164353531

结合上表,我们可以举出一些例子:

//如何匹配5到11位数字(比如QQ号)?
let a = /\d{5,11}/; 
//如何匹配刚好2个字母,前跟一个可选的空格后跟一个可选的数字?
let b = /\s?\w{2}\d?/; 
//如何匹配零个或多个非“/”字符
let c = /[^//]*/;
//如何匹配一个“hello”且前后至少一个空格
let d = /\s+hello\s+/;

看到这,是不是感觉已经基本能看懂别人发的长串正则表达式了

任选、分组和引用字符

竖线字符 “|” :用于分隔任选模式,他的作用就像我们的逻辑表达式里面的 “||”

/\d{3}|[a-z]{4}/匹配3个数字4个小写字母

需要注意的是:在找到匹配项之前,会从左到右依次适配任选模式。如果左边的任选模式匹配,则忽略右边的模式。比如,/a|ab/应用到字符串 “ab” 只会匹配字母 “a”

圆括号 “()”

  1. 把独立的模式分组为子表达式,从而让这些模式可以被|*+?等当作一个整体。例如/(ab|cd)+|ef/匹配字符串 “ef”,也匹配一个或多个字符串 “ab”“cd”

  2. 在完整的模式中定义子模式。回引前面的子表达式要使用 “\”字符加上数字。这里的数字指的是圆括号分组的子表达式在整个正则表达式中的位置。例如, “\1” 回引第一个子表达式, “\3” 回引第三个。

    由于子表达式可能会嵌套,所以它们的位置是按照左括号来计算的。例如,在下面的正则表达式中,嵌套的子表达式([Ss]cript)要使用 "\2" 来引用:

    let a = /[Jj]ava([Ss]cript?)\sis\s(fun\w*)/
    

    我们可以利用这个特性来完成一些操作:

    //匹配位于一对单或双引号间的0个或多个字符
    //必须 都是单引号 或 都是双引号
    let a = /(['"])[^'"]*\1 /;
    

    这个 "\1" 匹配第一个圆括号分组的子表达式匹配的内容。在这个例子中,它强制结尾的引号必须匹配开始的引号。

    如果不想让圆括号分组的子表达式生成数字引用,那么可以用 (?:..) 的形式。

    let a = /[Jj]ava(?:[Ss]cript?)\sis\s(fun\w*)/
    

    刚才这个例子中,子表达式(?:[Ss]cript)仅仅是一个分组,从而让 "?" 重复字符可以应用到该组。这样修改后的圆括号不会产生引用,因此在这个正则表达式中 "\2" 引用的是(fun\w*)匹配的文本。

    image-20221122195452910

任选、分组和引用字符的使用将前面冗长的表达式变得更加直观

锚点字符

假如现在有个需求:我们想匹配到Java这个单词

利用前面的知识,我们或许会采用/\sJava\s/的模式,在Java这个单词前后都需要有空格,避免错误匹配到类似Javascript这个单词身上这种结果。

但是这种做法也有问题,如果Java这个单词在句头或者句尾怎么办,其次我们获得的这个匹配项会自带两个空格,这不是我们想要的。

那么有没有一种字符可以匹配字符间的位置非实际的字符呢,答案是有的:

今天最后的重头戏,正则表达式锚点字符

例如,我们可以利用锚点字符\b匹配ASCII词边界,即\w (ASCII单词字符)\W (非单词字符) 的边界,或者ASCII单词字符与字符串开头或末尾的边界。

\b这样的组件并不表示匹配的字符串中用到的任何字符,它们表示的是匹配可以发生的合法位置。它们把模式锚定到被搜索字符串中特定的位置。

这些字符不占位,但是相当于一个指示位置的锚点

image-20221122201625044

UniCode字符类

ES2018中,如果正则表达式使用了u标志,则支持字符类\p{...}及其排除性形式\P{...},此时可以完成一些其他的标志无法实现的匹配

我们知道\d字符类只匹配ASCII数字

  • 如果想匹配一个十进制数字,可以使用/\p{Decimal_Number}/u
  • 如果想匹配任一个非十进制数字,可以使用/\P{Decimal_Number}/u
  • 如果想匹配任意类数值字符,包括分数和罗马数字,可以使用/\p{Number}/u

我们知道\w字符类只匹配ASCII文本

  • 如果想匹配一个国际化文本,可以使用/\p{Alphabetic}\p{Decimal_Number\p{Mark}}/u

  • 如果想要完全兼容世界上所有语言的话则要添加“Connector_Punctuation”“Join_Control”`两个分类。

  • 也可以匹配特定字母表或文字(script) 中字符的正则表达式,比如:

    let greekLetter = /\p{Script=Greek}/u
    let cyrillicLetter = /\p{Script=Cyrillic}/u

参考文献

《JavaScript高级程序设计》(第4版)

《JavaScript权威指南》(第7版)