共计 17905 个字符,预计需要花费 45 分钟才能阅读完成。
正则基础
常见简写形式
字符组 | 具体含义 | 记忆方式 |
---|---|---|
d | 表示 [0-9]。表示是一位数字。 | 其英文是 digit(数字) |
D | 表示 1。表示除数字外的任意字符。 | |
w | 表示 [0-9a-zA-Z_]。表示数字、大小写字母和下划线。 | w 是 word 的简写,也称单词字符。 |
W | 表示 2。非单词字符。 | |
s | 表示 [tvnrf]。表示空白符,包括空格、水平制表符、垂直制表符、换行符、回车符、换页 符。 | s 是 space 的首字母,空白符的单词是 white space。 |
S | 表示 3。非空白符。 | |
. | 表示 4。通配符,表示几乎任意字符。换行符、回车符、行分隔符和段分隔符 除外。 | 想想省略号 … 中的每个点,都可以理解成占位符,表示任何类似的东西。 |
[dD]/[wW]/[sS]/[^] | 任意字符 |
量词
字符组 | 具体含义 | 记忆方式 |
---|---|---|
{m,} | 至少出现 m 次 | |
{m} | 等价于{m, m}, 出现 m 次 | |
? | 等价于{0, 1}, 出现或者不出现 | 问号的意思: 有么? |
+ | 等价于{1,}, 表示至少出现一次 | 加号是追加的意思,表示必须现有一个,然后才考虑追加 |
* | 等价于{0,}, 出现任意次, 有可能不出现 | 看看天上的星星,可能一颗没有,可能零散有几颗,可能数也数不过来 |
修饰符
字符组 | 具体含义 | 记忆方式 |
---|---|---|
g | 匹配全局 | 单词是 global |
i | 忽略字母大小写 | 单词是 ignoreCase |
m | 多行匹配, 只影响 ^ 和 $,二者变成行的概念,即行开头和行结尾 | 单词是 multiline |
匹配位置
字符组 | 具体含义 | 记忆方式 |
---|---|---|
^ | (脱字符)匹配开头 | |
$ | (美元符号)匹配结尾 | |
b | 单词边界 | w 与 W 之间的位置,也包括 w 与 ^ 之间的位置,和 w 与 $ 之间的位置 |
B | 非单词边界 | |
(?=p) | “p” 的子模式, “p” 前面的位置 | positive lookahead(正向先行断言) |
(?!p) | 非 ”p” 前的位置 | negative lookahead(负向先行断言) |
匹配开头与结尾
-
/^|$/g
: 匹配列 -
/^|$/gm
: 匹配行,m
是既有修饰符
ES5 之后版本支持的位置判断
-
(?<=p)
: positive lookbehind(正向后行断言) -
(?<!p)
: negative lookbehind(负向后行断言)
举例
-
千分位
-
千分位示例
1234567890
-
三位数字的前面:
/(?=\d{3}$)/g
-
(?=p)
: 正向先行断言
-
-
多个三位数字:
/(?=(\d{3})+$)/g
-
+
: 量词, 多个
-
-
最前面不匹配:
/(?!^)(?=(\d{3})+$)/g
-
(?!^)
: 负向先行断言
-
-
非捕获:
-
正则:
/\B(?=(?:\d{3})+(?!\d))/g
- 千位分隔符的理解
-
-
-
带空格的千分位
123456789 123456789
-
/(?!\b)(?=(\d{3})+\b)/g
,即/(\B)(?=(\d{3})+\b)/g
-
^
和$
要替换成\b
-
-
格式化千分位
function format (num) {return num.toFixed(2).replace(/\B(?=(\d{3})+\b)/g, ",").replace(/^/, "$$"); }; console.log(format(1888) ); // => "$ 1,888.00"
-
-
验证密码: 长度为 6~12 位, 由数字、大写字母和小写字母,必须至少包含两种字符
- 长度为 6~12 位的数字、大小写字母:
/^[0-9a-zA-Z]{6, 12}$/
-
至少包含两种
-
解题核心
- 至少包含一位数字字符:
/(?=.*[0-9])/
- 至少包含一位数字字符:
-
划分情况
- 包含数字和大写字母
- 包含数字和小写字母
- 包含大写字母和小写字母
- 包含数字、大写字母和小写字母
-
正则:
/((?=.*[0-9])(?=.*[a-z])|(?=.*[0-9])(?=.*[A-Z])|(?=.*[a-z])(?=.*[A-Z]))^[0-9a-zA-Z]{6,12}$/
- 注意:
{6,12}
中间不能有空格
- 注意:
-
-
不能同时全部为一种(反向思路)
-
解题核心
- 不能全部为一种字符(数字):
/(?!^[0-9]{6,12}$)(^[0-9a-zA-Z]{6,12})/
- 不能全部为一种字符(数字):
-
划分情况
- 不能全为数字
- 不能全为大写字母
- 不能全为小写字母
- 正则:
/(?!^[0-9]{6,12}$)(?!^[a-z]{6,12}$)(?!^[A-Z]{6,12}$)(^[0-9a-zA-Z]{6,12}$)/
-
- 长度为 6~12 位的数字、大小写字母:
常用正则
词法 | 含义 | 记忆方式 |
---|---|---|
(?=.[A-Z]) / (?=.?[A-Z]) / (.*[A-Z]) | 至少包含一个大写字母 |
括号的作用
分组和分支
- 分组:
'ababa abbb ababab'.match(/(ab)+/g)
-
分支:
(p1|p2)
-
示例
var regex = /^I love (JavaScript|Regular Expression)$/ console.log(regex.test("I love JavaScript")) // true console.log(regex.test("I love Regular Expression")) // true
-
分组引用: 正则引擎给分组开辟了一个空间, 用来存储每个分组匹配到的数据
-
分组方法
- 之前:
/^d{4}-d{2}-d{2}$/
- 之后:
/^(d{4})-(d{2})-(d{2})$/
- 之前:
-
提取数据:
-
match 方法
- 使用:
'2017-06-12'.match(/(\d{4})-(\d{2})-(\d{2})/)
-
match 返回数组:
["2017-06-12", "2017", "06", "12", index: 0, input: "2017-06-12", groups: undefined]
- 第一个是整体匹配结果
- 然后是各个分组匹配到的结果
- 匹配下标
- 输入的文本
- 一个捕获组数组 或 undefined(如果没有定义命名捕获组)
-
注意:
-
使用
g
之后, 返回的是一个数组- 举例:
'1s1'.match(/\d/g) => ["1", "1"]
- 举例:
- 匹配不到, 则返回
null
-
- 使用:
-
exec 方法
- 使用:
/(\d{4})-(\d{2})-(\d{2})/.exec('2017-06-12')
- 使用:
- 正常操作 (
test
/match
/exec
) 之后, 可以通过RegExp
的$0
~$9
获取
-
-
替换: 转换
yyyy-mm-dd
为dd/mm/yyyy
-
$
方法'2019-09-23'.replace(/(\d{4})-(\d{2})-(\d{2})/, '$2/$3/$1')
-
RegExp.$
+function
方法'2019-09-23'.replace(/(\d{4})-(\d{2})-(\d{2})/, function() {return `${RegExp.$2}/${RegExp.$3}/${RegExp.$1}` })
-
function
方法'2019-09-23'.replace(/(\d{4})-(\d{2})-(\d{2})/, function(match, year, month, day) {return `${month}/${day}/${year}` })
-
反向引用: \1
-
同时匹配日期格式:
2019-09-24
,2019/09/24
,2019.09.24
- 正则:
/\d{4}(-|\/|\.)\d{2}(-|\/|\.)\d{2}/
- 问题: 格式不统一 ,
2019/09-24
同样能匹配 -
解决方法: 使用反向引用
- 正则:
/\d{4}(-|\/|\.)\d{2}(\1)\d{2}/
- 正则:
- 同理:
\2
是第二个分组引用, 依次类推
- 正则:
-
括号嵌套
-
正则
// ["1231231233", "123", "1", "23", "3", index: 0, input: "1231231233", groups: undefined] 1231231233'.match(/^((\d)(\d(\d)))\1\2\3\4$/)
-
拆分
/^((\d)(\d(\d)))$/.test('123') // true
/^(\d)$/.test('1') // true
/^(\d(\d))$/.test('23') // true
/^(\d)$/.test('3') // true, 同第二个一样
-
-
引用不存在的分组
-
会匹配反向引用本身
- 正则:
/\1/.test('\1') // true
- 正则:
-
-
分组后由量词
-
会匹配到最后一次匹配
- 正则:
'12345'.match(/(\d)+/) // ["12345", "5", index: 0, input: "12345", groups: undefined]
- 对于反向引用, 同理:
/(\d)+ \1/.test('12345 5') // true
- 正则:
-
非捕获括号: (?:p)
非捕获匹配到的值不会保存起来
-
与之相反的是
- 捕获型分组
- 捕获型分支
-
举例
'ababa abbb ababab'.match(/(?:ab)+/g) // ["abab", "ab", "ababab"] var regex = /^I love (?:JavaScript|Regular Expression)$/ regex.test("I love JavaScript") // true regex.test("I love Regular Expression") // true
实战应用
trim: 去掉头部和尾部的空字符串
- js:
'hello'.trim() // 'hello'
-
正则:
-
匹配到开头和结尾的空字符串, 替换掉 ( 效率高)
'hello'.replace(/^\s+|\s+$/g, '')
-
惰性匹配
*?
, 匹配所有字符串, 提取相应数据'hello'.replace(/^\s*(.*?)\s*$/, '$1') // 'hello'
-
将每个单词的首字母转换为大写
-
方法
function titleize (str) {return str.toLowerCase().replace(/(?:^|\s)\w/g, function (c) {return c.toUpperCase(); }); } // My Name Is Epeli console.log(titleize('my name is epeli') )
-
注意
-
\s
: 制表符空格等, 用来匹配name
这样的数据
-
驼峰
-
方法
var camelize = function(str) {return str.replace(/[-_\s]+(.)?/g, function(match, c){return c ? c.toUpperCase() : '' }) } console.log(camelize('-moz-transform'))
-
注意
-
replace: 关于 replace 参数
-
match
: 匹配到的字符串 -
p1,p2,...
: 代表第 n 个括号匹配到的字符串 -
offset
: 匹配到字符串在原字符串的偏移量 -
string
: 被匹配的原字符串 -
NamedCaptureGroup
: 命名捕获组匹配的对象
-
-
[-_\s]
: 连字符、下划线和空白符 -
?
: 应对单词结尾不是单词字符, 例如'-moz-transform'
-
中划线化
-
方法
var dasherize = function(str) {return str.replace(/([A-Z])/g, '-$1').replace(/[-_\s]+/g, '-').toLowerCase()} console.log(dasherize('MozTTransform'))
-
或者使用
function
var sperateLine = function(str) {return str.replace(/[A-Z]{1}/g, function(match) {return match ? `-${match.toLowerCase()}` : '' }) } console.log(sperateLine('MozTTransform'))
HTML 转义和反转义
-
转义
-
方法
var escapeHTML = function(str) { var escapeChars = { '<': 'lt', '>': 'gt', '"':'quot','&':'amp','\'':'#39' } return str.replace(new RegExp(`[${Object.keys(escapeChars).join('')}]`,'g'), function(match) {return `&${escapeChars[match]};` }) } console.log(escapeHTML('<div>hello, \'world\'</div>'))
-
-
反转义
-
方法
var unescapeHTML = function(str) { var htmlEntities = { 'lt': '<', 'gt': '>', 'quot': '"','amp':'&','#39':'\'' } return str.replace(new RegExp(`\&([^;]+);`, 'g'), function(match, key) {console.log(match, key) return (key in htmlEntities) ? htmlEntities[key] : match }) } console.log(unescapeHTML('<div>hello, 'world'</div>'))
-
注意
-
关于
/\&([^;]+);/g
中^
- 匹配输入字符串开头位置
- 方括号中使用, 表示不接受该字符集合
- 匹配
^
本身,使用\^
- 图示:
-
-
-
成对标签
- 简单匹配正则:
/<([^>]+)>[\d\D]*<\/\1>/
- 验证:
/<([^>]+)>[\d\D]*<\/\1>/.test('<title>wrong!</p>') // false
- 简单匹配正则:
回溯法
-
没有回溯的匹配
- 举例:
/ab{1,3}c/.test('abbbc')
- 举例:
-
有回溯的匹配
- 举例:
/ab{1,3}c/.test('abbc')
- 举例:
- 回溯产生的原因: 一次没有恰好匹配, 需要多次匹配
-
建议: 应该避免
-
举例
// .* 任意字符出现任意次, 会匹配到 abc"de, 匹配完之后发现还有" 正则, 然后会回溯,只匹配到 abc /".*"/.test('"abc"de') // 更改建议, 匹配非 "的字符任意次, 碰到" 就终止匹配, 减少回溯, 提高效率 /"[^"]*"/.test('"abc"de')
-
-
释义
-
百度百科
回溯法也称试探法,它的基本思想是: 从问题的某一种状态 (初始状态) 出发,搜索从这种状态出发 所能达到的所有“状态”,当一条路走到“尽头”的时候 (不能再前进),再后退一步或若干步,从 另一种可能“状态”出发,继续搜索,直到所有的“路径”(状态) 都试探过。这种不断“前进”、不断“回溯”寻找解的方法,就称作“回溯法”。
- 本质: 深度优先搜索算法
- 回溯: 退到之前的某一步的过程
-
-
常见的回溯形式
-
贪婪量词
- 尝试顺序从多往少的方向去尝试
-
举例
// ["12345", "123", "45", index: 0, input: "12345", groups: undefined] '12345'.match(/(\d{1,3})(\d{1,3})/)
-
惰性量词
- 贪婪量词后加问号尽可能少的匹配
-
举例
// ["1234", "1", "234", index: 0, input: "12345", groups: undefined] '12345'.match(/(\d{1,3}?)(\d{1,3})/)
-
会回溯的惰性
// ["12345", index: 0, input: "12345", groups: undefined] '12345'.match(/^\d{1,3}?\d{1,3}$/)
-
分支结构
-
分支也是惰性匹配
// ["can", index: 0, input: "candy", groups: undefined] 'candy'.match(/can|candy/)
-
整体匹配的话,也会回溯
// ["candy", index: 0, input: "candy", groups: undefined] 'candy'.match(/^(?:can|candy)$/)
-
-
总结
- 回溯相对 DFA 引擎, 匹配效率低一些
-
概念
- DFA: 确定型有限自动机
-
NFA: 非确定型有限自动机
- JavaScript 就是, 而且流行, 匹配慢(相对 DFA)
- 流行原因: 编译快, 有趣?
-
拆分
-
结构的具体含义
结构 说明 举例 字面量 匹配一个具体字符,包含需要转义和不需要转义的 不转义: /a/
匹配字符'a'
; 转义:/\n/
匹配换行符,\.
匹配小数点字符组 匹配一个字符,可以是多种可能之一;反义字符组,表示除特定字符的其他字符 /[0-9]/
表示匹配一个数字, 简写形式/\d/
;[^0-9]
表示匹配一个非数字, 简写形式/\D/
量词 表示一个字符连续出现; 或常见的简写形式 /a{1,3}/
表示a
字符连续出现 1~3 次;/a+/
表示a
出现至少一次锚 匹配一个位置, 而不是字符 ^
表示匹配位置的开头,\b
表示匹配单词边界,(?=\d)
表示数字的前面分组 用括号表示一个整体 (ab)+
表示ab
两个字符连续出现多次, 也可以使用非捕获组(?:ab)+
分支 多个子表达式多选一, 反向引用 `abc bcd 表示匹配
‘abc’或者
‘bcd’字符子串;
2` 表示引用第 2 个分组 -
操作符(从上至下)
操作符描述 操作符 优先级 转义符 \
1 括号和方括号 (...)
、(?:...)
、(?=...)
、(?!...)
、[...]
2 量词限定符 {m}
、{m,n}
、{m,}
、?
、*
、+
3 位置和序列 ^
、$
、\ 元字符
、一般字符
4 管道符(竖杠) ` ` 5 - 举例:
/ab?(c|de*)+|fg/
- 举例:
-
注意
-
匹配字符串整体问题
- 错误示例:
/^abc|bcd$/
- 错误示例:
-
![QQ20191010-145021.png](https://image-static.segmentfault.com/216/764/2167645415-5da548b931259_articlex)
- 正确示例: `/^(abc|bcd)$/`
![QQ20191010-145048.png](https://image-static.segmentfault.com/757/272/757272158-5da548b9cd455_articlex)
- 原因: 位置操作符优先级高于管道操作符
- 量词连缀问题
- 每个字符为 `'a'`,`'b'`,`'c'` 任选其一, 字符串长度是 3 的倍数
- 错误示例: `/^[abc]{3}+$/`
> 会报错, 说 `+` 前没有什么可重复的
- 正确示例: `/([abc]{3})+/`
![QQ20191010-150526.png](https://image-static.segmentfault.com/207/251/2072516231-5da548ba4ba73_articlex)
- 元字符转义问题
- 元字符
```
/\^\$\.\*\+\?\|\\\/\[\]\{\}\=\!\:\-\,/.test('^$.*+?|\\/[]{}=!:-,')
```
- 字符串中, 每个字符转义之后还是本身
```
// true
'^$.*+?|\\/[]{}=!:-,' === '\^\$\.\*\+\?\|\\\/\[\]\{\}\=\!\:\-\,'
```
- 不是每个字符都需要转义
- 跟字符组有关的 `[`、`]`、`^`、`-`。因此在会引起歧义的地方进行转义, 例如 `^`, 否则会把整个字符组看成反义字符组
```
'^$.*+?|\\/[]{}=!:-,'.match(/[\^$.*+?|\\/\[\]{}=!:\-,]/g)
```
- 匹配 `'[abc]'` 和 `{3,5}`
```
/\[abc]/.test('[abc]')
/\{3,5}/.test('{3,5}')
/{,3}/.test('{,3}')
```
- 原因: * 只需要在第一个方括号转义即可,因为后面的方括号构不成字符组,正则不会引发歧义,自然不需要转义 *
- 其他
- `=`, `!`, `:`, `-`, `,` 不在特殊结构, 不需要转义
- 括号前后需要转义, `/\(123\)/`
- `^`, `$`, `.`, `*`, `+`, `?`, `|`, `\`, `/` 等字符, 只要不在字符组内, 都需要转义
- 案例分析
- 正则: `/^(\d{15}|\d{17}[\dxX])$/`
- IPV4 正则: `/^((0{0,2}\d|0?\d{2}|1\d{2}|2[0-4]\d|25[0-5])\.){3}(0{0,2}\d|0?\d{2}|1\d{2}|2[0-4]\d|25[0-5])$/`
- 结构: `((...)\.){3}(...)`
- 结构匹配: `3 位数.3 位数.3 位数.3 位数 `
- 拆分
- `0{0,2}\d`: 匹配
```
0~9
// 或
00~09
// 或
000~009
```
- `0?\d{2}`: 匹配
```
10~99
// 或
010~099
```
- `1\d{2}`: 匹配 `100`~`199`
- `2[0-4]\d`: 匹配 `200`~`249`
- `25[0-5]`: 匹配 `250`~`255`
正则表达式构建
-
平衡法则
- 匹配预期的字符串
- 不匹配非预期的字符串
- 可读性和可维护性
- 效率
-
构建正则前提
-
是否能使用正则
- 举例:
'1010010001...'
- 举例:
-
是否有必要使用 (复杂) 正则
-
字符串分隔举例
var string = '2017-07-01' // 正则 var reg = /^(\d{4})-(\d{2})-(\d{2})/ console.log(string.match(reg)) // js api var stringArray = string.split('-') console.log(stringArray)
-
判断是否有问号
var string = '?id=xx&act=search' // 正则 console.log(string.search(/\?/)) // js api console.log(string.indexOf('?'))
-
获取子串
var string = 'JavaScript' // 正则 var reg = /.{4}(.+)/ console.log(string.match(reg)[1]) // js api console.log(string.substring(4))
-
是否有必要构建一个复杂的正则
- 密码问题: 长度 6~12 位, 由数字、小写和大写字符组成, 必须包含两种字符
- 复杂正则:
/(?!^[0-9]{6,12}$)(?!^[a-z]{6,12}$)(?!^[A-Z]{6,12}$)^[0-9A-Za-z]{6,12}$/
-
分拆简单正则:
var regex1 = /^[0-9a-zA-Z]{6,12}$/ var regex2 = /^[0-9]$/ var regex3 = /^[a-z]$/ var regex4 = /^[A-Z]$/ function checkPassword = function(string) {if (!regex1.test(string) return false if (regex2.test(string)) return false if (regex3.test(string)) return false if (regex4.test(string)) return false return true }
-
-
-
准确性
-
问题 1:
- 描述:匹配如下格式固话
055188888888
、0551-88888888
、(0551)88888888
-
分析(不考虑分机号和
+86
)- 区号, 以 0 开头的 3~4 位数字, 正则:
/0\d{2,3}/
- 号码, 非 0 开头的 6~7 位数字, 正则:
/[1-9]\d{6,7}/
- 匹配
055188888888
, 正则:/^0\d{2,3}[1-9]\d{6,7}$/
- 匹配
0551-88888888
, 正则:/^0\d{2,3}-[1-9]\d{6,7}$/
- 匹配
(0551)88888888
, 正则:/^\(0\d{2,3}\)[1-9]\d{6,7}$/
- 合并正则:
^/0\d{2,3}[1-9]\d{6,7}|0\d{2,3}-[1-9]\d{6,7}|\(0\d{2,3}\)[1-9]\d{6,7}$/
- 提取公共正则:
/^(0\d{2,3}|0\d{2,3}-|\(0\d{2,3}\))[1-9]\d{6,7}$/
- 进一步简写:
/^(0\d{2,3}-?|\(0\d{2,3}\))[1-9]\d{6,7}$/
- 区号, 以 0 开头的 3~4 位数字, 正则:
- 描述:匹配如下格式固话
-
![QQ20191011-121606.png](https://image-static.segmentfault.com/365/885/3658857746-5da548bae93e5_articlex)
- 测试
```
/^(0\d{2,3}-?|\(0\d{2,3}\))[1-9]\d{6,7}$/.test('055188888888')
/^(0\d{2,3}-?|\(0\d{2,3}\))[1-9]\d{6,7}$/.test('0551-88888888')
/^(0\d{2,3}-?|\(0\d{2,3}\))[1-9]\d{6,7}$/.test('(0551)88888888')
/^(0\d{2,3}-?|\(0\d{2,3}\))[1-9]\d{6,7}$/.test('051-8888888')
```
- 问题
- 3 位和 4 位数字不一定是真实区号
- 问题 2:
- 描述: 匹配如下格式浮点数 `1.23`、`+1.23`、`-1.23`、`10`、`+10`、`-10`、`.2`、`+.2`、`-.2`
- 分析
- 符号部分: `[+-]`
- 整数部分: `\d+`
- 小数部分: `\.\d+`
- 匹配 `1.23`、`+1.23`、`-1.23` 正则: `/^[+-]?\d+\.\d+$/`
- 匹配 `10`、`+10`、`-10` 正则: `/^[+-]?\d+$/`
- 匹配 `.2`、`+.2`、`-.2` 正则: `/^[+-]?\.\d+$/`
- 合并正则: `/^[+-]?(\d+\.\d+|\d+|\.\d+)$/`
![float_1.png](https://image-static.segmentfault.com/386/026/3860260627-5da548bb9d7f5_articlex)
- 另外一种写法: `/^[+-]?(\d+)?(\.)?\d+$/`
> 涉及到可维护性和可读性
-
效率
-
正则运行阶段
-
编译
引擎报错与否在这个阶段
- 设定起始位置
-
尝试匹配
可以优化的阶段
-
匹配失败,从下一位开始继续第 3 步
可以优化的阶段
- 最终结果: 匹配成功或失败
-
-
运行代码示例
var regex = /\d+/g; // 0 ["123", index: 0, input: "123abc34def", groups: undefined] console.log(regex.lastIndex, regex.exec("123abc34def") ); // 3 ["34", index: 6, input: "123abc34def", groups: undefined] console.log(regex.lastIndex, regex.exec("123abc34def") ); // 8 null console.log(regex.lastIndex, regex.exec("123abc34def") ); // 0 ["123", index: 0, input: "123abc34def", groups: undefined] console.log(regex.lastIndex, regex.exec("123abc34def") );
当使用
test
和exec
时, 正则有g
时, 起始位置是从lastIndex
开始的 -
优化方法
-
使用具体型字符组代替通配符, 来消除回溯
-
/".*"/.test('123"abc"456')
: 回溯有 4 次 -
/".*?"/.test('123"abc"456')
: 回溯有 2 次- 惰性匹配
*?
- 惰性匹配
-
/"[^"]*"/.test('123"abc"456')
: 无回溯
-
-
使用非捕获分组
不需要使用分组引用和反向引用时
-
/^[+-]?(\d+\.\d+|\d+|\.\d+)$/
=>/^[+-]?(?:\d+\.\d+|\d+|\.\d+)$/
-
-
独立出确定字符
-
/a+/
=>/aa*/
-
-
提取公共分支部分
可减少匹配过程中的重复
-
/^abc|^bcd/
=>/^(abc|bcd)/
-
/this|that/
=>/th(?:is|at)/
-
-
减少分支的数量, 缩小它们范围
-
/red|read/
=>/rea?d/
: 可读性降低
-
-
-
总结
- 关于准确性: 满足需求即可
- 关于效率: 看个人要求
-
准确性思路
- 针对每种情形, 分别写出正则
- 再用分支进行合并
- 提取公共部分
-
正则表达式编程
- 匹配: 目标字符串中是否有匹配的子串
-
正则表达式的四种操作
-
验证: 判断是否的操作
-
search
// true !!~'abc123'.search(/\d/)
-
match
// true !!'abc123'.match(/\d/)
-
test
// true /\d/.test('abc123')
-
exec
// true !!/\d/.exec('abc123')
-
-
切分:
-
切分
,
分隔的字符串// ["html", "js", "css"] 'html,js,css'.split(/,/)
-
日期切割
// ["2019", "10", "11"] '2019/10/11'.split(/\D/) '2019.10.11'.split(/\D/) '2019-10-11'.split(/\D/)
-
-
提取:
-
search
'2019-10-11'.search(/^(\d{4})\D(\d{2})\D(\d{2})$/) // 2019 10 11 console.log(RegExp.$1, RegExp.$2, RegExp.$3)
-
match
// ["2019-10-11", "2019", "10", "11", index: 0, input: "2019-10-11", groups: undefined] '2019-10-11'.match(/^(\d{4})\D(\d{2})\D(\d{2})$/)
-
replace
var date = [] '2019-10-11'.replace(/^(\d{4})\D(\d{2})\D(\d{2})$/, function(year, month, day) {date.push(year, month, day) }) // ["2019-10-11", "2019", "10"] console.log(date)
-
test
/^(\d{4})\D(\d{2})\D(\d{2})$/.test('2019-10-11') // 2019 10 11 console.log(RegExp.$1, RegExp.$2, RegExp.$3)
-
exec
/^(\d{4})\D(\d{2})\D(\d{2})$/.exec('2019-10-11') // 2019 10 11 console.log(RegExp.$1, RegExp.$2, RegExp.$3)
-
-
替换: 需要重点掌握
var tody = new Date('2019-10-11'.replace(/-/g, '/')) // Fri Oct 11 2019 00:00:00 GMT+0800 (China Standard Time) console.log(tody)
-
-
相关 API 注意要点
-
search
和match
会把字符串转成正则// 0 '2019.10.11'.search('.') // ["2", index: 0, input: "2019.10.11", groups: undefined] '2019.10.11'.match('.') // 4 '2019.10.11'.search('\\.') // [".", index: 4, input: "2019.10.11", groups: undefined] '2019.10.11'.match('\\.') // 4 '2019.10.11'.search(/\./) // [".", index: 4, input: "2019.10.11", groups: undefined] '2019.10.11'.match(/\./) // "2019/10.11" '2019.10.11'.replace('.', '/')
-
match
返回的格式问题: 与是否有修饰符g
有关// ["2019", "2019", index: 0, input: "2019.10.11", groups: undefined] console.log('2019.10.11'.match(/\b(\d+)\b/)) // ["2019", "10", "11"] console.log('2019.10.11'.match(/\b(\d+)\b/g))
-
exec
比match
更强大:match
使用g
之后, 没有关键信息index
,exec
可以解决这个问题, 并且接着上一次继续匹配var string = "2019.10.11"; var regex2 = /\b(\d+)\b/g; // ["2019", "2019", index: 0, input: "2019.10.11", groups: undefined] console.log(regex2.exec(string) ); // 4 console.log(regex2.lastIndex); // ["10", "10", index: 5, input: "2019.10.11", groups: undefined] console.log(regex2.exec(string) ); // 7 console.log(regex2.lastIndex); // ["11", "11", index: 8, input: "2019.10.11", groups: undefined] console.log(regex2.exec(string) ); // 10 console.log(regex2.lastIndex); // null console.log(regex2.exec(string) ); // 0 console.log(regex2.lastIndex);
-
对使用
exec
用法优化示例中
lastIndex
表示下次匹配的开始位置var string = '2019.10.11' var regex = /\b(\d+)\b/g var result while (result = regex.exec(string)) {console.log(result, regex.lastIndex) }
-
-
修饰符
g
对exec
和test
的影响-
字符串的四个方法, 每次匹配都是从 0 开始, 即
lastIndex
属性始终不变var regex = /a/g 'a'.search(regex) // 0 console.log(regex.lastIndex) 'ab'.search(regex) // 0 console.log(regex.lastIndex)
-
正则的
exec
和test
方法, 当正则含有g
, 每次匹配都会更改lastIndex
; 不含g
, 则不会改变lastIndex
var regex = /a/g // true 1 console.log(regex.test('a'), regex.lastIndex ) // true 3 console.log(regex.test('abac'), regex.lastIndex ) // false 0 console.log(regex.test('abacd'), regex.lastIndex )
-
-
test
整体匹配时需要^
和$
test
是看目标字符串中是否有子串符合条件// true /123/.test('a123b') // false /^123$/.test('a123b') // true /^123$/.test('123')
-
-
split
相关事项-
有 2 个参数, 第 2 个表示数组最大长度
// ["js", "css"] 'js,css,html'.split(/,/, 2)
-
使用分组, 则结果包含分隔符本身
// ["js", ",", "css", ",", "html"] 'js,css,html'.split(/(,)/)
-
-
replace
很强大-
第二个参数是字符串时, 有如下含义
属性 描述 $1,$2,…,$99 匹配第 1~99 个分组里捕获到的文本 $& 匹配到的子串文本 $` 匹配到的子串左边的文本 $’ 匹配到的子串右边的文本 $$ 美元符号 -
把
2,3,5
变成5=2+3
// "5=2+3" '2,3,5'.replace(/(\d+),(\d+),(\d+)/, '$3=$1+$2')
-
把
2,3,5
变成222,333,555
'2,3,5'.replace(/(\d+)/g, '$&$&$&')
-
把
2+3=5
变成2+3=2+3=5=5
// "2+3=2+3=5=5" '2+3=5'.replace(/(=)/, "$&$`$&$'$&")
-
-
第二个参数是函数时
replace
此时拿到的信息比exec
多// ["1234", "1", "4", 0, "1234 2345 3456"] // ["2345", "2", "5", 5, "1234 2345 3456"] // ["3456", "3", "6", 10, "1234 2345 3456"] "1234 2345 3456".replace(/(\d)\d{2}(\d)/g, function (match, $1, $2, index, input) { // $1 是每组数字的开始 // $2 是每组数字的结束 console.log([match, $1, $2, index, input]); })
-
-
使用构造函数需要注意的问题
不推荐使用, 会写很多的
\
// ["2017-06-27", "2017.06.27", "2017/06/27"] '2017-06-27 2017.06.27 2017/06/27'.match(/\d{4}(-|\.|\/)\d{2}\1\d{2}/g) // ["2017-06-27", "2017.06.27", "2017/06/27"] '2017-06-27 2017.06.27 2017/06/27'.match(new RegExp('\\d{4}(-|\\.|\\/)\\d{2}\\1\\d{2}', 'g'))
-
修饰符
修饰符 描述 单词 /g/ 全局匹配(找到所有的) global /i/ 忽略字母大小写 ignoreCase /m/ 多行匹配, 只影响 ^
和$
, 二者变成行概念(行开头、行结尾)multiline -
只读属性
var regex = /\w/img // true console.log(regex.global) // true console.log(regex.ignoreCase) // true console.log(regex.multiline)
-
-
source
属性对象属性, 除了
global
,ignoreCase
,multiline
,lastIndex
还有source
属性; 用来构建动态正则, 或确认真正的正则var className = "high"; // => (^|\s)high(\s|$) // => 即字符串 "(^|\\s)high(\\s|$)" var regex = new RegExp("(^|\\s)" + className + "(\\s|$)"); console.log(regex.source) console.log(regex.test('high'), regex.test('high'))
-
构造函数属性
静态属性随着最后一次正则操作而变化, 除了
$1,...$9
, 还有几个不太常用的(有兼容问题)静态属性 描述 简写形式 RegExp.input
最近一次目标字符串 RegExp[‘$_’] RegExp.lastMatch
最近一次匹配的文本 RegExp[‘$&’] RegExp.lastParen
最近一次捕获的文本 RegExp[‘$+’] RegExp.leftContext
目标匹配 lastMatch 之前的文本 RegExp[‘$`’] RegExp.rightContext
目标匹配 lastMatch 之后的文本 RegExp[“$'”] var regex = /([abc])(\d)/g var string = 'a1b2c3d4e5' string.match(regex) // a1b2c3d4e5 a1b2c3d4e5 console.log(RegExp.input, RegExp['$_']) // c3 c3 console.log(RegExp.lastMatch, RegExp['$&']) // 3 3 console.log(RegExp.lastParen, RegExp['$+']) // a1b2 c3 console.log(RegExp.leftContext, RegExp['$&']) // d4e5 d4e5 console.log(RegExp.rightContext, RegExp["$'"])
真实案例
-
构造函数生成正则: 通过 class 获取 dom
function getClassByName(className) {var elements = document.getElementByTagName('*') var regex = new RegExp('(^|\\s)' + className + '($|\\s)') var result = [] var elementsLength = elements.length for (var i = 0; i < elementsLength; i++) {var element = elements[i] if (regex.test(element.className)) {result.push(element) } } return result }
-
字符串保存数据: 判断数据类型
var utils = {} 'Boolean|Number|String|Function|Array|Date|RegExp|Object|Error'.split('|').forEach(function(item) {utils['is' + item] = function(obj) {return {}.toString.call(obj) === '[object'+ item +']' } }) console.log(utils.isArray([1, 2, 3]))
-
正则替代 &&(不兼容 IE)
var readyRegex = /complete|loaded|interactive/ function ready(callback) {if (readyRegex.test(document.readyState) && document.body) {callback() } else {document.addEventListener('DomContentLoaded', function() {callback() }, false) } }
-
强大的 replace: 参数查询压缩
function compress(source) {var keys = {} // [^=&]中的 & 不能去掉, 否则第二次会匹配成 &b=2 // key 不能为空, value 有可能为空, 所以第一次是 +, 第二次是 * source.replace(/([^=&]+)=([^&])*/g, function(full, key, value) {keys[key] = (keys[key] ? keys[key] + ',' : '') + value }) var result = [] for (var key in keys) {result.push(key + '=' + keys[key]) } return result.join('&') } // a=1,3&b=2,4 console.log(compress('a=1&b=2&a=3&b=4'))
-
根据字符串生成正则
function createRegex(regex) { try {if (regex[0] === '/') {regex = regex.split('/') regex.shift() var flags = regex.pop() regex = regex.join('/') regex = new RegExp(regex, flags) } else {regex = new RegExp(regex, 'g') } return { success: true, result: regex } } catch (e) { return { success: false, message: '无效的正则表达式' } } } // {success: true, result: /\d/gm} console.log(createRegex('/\\d/gm')) // {success: true, result: /\d/g} console.log(createRegex('/\\d/g'))
参考资料
-
教程类
-
《JavaScript 正则表达式迷你书》
- JS 正则表达式完整教程 - 老姚
- 这次不会说我的正则教程没写全了吧??
-
-
文档类
- 正则表达式简明参考
- 正则表达式
- 不包含某个字符
- 至少包含某个字符
- 最全的常用正则表达式大全——包括校验数字、字符、一些特殊的需求等等
-
实用?
-
网址校验
- 验证类
- JavaScript 正则编写 - 原理篇
-
-
工具
- regexper: 正则可视化网站
- 0-9 ↩
- 0-9a-zA-Z_ ↩
- tvnrf ↩
- nru2028u2029 ↩