关于 JS 的正则表达式
正则表达式用处
寻找符合匹配规则(模式)的内容。
// 书写规则如下
/原子(原子表){长度}/模式修饰符
转义符
符号 | 意义 |
---|---|
\ | 转义符,可以让特殊的字符失去原有的特殊意义 |
\n | 转义字符,换行(LF),将当前位置移到下一行开头 |
\r | 转义字符,回车(CR),将当前位置移到本行开头 |
\t | 转义字符,水平制表(HT),(跳到下一个TAB位置) |
问题:
let price = 22.34
console.log(/\d+\.\d+/.test(price)) // true
let reg = new RegExp("\d+\.\d+")
console.log(reg.test(price)) // false
// 为什么上面的结果不一致?
console.log('\d' === 'd') // true
reg = new RegExp("\\d+\\.\\d+")
console.log(reg.test(price)) // true
原子或原子表
一个原子(原子表)表示该位置上的匹配规则,是正则表达式的最基本单位,每个模式至少要有一个原子,任何一种字符都可以作为原子,原子我们可以分为以下几类:
普通字符: 常见的字符,如大小写字母、数字等;
特殊字符或者不可打印的字符: 普通字符可以直接做为原子,但是对于特殊字符,比如:
"
、'
、\
等,这些字符作为原子的时候,需要使用转义字符 “\” 来取消他的特殊意义;原子表: 原子表是指使用
[]
将一组彼此平等的原子包在一起组成的集合,原子前面可以加^
,表示除这几个原子之外的字符,原子之间可以用-
连接,表示连接一组ASCII码顺序排列的原子;
[asdf]
表示对应位置上的字符可以是asdf
中的任何一个字符;
[asdf]
表示对应位置上的字符可以是除asdf
这四个字符以外的所有字符;
[0-9A-D]
表示对应位置上的字符可以是0,1,2,3,4,5,6,7,8,9,A,B,C,D
中的任何一个字符。
let preg = /1[^124][0-9]{9}/
let result = preg.exec('abc13699909900def')
console.log(result)
- 范围字符: 系统内置的一些范围字符,用来表示某一类字符。
原子 | 意义 |
---|---|
0-9 | 按照ascii表中的编码字符,相当于[0123456789] |
a-z | 按照ascii表中的编码字符 |
A-Z | 按照ascii表中的编码字符 |
\d | 相当于[0-9] |
\D | 表示除了0-9之外的所有字符,相当于[^0-9] |
\w | 相当于 [0-9a-zA-Z_] |
\W | 相当于 [^0-9a-zA-Z_] |
\s | 匹配空字符,包含空格、换行、制表符等 |
\S | 匹配非空字符 |
. | 除换行符和其他 unicode 行终止符之外的任意字符 |
var preq =/1[^124]\d\d\d/
var result = preq.execl('abc13699909900def')
consolelog(result)
元字符
元字符不能单独存在,它一般被用来修饰原子。元字符根据其功能可以分为以下几类:
数量限定符
限定符 含义 例子 *
匹配它前面原子0次或多次(贪婪匹配) ab*
,可以匹配a
、ab
、abbbbbb
等+
匹配它前面原子1次或多次(贪婪匹配) ?
匹配它前面原子0次或1次 {m}
匹配它前面的原子m次 {m,}
匹配它前面原子m次或多次 {m,n}
匹配它前面原子最少m次,最多n次 *?
、+?
、??
、{m,}?
、{m,n}?
使 *、+、?、{m,}、{m,n} 变成非贪婪模式,也就是使这些匹配次数不定的表达式尽可能少的匹配
var preg =/1[^124]\d{9}/
var result = preg.execl('abc13699909900def')
consolelog(result)
// 默认情况下,正则表达式是贪婪的,会尽可能的匹配更多 var preg = /1[^124]\d{5,9}/
var result =preg.exec('abc13699909900def')
console.log(result)
// 非贪婪匹配,在满足条件下,尽可能少的匹配
var preg = /1[^124]\d{5,9}?/
var result =preg.exec('abc13699909900def')
console.log(result)
边界限制符
边界限制符 含义 ^
从字符串开始位置匹配 $
匹配到字符串的尾部
var preg = /^1[^124]\d{5,9}?/
// 字符串要从开头位置开始匹配,也就是说从开头就要符合正则规则
var result =preg.exec('13699909900def')
console.log(result)
// 一直匹配到结尾,它会使非贪婪模式失效
var preg = /^1[^124]\d{5,9}$/
var result =preg.exec('13699909900')
console.log(result)
- 模式选择符:
|
用|
分割多个匹配模式,符合其中一个或多个都会被匹配到。
// | 可以连接多个正则表达式规则,表示符合任何一个规则即可
var preg = /^1[3589]\d{9}$|^16[56]\d{8}$|^17[678]\d{8}$/
var result =preg.exec('17799909900')
console.log(result)
- 分组(子模式)相关元字符: 分组是指使用
()
包裹正则表达式中的某一个片段,将这一段正则表达式当成一个组来看待,分组不会对全局表达式结果产生影响,不过却能额外或者该分组的执行结果,分组主要用于提取数据。
符号 | 含义 |
---|---|
() | 分组符(子模式符),会捕获分组的内容 |
` | ` |
\n (n 是整数) | 回溯引用,调用第n个分组的结果 |
?: 表达式 | 分组符内使用,非捕获分组,不再捕获对应分组的内容 |
var preg = /\w+@(\w+.[a-z]{2,10})/
var result = preg.exec('test@qq.com')
// 分组内容会被提取出来,一并放到结果中
console.log(result)
// 结尾是 com cn net 任意一个都行
var preg = /^\w+@(\w+.(com|cn|net))$/
var result = preg.exec('test@qq.com')
console.log(result)
// 使用 \1 \2 等可以引用之前已捕获的分组的结果
var preg = /<(\w+)>(\S*)<\/\1>/
var str = '<div>我很好</div><span>你很好</span>'
var result = preg.exec(str)
console.log(result)
// 非捕获组表示改组内容不再被捕获,也不会在结果中显示了
var preg = /<(?:[a-z]{1,4})>.+<\/(\w+)>/
var str = '<div>我很好</div><span>你很好</span>'
var result = preg.exec(str)
console.log(result)
原子表符号:
符号 含义 []
原子表的边界 放到原子表中的 ^
原子表内的元素取反操作 环视(断言) 如同
^
代表开头,$
代表结尾,\b
代表单词边界一样,断言也有类似的作用,它们只匹配某些位置,在匹配过程中,不占用字符,所以被称为“零宽”。在处理匹配的时候,也是对当前位周围的情况进行观察,所以也叫“环视”。符号 含义 (?=表达式)
正向先行断言,在某个位置向右看(写在右面),紧挨着该位置的右侧必须能匹配表达式 (?!表达式)
反向先行断言,在某个位置向右看(写在右面),紧挨着该位置的右侧必须不能匹配表达式 (?<=表达式)
正向后行断言,在某个位置向左看(写在左面),紧挨着该位置的左侧必须能匹配表达式 (?<!表达式)
反向后行断言,在某个位置向左看(写在左面),紧挨着该位置的左侧必须不能匹配表达式 js// 正向先行断言 // 匹配含有字符o的单词,但要求o的右面必须是个a,b,u 中的一个 var preg = /\w*o(?=[abu])\w*/ var str = 'hello world, I am your hero' var result = preg.exec(str) console.log(result) // your // 反向先行断言 // 匹配含有字符o的单词,但要求o的右面不能是u var preg = /\w*o(?!u)\w*/ var str = 'hello world, I am your brother' var result = preg.exec(str) console.log(result) // brother // 正向后行断言 // 匹配含有字符o的单词,但要求o的左面必须是 r var preg = /\w*(?<=r)o\w*/ var str = 'hello world, I am your hero' var result = preg.exec(str) console.log(result) // hero // 反向后行断言 // 匹配含有字符o的单词,但要求o的左面不是 l var preg = /\w*(?<!r)o\w*/ var str = 'hello world, I am your hero' var result = preg.exec(str) console.log(result) // world
关于先行(lookahead)和后行(lookbehind): 正则表达式引擎在执行字符串和表达式匹配时,会从头到尾(从前到后)连续扫描字符串中的字符,设想有一个扫描指针指向字符边界处并随匹配过程移动。先行断言,是当扫描指针位于某处时,引擎会尝试匹配指针还未扫过的字符,先于指针到达该字符,故称为先行。后行断言,引警会尝试匹配指针已扫过的字符,后于指针到达该字符,故称为后行。
问题:
//字符串中必须含有至少一个大写字母、至少一个小写字母、至少一个数字,同时字符串的长度不能低于6位
var preg = /(?=.*[A-Z])(?=.*[a-z])(?=.*[0-9]).{6,}/
var str = '1A11a132'
var result = preg.exec(str)
console.log(result)
模式修饰符
对整个匹配规则(模式)进行微调。
修饰符 | 意义 | 例子 |
---|---|---|
i | 匹配时不区分大小写 | /^[0-9a-z]+@[a-z0-9-]{1,64}\.[a-z]{2,12}$/i |
g | 全局匹配(如果使用 exec() ,需要多次调用) | /^[0-9a-z]+@[a-z0-9-]{1,64}\.[a-z]{2,12}$/ig |
m | 多行匹配,如果被匹配的字符串中有换行,则每行的开头都可以被^ 符号识别 | /^[0-9a-z]+@[a-z0-9-]{1,64}\.[a-z]{2,12}/m |
y 修饰符 (ES6)
es5 修饰符:i
(忽略大小写) m
(多行匹配) g
(全局匹配) .
只能匹配换行符以外的任意单个字符 y
叫做粘连(sticky)修饰符,文档描述
const str = 'aaa_aa_a'
const reg1 = /a+/g // 每次匹配剩余的
const reg2 = /a+/y // 每次从剩余的第一个开始匹配
console.log(reg1.exec(str))
// ['aaa', index: 0, input: 'aaa_aa_a', groups: undefined]
console.log(reg1.exec(str))
// ['aa', index: 4, input: 'aaa_aa_a', groups: undefined]
console.log(reg1.exec(str))
// ['a', index: 7, input: 'aaa_aa_a', groups: undefined]
console.log(reg2.exec(str))
// ['aaa', index: 0, input: 'aaa_aa_a', groups: undefined]
console.log(reg2.exec(str))
// null 为什么?每次从剩余的第一个开始匹配,_aa_a 不满足 reg2
console.log(reg2.exec(str))
// ['aaa', index: 0, input: 'aaa_aa_a', groups: undefined] , 上一次结果为 null ,所以从头开始匹配
u 修饰符 (ES6)
const regex = new RegExp('\u{61}', 'u');
console.log(regex.unicode); // true
const str = '\uD842\uDFB7' // '𠮷' 表示4个字节的 UTF16 的编码,表示一个字符,超出 unicode 范围
console.log(/^\uD842/.test(str)) // true , es5 用法,其实是不对的,因为上面表示一个字符(这是一个整体,不能拆开),这个正则处理当做两个字符来处理了
console.log(/^\uD842/u.test(str)) // false
当码点超出 Unicode 范围, 使用 u 修饰符就可以识别了。.
匹配符就不能识别了(在 Unicode 范围内,只能匹配换行符以外的任意单个字符),
const str = '\uD842\uDFB7'
console.log(/^.$/.test(str)) // false
console.log(/^.$/u.test(str)) // true
console.log(/\u{61}/.test('a')) // false
console.log(/\u{61}/u.test('a')) // true
console.log(/𠮷{2}/.test('𠮷𠮷')) // false ,当前 𠮷 码点超出 unicode 范围了
console.log(/𠮷{2}/u.test('𠮷𠮷')) // false
dotAll (ES9/ES2018)
.
只能匹配换行符以外的任意单个字符(这些字符属于 Unicode 范围内) s
修饰符可以开启 dotAll 模式
console.log(/./.test('\n')) // false
console.log(/./.test('\r')) // false
console.log(/./.test('\u{2028}')) // false 行分隔符
console.log(/./.test('\u{2029}')) // false 段分隔符
console.log(/./.test('\uD842\uDFB7')) // true
console.log(/./.test('𠮷')) // true
// 使用 dotAll
console.log(/./s.test('\n')) // true
console.log(/./s.test('\r')) // true
console.log(/./s.test('\u{2028}')) // true
console.log(/./s.test('\u{2029}')) // true
具名组匹配 (ES9/ES2018)
/(\d{4})-(\d{2})-(\d{2})/.exec('2021-08-12')
// ['2021-08-12', '2021', '08', '12', index: 0, input: '2021-08-12', groups: undefined]
const regExp = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/
console.log(regExp.exec('2021-08-12'))
// groups: {year: '2021', month: '08', day: '12'}
let groups = regExp.exec('2021-08-12').groups
const { year, month, day } = groups ?? {}
后行断言 (ES9/ES2018)
对应的还有先行断言
// 先行断言 es5 语法 ?=
const str = 'ecmascript'
console.log(str.match(/ecma(?=script)/))
// ['ecma', index: 0, input: 'ecmascript', groups: undefined]
// 上面的意思:想匹配后面跟着 script 的 'ecma' 字符串
const str1 = 'ecmaxxscript'
console.log(str1.match(/ecma(?=script)/)) // null
// 后行断言 ?<= ?<!
console.log(str.match(/(?<=ecma)script/))
// ['script', index: 4, input: 'ecmascript', groups: undefined]
const str2 = 'ecmajjscript'
console.log(str.match(/(?<=ecma)script/)) // null
console.log(str.match(/(?<!ecma)script/))
// ['script', index: 6, input: 'ecmajjscript', groups: undefined]