Skip to content

正则表达式

基础知识

创建正则

字面量创建

有变量时使用eval` 转换为 js 语法来实现将变量解析到正则

javascript
let hd = "love";
let a = "v";
console.log(eval(`/${a}/`).test(hd)); //true

对象创建

javascript
let hd = "dotohi.com";
let web = "dotohi";
let reg = new RegExp(web);
console.log(reg.test(hd)); //true

字符转义

使用 RegExp 构建正则时在转义上会有些区别,下面是对象与字面量定义正则时区别

javascript
let price = 12.23;
//含义1: . 除换行外任何字符   含义2: .普通点
//含义1: d 字母d            含义2: \d 数字 0~9
console.log(/\d+\.\d+/.test(price));

//字符串中 \d 与 d 是一样的,所以在 new RegExp 时\d 即为 d
console.log("\d" == "d");

//使用对象定义正则时,可以先把字符串打印一样,结果是字面量一样的定义就对了
console.log("\\d+\\.\\d+");
let reg = new RegExp("\\d+\\.\\d+");
console.log(reg.test(price));

元子字符

元字符是正则表达式中的最小元素,只代表单一(一个)字符

字符列表

元字符说明示例
\s任意一个空白字符匹配,如空格,制表符\t,换行符\n[\n\f\r\t\v]
\S除了空白符外任意一个字符匹配[^\n\f\r\t\v]
.匹配除换行符外的任意字符

所有字符

可以使用 [\s\S][\d\D] 来匹配所有字符

模式修饰

正则表达式在执行时会按他们的默认执行方式进行,但有时候默认的处理方式总不能满足我们的需求,所以可以使用模式修正符更改默认方式。

修饰符说明
i不区分大小写字母的匹配
g全局搜索所有匹配内容
m视为多行
s视为单行忽略换行符,使用. 可以匹配所有字符
yregexp.lastIndex 开始匹配
u正确处理四个字符的 UTF-16 编码

g

不加gmatch的结果

image-20241210204405875

gmatch的结果

image-20241210202701264

u

u可以理解为Unicode,每个字符都有属性,如L属性表示是字母,P 表示标点符号,需要结合 u 模式才有效。其他属性简写可以访问 属性的别名 网站查看。

javascript
//使用\p{L}属性匹配字母
let hd = "loveagri2010.我爱你,加油!"
console.log(hd.match(/\p{L}+/u))

//使用\p{P}属性匹配标点
console.log(hd.match(/\p{P}+/gu));

字符也有 unicode 文字系统属性 Script=文字系统,下面是使用 \p{sc=Han} 获取中文字符 han为中文系统,其他语言请查看 文字语言表

javascript
let hd = `
张三:010-99999999,李四:020-88888888`;
let res = hd.match(/\p{sc=Han}+/gu);
console.log(res);

使用 u 模式可以正确处理四个字符的 UTF-16 字节编码

javascript
let str = "𝒳𝒴";
console.table(str.match(/[𝒳𝒴]/)); //结果为乱字符"�"

console.table(str.match(/[𝒳𝒴]/u)); //结果正确 "𝒳"

lastIndex

RegExp 对象lastIndex 属性可以返回或者设置正则表达式开始匹配的位置

  • 必须结合 g 修饰符使用
  • exec 方法有效
  • 匹配完成时,lastIndex 会被重置为 0
javascript
let hd = `静思田园不断分享视频教程,静思田园网址是 dotohi.com`
let reg = /静思田园(.{2})/g
reg.lastIndex = 10 //从索引10开始搜索
console.log(reg.exec(hd))
console.log(reg.lastIndex)

reg = /\p{sc=Han}/gu
while ((res = reg.exec(hd))) {
    console.log(res[0])
}

y

我们来对比使用 yg 模式,使用 g 模式会一直匹配字符串

javascript
let hd = "udunren";
let reg = /u/g;
console.log(reg.exec(hd));
console.log(reg.lastIndex); //1
console.log(reg.exec(hd));
console.log(reg.lastIndex); //3
console.log(reg.exec(hd)); //null
console.log(reg.lastIndex); //0

但使用y 模式后如果从 lastIndex 开始匹配不成功就不继续匹配了,如果匹配就会一直匹配下去。

javascript
let hd = "udunren";
let reg = /u/y;
console.log(reg.exec(hd));
console.log(reg.lastIndex); //1
console.log(reg.exec(hd)); //null
console.log(reg.lastIndex); //0

因为使用 y 模式可以在匹配不到时停止匹配,在匹配下面字符中的 qq 时可以提高匹配效率

javascript
let hd = `我爱你QQ群:11111111,999999999,88888888
随便写点,网址是 dotohi.com`;

let reg = /(\d+),?/y;
reg.lastIndex = 7;
while ((res = reg.exec(hd))) console.log(res[1]);

原子表

原子表中有些正则字符不需要转义,如果转义也是没问题的,可以理解为在原子表中. 就是小数点

javascript
let str = "(dotohi.com)+";
console.table(str.match(/[().+]/g));

//使用转义也没有问题
console.table(str.match(/[\(\)\.\+]/g));

原子组

原子组与原子表的差别在于原子组一次匹配多个元子,而原子表则是匹配任意一个字符

基本使用

没有添加 g 模式修正符时只匹配到第一个,匹配到的信息包含以下数据

变量说明
0匹配到的完整内容
1,2....匹配到的原子组
index原字符串中的位置
input原字符串
groups命名分组

match中使用原子组匹配,会将每个组数据返回到结果中

  • 0 为匹配到的整个匹配正则表达式的完整内容
  • 1/2 ...等 为原子级内容
  • index 匹配的开始位置
  • input 原始数据
  • groups 组别名
javascript
let hd = "houdunren.com";
console.log(hd.match(/houdun(ren)\.(com)/));
//["houdunren.com", "ren", "com", index: 0, input: "houdunren.com", groups: undefined]

下面使用原子组匹配标题元素

javascript
let hd = `
  <h1>linux</h1>
  <span>php</span>
  <h2>java</h2>
