正则

基础知识

对比分析

与普通函数操作字符串来比较,正则表达式可以写出更简洁、功能强大的代码。

下面使用获取字符串中的所有数字来比较函数与正则的差异。

const str = 'dsfafa1214fdsfs1222'

console.time('fn')
const nums = [...str].filter(a => !Number.isNaN(Number.parseInt(a)))
console.log(nums.join(''))
console.timeEnd('fn')
const str = 'dsfafa1214fdsfs1222'

console.time('re')
const num = str.match(/\d/g).join('')
console.timeEnd('re')

创建正则

JS提供字面量与对象两种方式创建正则表达式

字面量创建

使用//包裹的字面量创建方式是推荐的作法,但它不能在其它使用变量

const str = 'mkimq'
const a = 'i'

console.log(eval(`/${a}/`).test(str))
console.log(/m/.test(str))

// a 变量时将不可以查询
const a = 'm'
console.log(/a/.test(str))

虽然可以使用 eval 转换为js语法来实现将变量解析到正则中,但是比较麻烦,所以有变量时建议使用下面的对象创建方式

const str = 'mkimq'
const a = 'm'

console.log(eval(`/${a}/`).test(str))

对象创建

当正则需要动态创建时使用对象方式

const str = 'mkimq'
const m = 'mk'
const reg = new RegExp(m)

console.log(reg.test(str))

选择符

|这个符号带表选择修释符,也就是|左右两侧有一个匹配到就可以。

const tel = '010-12345678'

console.log(tel.match(/(010|020)\-\d{7,8}/))

字符转义

转义用于改变字符的含义,用来对某个字符有多种语义时的处理。

假如有这样的场景,如果我们想通过正则查找/符号,但是 /在正则中有特殊的意义。如果写成///这会造成解析错误,所以要使用转义语法 ///来匹配。

字符边界

使用字符边界符用于控制匹配内容的开始与结束约定。

边界符说明
^匹配字符串的开始
$匹配字符串的结束,忽略换行符

元子字符

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

字符列表

元字符说明示例
\d匹配任意一个数字[0-9]
\D与除了数字以外的任何一个字符匹配[^0-9]
\w与任意一个英文字母,数字或下划线匹配[a-zA-Z_]
\W除了字母,数字或下划线外与任何字符匹配[^a-zA-Z_]
\s任意一个空白字符匹配,如空格,制表符\t,换行符\n[\n\f\r\t\v]
\S除了空白符外任意一个字符匹配[^\n\f\r\t\v]
.匹配除换行符外的任意字符

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

模式修饰

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

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

lastIndex

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

  • 必须结合g修饰符使用
  • 对exec方法有效
  • 匹配完成时,lastIndex会被重置为0
let mk = `mkimq在线文档,网址是mkimq.com`
let reg = /mkimq(.{2})/g
reg.lastIndex = 10 // 从索引10开始搜索
console.log(reg.exec(mk))
console.log(reg.lastIndex)

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

原子表

在一组字符中匹配某个元字符,在正则表达式中通过元字符表来完成,就是放到[] (方括号)中。

原子表说明
[]只匹配其中的一个原子
[^]只匹配"除了"其中字符的任意一个原子
[0-9]匹配0-9任何一个数字
[a-z]匹配小写a-z任何一个字母
[A-Z]匹配大写A-Z任何一个字母

原子组

  • 如果一次要匹配多个元子,可以通过元子组完成
  • 原子组与原子表的差别在于原子组一次匹配多个元子,而原子表则是匹配任意一个字符
  • 元字符组用 () 包裹
变量说明
0匹配到的完整内容
1,2....匹配到的原子组
index原字符串中的位置
input原字符串
groups命名分组
const str = 'mankeung.com'

const d = str.match(/man(keung)\.(com)/)

console.log(d)

// [
// 	'mankeung.com',
// 	'keung',
// 	'com',
// 	index: 0,
// 	input: 'mankeung.com',
// 	groups: undefined
// ]

引用分组

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

const h = `
	<h1>mkimq</h1>
	<span>在线文档</span>
	<h2>mkimq.com</h2>
`

const reg = /<(h[1-6])>([\s\S]*)<\/\1>/gi // \1引入原子组1

console.log(h.replace(reg, `<p>$2</p>`))

分组别名

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

const h = `
	<h1>mkimq</h1>
	<span>在线文档</span>
	<h2>mkimq.com</h2>
`

const reg = /<?<tag>h[1-6]>(?<con>[\s\S]*)<\/\1>/gi

console.log(h.replace(reg, `<p>$<con></p>`))

组别名使用 ?<> 形式定义

重复匹配

基本使用

符号说明
*重复零次或更多次
+重复一次或更多次
?重复零次或一次
{n}重复n次
{n,}重复n次或更多次
{n,m}重复n到m次

禁止贪婪

正则表达式在进行重复匹配时,默认是贪婪匹配模式,也就是说会尽量匹配更多内容,但是有的时候我们并不希望他匹配更多内容,这时可以通过?进行修饰来禁止重复匹配

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

