语法篇
元字符
( [ { \ ^ $ | ) ? * + . ] }
匹配这些字符的时候建议都加\进行转义
匹配模式 i g m u s
- i 表示对大小写不敏感
- g 查找所有匹配的字符,而不是找到第一个就不找了
- m 多行匹配,会改变
^和 $ 的行为 - u 可以匹配4字节的unicode编码
- s dotAll 模式, .可以匹配换行符
console.log(/^\uD83D/.test('\uD83D\uDC2A')) //true
console.log(/^\uD83D/u.test('\uD83D\uDC2A')) //false
默认情况下, . 可 以匹配任意字符,除了换行符,且 . 不能匹配 unicode字符,需要使用 u 选项启用unicode模式才可以。
/foo.bar/.test('foo\nbar') // false
/foo.bar/s.test('foo\nbar') // 开启dotAll 模式 . 就可以匹配换行符了
类 [] 查找某个范围内的字符
- [abc] 查找方括号之间的任何字符
- [0-9] 查找任何从0 至 9 的数字
只要匹配到一次就算匹配到了。
/[abc]/.test('fdsfda') // true
/[abc]/.test('a432423') // true
/[abc]/.test('a') // true
/[0-9]/.test('9-fdsfa') // true
/[0-9]/.test('2ewrewr') // true
还有一些预定义类方便直接使用
| 预定义类 | 等价 | 描述 |
|---|---|---|
| \s | [\t\n\x0B\f\r] | 空格 |
| \S | [^\t\n\x0B\f\r] | 非空格 |
| \d | [0-9] | 数字 |
| \D | [^0-9] | 非数字 |
| \w | [a-zA-Z_0-9] | 单词字符 ( 字母、数字、下划线) |
| \W | [^a-zA-Z_0-9] | 非单词字符 |
| . | [^\r\n] | 任意字符,除了回车与换行外所有字符 |
| \f | \x0c \cL | 匹配一个换页符 |
| \n | \x0a \cJ | 匹配一个换行符 |
| \r | \x0d \cM | 匹配一个回车符 |
| \t | \x09 \cI | 匹配一个制表符 |
| \v | \x0b \cK | 匹配一个垂直制表符 |
| \xxx | 查找以八进制数 xxx 规定的字符 | |
| \xdd | 查找以十六进制数 dd 规定的字符 | |
| \uxxxx | 查找以十六进制数 xxxx 规定的 Unicode 字符 | |
. | 匹配除换行符(\n、\r)之外的任何单个字符。要匹配包括'\n'在内的任何字符 |
量词 + ? * {n,m}
| 量词 | 等价 | 描述 |
|---|---|---|
| n * | {0,} | 匹配零个或多个n |
| n ? | {0,1} | 可选,匹配0次或一次 |
| n + | {1,} | 匹配至少一个 n 的字符串 |
| {n} | 匹配n次 | |
| {n,m} | 匹配n到m次 | |
| {n,} | 至少匹配n次 |
边界 ^ $ \b \B
- /^xx/ 匹配 以xx开始, 在类 [] 中表示排除非的意思
/^xx/.test('xx') // true
/^xx/.test('x') // false
- $ 表示以 xx结束
/aaaxx$/.test('aaaxx') // true
console.log(/aaaxx$/.test('xx')) // false
- \b 表示单词边界 也就是指的单词和空格间的位置
/er\b/.test('never') // true 可以匹配 never 中的 er
/er\b/.test('er sdfas') // true
/er\b/.test('erb') // false 不能匹配
- \B 表示 匹配非单词边界
/er\B/.test('era') // true
/er\B/.test('era fadsfa') // true
/er\B/.test('er fadsfa') // false
分组 ()
分组使用(),提取相匹配的字符串,使量词作用于分组。
{}
/hehe{3}/.test('heheee') // true 是把e匹配三次,而不是整个单词
/hehe{3}/.test('heheeeeee') // true
()
/(hehe){3}/.test('hehehehehehe') // true 相同的视为一个组,然后量词再作用于他
或 |
分组中可以使用 | 表示或的意思
/T(oo|ii)m/.test('Toom') // true
/T(oo|ii)m/.test('Tiim') // true
反向引用
使用 () 之后可以使用 $1 - $9 等来匹配
'2018-02-11'.replace(/(\d{4})\-(\d{2})\-(\d{2})/g, '$1/$2/$3') // "2018/02/11"
'2018-02-11'.replace(/(\d{4})\-(\d{2})\-(\d{2})/g, '$2/$3/$1') // "02/11/2018"
后向引用
\n 表示后项引用, \1 是指在正则表达式中,从左往右第一个 () 中的内容;以此类推 , \2 代表第二个 () , \0 表示整个表达式。
匹配多个日期格式,表达式中的 \1 代表重复 引用第一个() 既 (\-|\/|.)
貌似重复的必须是“确定”的才行。
/\d{4}(\-|\/|.)\d{1,2}\1\d{1,2}/.test('2016-03-26') // true
/\d{4}(\-|\/|.)\d{1,2}\1\d{1,2}/.test('2016/03/26') // true
/\d{4}(\-|\/|.)\d{1,2}\1\d{1,2}/.test('2016.03.26') // true
分组命名 (?)
ES2018 之前的分组是通过数字命名的
let res = /(\d{4})\-(\d{2})\-(\d{2})/.exec('2021-10-30')
console.log(res[0]) // 匹配到的所有字符串 2021-10-30
console.log(res[1]) // 2021
console.log(res[2]) // 10
console.log(res[3]) // 30
现在可以通过指定分组名称,增加代码的可读性,以便于维护
let res = /(?<year>\d{4})\-(?<month>\d{2})\-(?<day>\d{2})/.exec('2018-10-25')
console.log(res)
[
"2018-10-25",
"2018",
"10",
"25"
]
console.log(res.groups.year) // 2018
console.log(res.groups.month) // 10
console.log(res.groups.day) // 25
还可以结合之前的 replace 方法
let reg = /(?<year>\d{4})\-(?<month>\d{2})\-(?<day>\d{2})/
let str = '2021-10-30'
let res = str.replace(reg, '$<month>/$<day>/$<year>')
console.log(res) // 10/30/2021
忽略分组 (?:)
就是在匹配的结果里面不要这个分组
(?:tom).(ok) // 这里的$1 就匹配的是ok
前瞻 (?=) (?!) 非捕获组
正向前瞻 (?=) 后面要有
强调的是这个位置右边的有要求,但是右边的东西不会放到结果里面去
比如 /read(?=img)/ 去匹配 "reading", 结果是 "read" ,"ing"是不会到结果里面去的。
console.log('1a2bc*456v8'.replace(/\w(?=\d)/g, '-'))
// 1-2bc*--6-8
// 表示匹配 后面是数字的单词字符
1后面是a 不匹配 // 1a2bc*456v8
a后面是1 匹配 // 1-2bc*456v8
2后面是b 不匹配 // 1-2bc*456v8
console.log(/\d+(?= dollars)/u.exec('42 dollars'))
// 匹配字符串中紧跟着dollars的数字
[
"42" // 只匹配到42
]
后面不要有xx
console.log('1a2bc*456v8'.replace(/\w(?!\d)/g, '-'))
//-a---*45-v-
// 表示匹配后面不是数字的单词字符
1后面不是数字
b后面不是数字
c后面不是数字
*后面是数字
同理
console.log(/\d+(?! dollars)/u.exec('77 pesos'))
["77"] 负向前瞻 匹配字符串中紧跟着的不是dollars的数字
eg、长度4-8个字符之间,并且必须包含一个数字
/^(?=.*\d).{4,8}$/
后顾 (?<=) (?<!)
(?<=) 前面要有xx
console.log(/(?<=\$)\d+/u.exec('333$66'))
//["66"] 匹配数字前面是$的字符串
(?<!) 前面不能有
console.log(/(?<!\$)\d+/u.exec('333$66'))
// ['333'] 匹配数字前面不能是$的字符串
贪婪模式 与 非贪婪模式 ?
正则表达式在匹配的时候默认会尽可能多的匹配, 叫做贪婪模式。通过在限定符后面加 ? 可以进行非贪婪模式匹配 。
例如 \d{3,6} 会默认匹配 6 个数字 而不是三个 在量词 {} 后面添加一个 ? 就可以修改成非贪婪模式, 进而少的匹配 3 次。
console.log('12345678'.replace(/\d{3,6}/, '-'))
// -78 一次匹配6个字符串,替换成-
console.log('12345678'.replace(/\d{3,6}?/, '-'))
// -45678 // 尽可能少的匹配 3 次 , 值替换了 123
console.log('abbbb'.replace(/ab+/, '-')) // - 全部匹配了
console.log('abbbb'.replace(/ab+?/, '-')) // -bbb 只匹配了一次
优先级
- 转义 \
- 括号 [] 、(?:) 、(?=) 、[]
- 字符和位置
- 或 |
RegExp 实例上的属性
- lastIndex 当前表达式匹配内容的最后一个字符串的下一个位置 序号从 0 开始计算
const reg = /\w/g
reg.test('a') // true
reg.test('a') // false
方法
test(str)
检测字符串参数中是否存在正则表达式匹配的字符串 , 如果用 g 修饰那么 lastIndex 会改变
exec(str)
使用正则表达式对字符串执行搜索,并将更新全局RegExp对象的属性以反映匹配结果 如果匹配失败,exec() 方法返回 null 如果匹配成功,exec() 方法返回一个数组,并更新正则表达式对象的属性
- 数组索引0:匹配的全部字符串
- 数组索引1,2..n:括号中的分组捕获
- index:属性是匹配文本的第一个字符的位置
- input:存放被检索的字符串
要注意的是:
-
exec()永远只返回一个匹配项(指匹配整个正则的)
-
如果设置了g修饰符,每次调用exec()会在字符串中继续查找新匹配项,不设置g修饰符,对一个字符串每次调用exec()永远只返回第一个匹配项。
所以如果要匹配一个字符串中的所有需要匹配的地方,那么可以设置g修饰符,然后通过循环不断调用exec方法。
const str = "Reading and Writing";
const pattern = /\b([a-zA-Z]+)ing\b/g;
let matches;
// 直到 匹配不上的时候返回null
while ((matches = pattern.exec(str))) {
console.log(matches.index + " " + matches[0] + " " + matches[1]);
}
console.log(matches)
// 0 Reading Read
// 12 Writing Writ
// null
search(reg)
search() 方法用于检索字符串中指定的子字符串,或检索与正则表达式相匹配的子字符串
方法返回第一个匹配结果的index,查找不到返回-1
search() 方法不执行全局匹配,它将忽略修饰符g,并且总是从字符串的开始进行检索
split(reg)
split() 方法一般用来分割字符串成数组,也可以传入正则表达式,使用正则可以避免一些传入字符串解决不了的问题
'143134jhg432g2g324g232g'.split(/\d/) // 用数字分割
// ["", "", "", "", "", "", "jhg", "", "", "g", "g", "", "", "g", "", "", "g"]
'a b c'.split(' ')
// ["a", "b", "", "", "", "c"] 无法识别连续的字符串
'a b c'.split(/\s*/)
// ["a", "b", "c"] // ko
match(reg)
match() 方法将检索字符串,以找到一个或多个与reg相匹配的文本,reg是否有修饰符 g影响很大 返回值与 RegExp.prototype.exec的返回类似,不过只返回匹配的字符串数组
'cdbbdbsdbdbzddzdbbbd'.match(/d(b+)d/g)
// ["dbbd", "dbd", "dbbbd"]
如果修饰符有 g则匹配出所有匹配的数组,如果不是,则出第一个匹配的字符串,以及相应的捕获内容
'cdbbdbsdbdbzddzdbbbd'.match(/d(b+)d/)
[
"dbbd",
"bb"
]
replace()
找到匹配并替换,传入string、number比较常见,这里传入回调function是比较高级的用法,这里可以参考MDN 比如一个场景,把手机号的中间4位换成*
function validateMobile(str) {
return /^[1][0-9]{10}$/.test(str) &&
str.replace(
/(\d{3})(\d{4})(\d{4})/,
(rs, $1, $2, $3) => `${$1}****${$3}`
);
}
console.log(validateMobile('15625468976'))
// 156****8976
let str = "asss23sjdssskssa7lsssdkjsssdss";
const arr = str.split(/\s*/);
// 把字符串转换为数组
const str2 = arr.sort().join("");
// 首先进行排序,这样结果会把相同的字符放在一起,然后再转换为字符串
let value = "";
let index = 0;
str2.replace(/(\w)\1*/g, function ($0, $1) {
//匹配字符
if (index < $0.length) {
index = $0.length;
// index是出现次数
value = $1;
// value是对应字符
}
});
console.log(`最多的字符: ${value} ,重复的次数: ${index}`);
let str = "asss23sjdssskssa7lsssdkjsssdss";
const arr = str.split(/\s*/);
let obj = {}
arr.forEach(item => {
if(obj[item] === undefined) {
obj[item] = 1
}else {
obj[item] = obj[item]+ 1
}
})
let values = Object.values(obj)
let keys = Object.keys(obj)
let max = Math.max(...values)
let valueIndex = values.indexOf(max)
console.log(`最多的字符: ${keys[valueIndex]} ,重复的次数: ${max}`);
案例
1、贪婪模式 与非贪婪模式 取第一个span中的文本
/.+/ // 贪婪模式
/.+?/ // 非贪婪模式
假设现在有如下的字符串,需要取字符串中第一个span的文本text1
let str = '<p><span>text1</span><span>text2</span></p>'
// 于是我们写下了 以下的正则
console.log(str.match(/<span>(.+)<\/span>/))
// 我们得到了如下的结果
[
"<span>text1</span><span>text2</span>",
"text1</span><span>text2"
]
// 却发现匹配到的竟然是 text1</span><span>text2
// 这是因为 我们括号中写的是 `(.+)` .为匹配任意字符, +则表示匹配一次以上。
// 当规则匹配到了`text1`的时候,还会继续查找下一个,发现`<`也命中了`.`这个规则
// 于是就持续的往后找,知道找到最后一个span,结束本次匹配。
当我们修改正则之后
console.log(str.match(/<span>(.+?)<\/span>/))
[
"<span>text1</span>",
"text1"
]
// 这次就能匹配到我们想要的结果了
// `?`的作为是,匹配`0~1`次规则
// 但是如果跟在`*`、`+`之类的表示数量的特殊字符后,含义就会变为匹配尽量少的字符。
// 当正则匹配到了text1后,判断后边的</span>命中了规则,就直接返回结果,不会往后继续匹配。
简单的记忆
贪婪模式:尽可能的多拿
非贪婪模式:尽可能的少拿