`;

console.table(hd.match(/<(h[1-6])[\s\S]*<\/\1>/g));

引用分组

\n 在匹配时引用原子组, $n 指在替换时使用匹配的组数据。下面将标签替换为p标签

javascript
let hd = `
  <h1>linux</h1>
  <span>php</span>
  <h2>java</h2>
`;

let reg = /<(h[1-6])>([\s\S]*)<\/\1>/gi;
console.log(hd.replace(reg, `<p>$2</p>`));

如果只希望组参与匹配,便不希望返回到结果中使用 (?: 处理。下面是获取所有域名的示例

js
let str = `
https://www.baidu.com
http://jd.com
https://cc.com
`

let dd = /https?:\/\/((?:\w+\.)?\w+\.(?:com|org|cn))/gi
while ((v = dd.exec(str))) {
    console.dir(v)
}

image-20241210215924699

分组别名

如果希望返回的组数据更清晰,可以为原子组编号,结果将保存在返回的 groups字段中

js
let hd = "<h1>dotohi.com</h1>"
console.dir(hd.match(/<(?<tag>h[1-6])[\s\S]*<\/\1>/));

image-20241210220047272

组别名使用 ?<> 形式定义,下面将标签替换为p标签

javascript
let hd = `
<h1>linux</h1>
<span>php</span>
<h2>java</h2>
`
let reg = /<(?<tag>h[1-6])>(?<con>[\s\S]*)<\/\1>/gi
console.log(hd.replace(reg, `<p>$<con></p>`));

示例:

html
<body>
    <a href="https://www.baidu.com">百度</a>
    <a href="https://www.jd.com">京东</a>
    <a href="https://www.mi.com.cn">小米</a>
</body>

<script>
    let body = document.body.innerHTML
    let reg = /<a\s*.+?(?<link>https?:\/\/(\w+\.)+(com|org|cc|cn)).*>(?<title>.+)<\/a>/gi
    const links = []
    console.log([...body.matchAll(reg)].map(item => item.groups));
</script>

禁止贪婪

使用说明
*?重复任意次,但尽可能少重复
+?重复 1 次或更多次,但尽可能少重复
??重复 0 次或 1 次,但尽可能少重复
{n,m}?重复 n 到 m 次,但尽可能少重复
{n,}?重复 n 次以上,但尽可能少重复

全局匹配

match

html
<body>
    <h1>百度</h1>
    <h2>京东</h2>
    <h1>小米</h1>
</body>

<script>
    function elem(tag,r='') {
        const reg = new RegExp("<(" + tag + ")>.+?<\.\\1>",r)
        return document.body.innerHTML.match(reg)
    }
    console.log(elem("h1"));
</script>

gg结果

image-20241210224608738

matchAll

在新浏览器中支持使用 matchAll 操作,并返回迭代对象,需要添加 g 修饰符

html
<body>
    <h1>百度</h1>
    <h2>京东</h2>
    <h1>小米</h1>
</body>

<script>
    const reg = new RegExp("<(?<tag>h[1-6])>(.+?)<\/\\1>", "g")
    let iterators = document.body.innerHTML.matchAll(reg)

    for (const iterator of iterators) {
        console.log(iterator)
    }

  	//另两种种迭代遍历方式
    while ((data = iterators.next().value)) {
        console.log(data)
    }
  
    while (({ done, value } = iterators.next()) && !done) {
        console.log(value)
    }
</script>

![image-20241210230647265](/Users/press/Library/Application Support/typora-user-images/image-20241210230647265.png)

exec

exec() 方法在一个指定字符串中执行一个搜索匹配。返回一个结果数组或null。在设置了 globalsticky 标志位的情况下(如 /foo/g/foo/y),JavaScript RegExp 对象是有状态的。它们会将上次成功匹配后的位置记录在 lastIndex 属性中。如果最后一个未匹配到,则lastIndex会被置为0。

html
<body>
    <h1>百度</h1>
    <h2>京东</h2>
    <h1>小米</h1>
</body>

<script>
    const reg = new RegExp("<(?<tag>h[1-6])>(.+?)<\/\\1>", "g")
    let iterators = reg.exec(document.body.innerHTML)
    console.log(iterators, reg.lastIndex)
    iterators = reg.exec(document.body.innerHTML)
    console.log(iterators, reg.lastIndex)
    iterators = reg.exec(document.body.innerHTML)
    console.log(iterators, reg.lastIndex)
    iterators = reg.exec(document.body.innerHTML)
    console.log(iterators, reg.lastIndex)
</script>

image-20241210235525395

如果 d 标志被使用,则 RegExp.prototype.hasIndices 的值是 true;否则是 falsed 标志表示正则表达式匹配的结果应该包含每个捕获组子字符串开始和结束的索引。它不会以任何方式改变正则表达式的解释或匹配行为,它只在匹配的结果中提供额外的信息。

js
const str1 = "foo bar foo"

const regex1 = /foo/dg

console.log(regex1.hasIndices) // Output: true

console.log(regex1.exec(str1).indices[0]) // Output: Array [0, 3]
console.log(regex1.exec(str1).indices[0]) // Output: Array [8, 11]

const str2 = "foo bar foo"

const regex2 = /foo/

console.log(regex2.hasIndices) // Output: false

console.log(regex2.exec(str2).indices) // Output: undefined

字符方法

search() 方法用于检索字符串中指定的子字符串,也可以使用正则表达式搜索,返回值为索引位置

javascript
let str = "dotohi.com"
console.log(str.search(".com"))
console.log(str.search(/\.com/i));//6

match

直接使用字符串或者正则搜索,如果使用 g 修饰符时,就不会有结果的详细信息了(可以使用 exec),下面是获取所有 h1~6 的标题元素

javascript
let body = document.body.innerHTML;
let result = body.match(/<(h[1-6])>[\s\S]+?<\/\1>/g);
console.table(result);

matchAll

在新浏览器中支持使用 matchAll 操作,并返回迭代对象

replace

replace 方法不仅可以执行基本字符替换,也可以进行正则替换

替换字符串可以插入下面的特殊变量名:

变量说明
$$插入一个 "$"。
$&插入匹配的子串。
$`插入当前匹配的子串左边的内容。
$'插入当前匹配的子串右边的内容。
$n假如第一个参数是 RegExp 对象,并且 n 是个小于 100 的非负整数,那么插入第 n 个括号匹配的字符串。提示:索引是从 1 开始