全局匹配

使用match 全局获取页面中标签内容,但并不会返回匹配细节

matchAll

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

const str = 'mankeung'
const reg = /[a-z]/ig

for (const interator of str.matchAll(reg)) {
	console.log(interator)
}

在原型定义 matchAll方法,用于在旧浏览器中工作,不需要添加g 模式运行

String.prototype.matchAll = function(reg) {
	const res = this.match(reg)

	if (res) {
		const str = this.replace(res[0], '^'.repeat(res[0].length))
		const match = str.matchAll(reg) || []
		return [res, ...match]
	}
}

const str = 'mankeung'
console.dir(str.matchAll(/(U)/i))

exec

使用g模式修正符并结合exec循环操作可以获取结果和匹配细节

function search(string, reg) {
	const matchs = []
	let data = ''

	while ((data = reg.exec(string))) {
		matchs.push(data)
	}

	return matchs
}

const str = `
	<h1>mkimq.com</h1>
	<h2>mankeung</h2>
	<h1>在线文档</h1>
`

console.log(search(str, /<(h[1-6])>[\s\S]+?<\/\1>/gi))

字符方法

String提供的支持正则表达式的方法

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

const str = 'mkimq.com'
console.log(str.serach('com'))
console.log(str.search(/\.com/i))

match

const str = 'mkimq.com'
console.log(str.match('com'))
console.log(str.match(/\.com/i))

matchAll

const str = 'mkimq'
const reg = /[a-z]/ig

for (const iterator of str.matchAll(reg)) {
    console.log(iterator)
}

split

const str = '2022-03-18'
console.log(str.split('-'))
console.log(str.split(/-|\//))

replace

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

const str = '2022/03/18'
console.log(str.replace(/\//g, '-'))
变量说明
$$插入一个 "$"。
$&插入匹配的子串。
`$``插入当前匹配的子串左边的内容。
$'插入当前匹配的子串右边的内容。
$n假如第一个参数是 RegExp 对象,并且 n 是个小于100的非负整数,那么插入第 n 个括号匹配的字符串。提示:索引是从1开始
const str = '=mkimq='

console.log(str.replace(/mkimq/g, "$`$&$'"))

const phone = '(010)9999999 (020)88888888'

console.log(phone.replace(/\((\d{3,4})\)(\d{7,8})/g, '$1-$2'))

const txt = '在线文档,记录每一步。'

console.log(txt.replace(/文档/g, `<a href="https://www.mkimq.com">$&</a>`))

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

<body>
    <main>
        <a style="color:red" href="http://www.mkimq.com">
            mkimq
        </a>
        <a id="l1" href="http://baidu.com">百度</a>
        <a href="http://yahoo.com">雅虎</a>
        <h4>http://www.mkimq.com</h4>
    </main>
</body>
<script>
    const main = document.querySelector("body main");
    const reg = /(<a.*href=['"])(http)(:\/\/)(www\.)?(mankeung|mkimq)/gi;
    main.innerHTML = main.innerHTML.replace(reg, (v, ...args) => {
        args[1] += "s";
        args[3] = args[3] || "www.";
        return args.splice(0, 5).join("");
    });
</script>

回调函数

replace 支持回调函数操作,用于处理复杂的替换逻辑

变量名代表的值
match匹配的子串。(对应于上述的$&。)
p1,p2, ...假如replace()方法的第一个参数是一个 RegExp 对象,则代表第n个括号匹配的字符串。(对应于上述的$1,$2等。)例如,如果是用 /(\a+)(\b+)/ 这个来匹配,p1 就是匹配的 \a+,p2 就是匹配的 \b+。
offset匹配到的子字符串在原字符串中的偏移量。(比如,如果原字符串是 'abcd',匹配到的子字符串是 'bc',那么这个参数将会是 1)
string被匹配的原字符串。
NamedCaptureGroup命名捕获组匹配的对象

正则方法

test

检测是否匹配

console.log(/^\w+@\w+\.\w+$/.test('mankeung1011@gmail.com'))

exec

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

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

断言匹配

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

(?=exp)

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

把后面是教程的mkimq汉字加上链接

const str = 'mkimq在线文档,mkimq教程。'
const reg = /mkimq(?=教程)/gi

console.log(str.replace(reg, v => `<a href="https://www.mkimq.com">${v}</a>`))

// const reg = /^(?=[a-z]{5}$)/i // 必须为五位

// 价格后面 添加上 .00
// const reg = /(\d+)(.00)?(?=元)/gi

// str.replace(reg, (v, ...args) => {
//     args[1] = args[1] || '.00'

//     return args.splice(0, 2).join('')
// })

(?<=exp)

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

// 匹配前面是 mkimq
const reg = /(?<=mkimq)\d+/i

// 匹配前后都是数字的内容
const reg = /(?<=\d)[a-z]+(?=\d{3})/i

(?!exp)

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

(?<!exp)

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

贡献者: mankueng