正则表达式

正则表达式中的 \w 并不代表字母, 而是代表 [a-zA-Z0-9_], 因此错误理解会导致数字和下划线也被纳入匹配.

正则表达式中的 [.^x] 形式无法像预期那样发挥作用, 此时的 . 会被视作一个普通的字符.

通常用来匹配特定语言的字符.

参考: https://2ality.com/2017/07/regexp-unicode-property-escapes.html

\p{Emoji}

\p{Han}
等价于
\p{Script=Han}

JavaScript的正则表达式默认使用贪婪模式, 会尽可能匹配长内容.

注意, 用 | 表示的字符串匹配不受贪婪影响, 并不会返回最长的匹配结果, 只会返回最早的匹配结果,
因此需要手动调整先后顺序来决定匹配使用的模式.

在量词后加上 ? 将使得相关匹配项变成非贪婪模式,
在非贪婪模式下匹配将尽可能匹配短内容, 这会返回更多匹配项:

'12345'.match(/\d+/g)
// ["12345"]
'12345'.match(/\d+?/g)
// ["1", "2", "3", "4", "5"]

m标志位代表多行匹配, ^和$将用于匹配任意行的开头和结尾, 而不再是整个字符串的开头和结尾.

'1\n2\n3'.match(/^\d+$/g)
// null
'1\n2\n3'.match(/^\d+$/mg)
// ["1", "2", "3"]
const text = `
[name1](url1)
[name2](url2)
`
for (const { groups } of text.matchAll(/\[(?<name>\S+)\]\((?<url>\S+)\)/g)) {
console.log(groups.name)
console.log(groups.url)
}

引用分为两种, 一种是正则表达式内引用, 一种是替换时引用, 前者用于正则表达式内, 后者用于replace方法.

正则表达式内引用从 \1 开始索引(因为 \0 是ASCII里的空字符NUL)

'12'.match(/^(\d)\1$/)
// null
'11'.match(/^(\d)\1$/)
// ["11", "1"]

$& 引用整个匹配到的字符串, 可以用以下函数转义所有特殊字符.

function escapeRegExp(string) {
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
}

替换时引用在replace的第二个参数里使用, 从$1开始索引.

'1234'.replace(/^(\d)(\d)(\d)(\d)$/g, '[$$]') // $$ 用于代表原本的 $
// [$]
'1234'.replace(/^(\d)(\d)(\d)(\d)$/g, '[$1][$2][$3][$4]')
// [1][2][3][4]
'1234'.replace(/^(\d)(\d)(\d)(\d)$/, '[$&]')
// [1234]
'1234'.replace(/^(?:\d)(\d)(\d)(\d)$/, '[$1][$2][$3][$4]') // 第一个数字为非获取匹配
// [2][3][4][$4] 只匹配到3个, $4不存在

非获取匹配是获取匹配的反面, 在使用括号 () 的情况下, 非获取匹配并不会作为匹配项返回
(也不能用于后向引用).
非获取匹配通常是为了使一个由多个字符组成的匹配项能够加上量词, 却又不希望该匹配项会作为捕获的结果返回.
非获取匹配使用 (?:).

'1234'.match(/^(\d)(\d)(\d)(\d)$/)
// ["1234", "1", "2", "3", "4"]
'1234'.match(/^(?:\d)(?:\d)(?:\d)(?:\d)$/)
// ["1234"]
'1234'.match(/^(?:\d)(\d)(\d)(?:\d)$/)
// ["1234", "2", "3"]
'1234'.match(/^(\d)(?:\d){2}(\d)$/)
// ["1234", "1", "4"]

零宽度断言是一种边界检查, 影响的是正则表达式的整体匹配情况, 断言内的部分不会出现在整体匹配结果里.

^$ 也是零宽度断言, 意识到零宽度断言是边界检查有助于理解下面的其他零宽度断言.

零宽度断言不是一种"匹配", 而是一种"描述性的检查",
这就是为什么零宽度断言 经常放在表达式的边缘
(不在表达式边缘的情况常常有替代的方法可以直接处理匹配, 所以少见).

最简单的理解方式是, 正则引擎在遇到零宽度断言时, 会将其当作一种突发的检查命令,
当满足断言的检查条件时, 将继续处理之后的内容.

(?=)

非获取匹配经常能代替正向断言.

// 正向断言
'Hello World'.match(/Hello(?= World)/g)
// ["Hello"]
// 非获取匹配
'Hello World'.match(/Hello(?: World)/g)
// ["Hello World"]
// 正向断言做不到, 因为断言之后的内容不可能同时是" My "和"World"
'Hello My World'.match(/Hello(?= My )World/g)
// null
// 非获取匹配能做到, 因为它是匹配
'Hello My World'.match(/Hello(?: My )World/g)
// ["Hello My World"]

(?!)

'Hello Kitty'.match(/Hello(?! World)/g)
// ["Hello"]
'Hello World'.match(/Hello(?! World)/g)
// null
// 这是一句废话, 因为断言之后的内容只能是" Kitty", 必然不可能是" World", 可以直接去掉断言部分.
'Hello Kitty'.match(/Hello(?! World) Kitty/g)
// ["Hello Kitty"]

(?<=)

和正向断言一样, 非获取匹配经常能代替反向断言.

// 反向断言
'Hello World'.match(/(?<=Hello )World/g)
// ["World"]
// 非获取匹配
'Hello World'.match(/(?:Hello )World/g)
// ["Hello World"]
// 反向断言做不到, 因为断言之前的内容不可能同时是"Hello"和" My "
'Hello My World'.match(/Hello(?<= My )World/g)
// null
// 非获取匹配能做到
'Hello My World'.match(/Hello(?: My )World/g)
// ["Hello My World"]

(?<!)

'Hello World'.match(/(?<!Hello )World/g)
// null
'Goodbye World'.match(/(?<!Hello )World/g)
// ["World"]
// 这是一句废话, 因为断言前面只能是"Goodbye ", 必然不可能是"Hello ", 可以直接去掉断言部分.
'Goodbye World'.match(/Goodbye (?<!Hello )World/g)
// ["Goodbye World"]