在love前后添加三个=$`$'

javascript
let hd = "=love=";
console.log(hd.replace(/love/g, "$`$`$&$'$'"));

把电话号用 - 连接,$n的使用。

javascript
let hd = "(010)99999999 (020)8888888";
console.log(hd.replace(/\((\d{3,4})\)(\d{7,8})/g, "$1-$2"));

把所有教育汉字加上链接

javascript
<body>
  在线教育是一种高效的学习方式,教育是一生的事业
</body>
<script>
  const body = document.body;
  body.innerHTML = body.innerHTML.replace(
    /教育/g,
    `<a href="https://www.dotohi.com">$&</a>`
  );
</script>

为链接添加上https ,并补全 www.

html
<body>
  <main>
      <a style="color:red" href="http://www.face.com">
          开源系统
      </a>
      <a id="l1" href="http://dd.com">滴滴</a>
      <a href="http://yahoo.com">雅虎</a>
      <h4>http://www.hdcms.com</h4>
  </main>
</body>
<script>

  document.body.innerHTML = document.body.innerHTML.replace(/(<a.+href=["'])(?<https>https?)(:\/\/)(?<www>www\.)?(.+)(["'].*>)/gm, (v, ...arg) => {
      arg[1] = 'https'
      arg[3] = 'www.'
      return arg.splice(0, 6).join('')
  });
</script>

正则方法

test

test() 方法执行一个检索,用来查看正则表达式与指定的字符串是否匹配。返回 truefalse

javascript
let str = "hello world!";
let result = /^hello/.test(str);
console.log(result);
// true

exec

不使用 g 修饰符时与 match 方法使用相似,使用 g 修饰符后可以循环调用直到全部匹配完。

  • 使用 g 修饰符多次操作时使用同一个正则,即把正则定义为变量使用
  • 使用 g 修饰符最后匹配不到时返回 null

断言匹配

断言虽然写在扩号中但它不是组,所以不会在匹配结果中保存,可以将断言理解为正则中的条件。

(?=exp)

零宽先行断言 ?=exp 匹配后面为 exp 的内容

js
var str="abZW863";
var reg=/ab(?=[A-Z])/;
console.log(str.match(reg));

下面是将价格后面 添加上 .00

javascript
<script>
  let lessons = `
    js,200元,300次
    php,300.00元,100次
    node.js,180元,260次
  `;
  let reg = /(\d+)(.00)?(?=元)/gi;
  lessons = lessons.replace(reg, (v, ...args) => {
    args[1] = args[1] || ".00";
    return args.splice(0, 2).join("");
  });
  console.log(lessons);
</script>

(?<=exp)

零宽后行断言 ?<=exp 匹配前面为 exp 的内容

匹配前面是love 的数字

javascript
let hd = "love789lovely666";
let reg = /(?<=love)\d+/i;
console.log(hd.match(reg)); //789

匹配前后都是数字的内容

javascript
    let hd = "love789lovely666"
    let reg = /(?<=\d)[[a-zA-Z]+(?=\d)/gi
    console.log(hd.match(reg)) //lovely

注意\1对应的匹配对象。

html
<body>
    <a href="https://baidu.com">百度</a>
    <a href="https://yahoo.com">雅虎</a>
</body>
<script>
    const body = document.body
    let reg = /(?<=<a.*href=(['"])).+?(?=\1)/gi
    console.log(body.innerHTML.match(reg))
    body.innerHTML = body.innerHTML.replace(reg, "https://dotohi.com");
</script>

(?!exp)

零宽负向先行断言 后面不能出现 exp 指定的内容

使用 (?!exp)字母后面不能为两位数字

js
let hd = "love12"
let reg = /[a-z]+(?!\d{2})$/i
console.table(reg.exec(hd)); //null

不能出现you

html
<body>
    <main>
        <input type="text" name="username" />
    </main>
</body>
<script>
    const input = document.querySelector(`[name="username"]`)
    input.addEventListener("keyup", function () {
        const reg = /^(?!.*you.*)[a-z]{8,13}$/i
        console.log(this.value.match(reg))
    });
</script>

(?<!)

零宽负向后行断言 前面不能出现 exp 指定的内容

javascript
let hd = "love99you";
let reg = /(?<!\d+)[a-z]+/ig;
console.log(reg.exec(hd)); //love
console.log(reg.exec(hd)); //ou