最近学习了 AST 抽象语法树,在代码解析的过程中广泛使用了正则表达式,由此认识到自己在正则基础方面的薄弱,虽然清楚每个符号所表示的含义,但是当一大串正则符号出现在自己面前时,还是会懵逼一会,无法融汇贯通的掌握正则。
下面是自己整理的正则基础知识和收集到的一些实战训练。希望通过训练和笔记来加深自己对正则表达式的掌握!
基础
正则表达式(Regular Expression,在代码中常简写为regex、regexp或RE)使用单个字符串来描述、匹配一系列符合某个句法规则的字符串搜索模式。
搜索模式可用于文本搜索和文本替换。
下面是正则的基础语法,只要熟悉就行,重要还是结合实际多运用正则,才能真正的掌握正则。
创建正则表达式
方式1:
// 语法格式为: /正则表达式主体/修饰符(可选)
// 案例
var patt = /var/i
// var 是正则表达式主体
// i 是修饰符
复制代码
方式2:
// 创建 RegExp 对象
// new RegExp(正则表达式主体[, 修饰符])
// RegExp(正则表达式主体[, 修饰符])
var patt = new RegExp('var', 'i')
复制代码
注意:
/var/i
创键的正则表达式对象和new RegExp(var, i)
创建的对象并不相等。虽然它们匹配规则一样。
指定匹配模式的修饰符有:
修饰符 | 描述 |
---|---|
i | 执行对大小写不敏感的匹配。 |
g | 执行全局匹配(查找所有匹配而非在找到第一个匹配后停止)。 |
m | 执行多行匹配。 |
s | 允许 . 去匹配多行 |
u | Unicode; 将模式视为Unicode序列点的序列Unicode; |
y | 粘性匹配; 仅匹配目标字符串中此正则表达式的lastIndex 属性指示的索引(并且不尝试从任何后续的索引匹配)。 |
字符类别(Character Classes)
元字符是正则表达式规定的一个特殊代码
代码 | 说明 |
---|---|
. | 匹配除换行符以外的任意字符。注意:/[.]/ 中匹配的是符号点. |
\w | 匹配字母或数字或下划线或汉字 [A-Za-z0-9_] |
\s | 匹配任意的空白符 [\f\n\r\t\v\u00a0] |
\d | 匹配数字 [0-9] |
\ | 用于转义 |
边界(Boundaries)
代码 | 说明 |
---|---|
\b | 匹配单词的开始或结束 例如:/\bno/ 匹配 "at noon" 中的 "no" |
^ | 匹配字符串的开始 |
$ | 匹配字符串的结束 |
分组(Grouping)与反向引用(back references)
字符 | 含义 |
---|---|
(x) |
捕获括号。匹配 x 并且捕获匹配项。 /(foo)/ 匹配且捕获 "foo bar." 中的 "foo" 。被匹配的子字符串可以通过 元素[n] 中找到,或 RegExp 对象的属性 $n 中找到。会消耗性能,如果需要捕获的数组,可以使用非捕获括号 |
\n |
反向引用(back reference),指向正则表达式中第 n 个括号(从左开始数)中匹配的子字符串。/apple(,)\sorange\1/ 匹配 "apple, orange, cherry, peach." 中的 "apple,orange," (最后的, 号) |
(?:x) |
非捕获括号(non-capturing parentheses)。匹配 x 不会捕获匹配项。匹配项不能够从结果再次访问。(推荐) |
重复
代码/语法 | 说明 |
---|---|
* | 重复零次或更多次 |
+ | 重复一次或更多次 |
? | 重复零次或一次 |
x*? x+? |
最小可能匹配,非贪婪模式。/".*?"/ 匹配 "foo" "bar" 中的 "foo" |
{n} | 重复n次 |
{n,m} | 重复n到m次 |
断言(Assertions)
代码/语法 | 说明 |
---|---|
x(?=y) | 只有当 x 后面紧跟着 y 时,才匹配 x。/Jack(?=Sprat)/ 只有在 'Jack' 后面紧跟着 'Sprat' 时才匹配 |
x(?!y) | 只有当 x 后面不是紧跟着 y 时,才匹配 x。 |
实例属性
RegExp.prototype.global
是否开启全局匹配RegExp.prototype.ignoreCase
是否要忽略字符的大小写。RegExp.prototype.lastIndex
下次匹配开始的字符串索引位置。RegExp.prototype.multiline
是否开启多行模式匹配(影响 ^ 和 $ 的行为)。RegExp.prototype.source
正则对象的源模式文本。RegExp.prototype.sticky
是否开启粘滞匹配。
实例方法
RegExp.prototype.exec()
在目标字符串中执行一次正则匹配操作。RegExp.prototype.test()
测试当前正则是否能匹配目标字符串。String.prototype.match(pattern)
根据pattern
对字符串进行正则匹配,返回匹配结果数组,如匹配不到返回null
String.prototype.replace(pattern, replacement)
根据pattern
进行正则匹配,把匹配结果替换为replacement
, 返回一个新的字符串
实战
改变文本结构
var re = /(\w+)\s(\w+)/
// \w 匹配 [A-Za-z0-9_]
// + 重复一次或更多次
// \s 匹配 空格符
// () 捕获括号
var str = "John Smith"
var newstr = str.replace(re, "$2, $1")
// $1: 捕获的值为 John
// $2: 捕获的值为 Smith
console.log(newstr) // 转换后为 "Smith, John"
复制代码
训练1: 请通过正则表达式,把 () => 1+1
修改为 function () { return 1+1 }
多行匹配
var s = "Please yes\nmake my day!";
s.match(/yes.*day/);
// . 符号默认不匹配 \n
// 可以改为 /yes.*day/s ,使用 s 标识符来匹配多行
// 返回 null
s.match(/yes[^]*day/);
// ^ 匹配字符串的开始
// [^]* 表示匹配任何字符,包括换行符
// 返回 yes\nmake my day
复制代码
训练2: 请通过正则表达式,获取到 ab a\nb ab
中的 a\nb
字符串
使用带有 ”sticky“ 标志的正则表达式
var text = "First line\nsecond line";
var regex = /(\S+) line\n?/y;
// () 捕获括号
// \S+ 匹配一次或更多次非空白符
// \n? 匹配零到一次换行符
var match = regex.exec(text);
console.log(match[1]); // 匹配到 First
// 输入 match[0] 返回的是 First line\n 所要进行匹配的文本
console.log(regex.lastIndex); // 匹配到位置 11
var match2 = regex.exec(text);
console.log(match2[1]); // 匹配到 Second
console.log(regex.lastIndex); // 匹配到位置 22
var match3 = regex.exec(text);
console.log(match3 === null); // 返回 true, 没有匹配到结果
复制代码
训练3: 有文本"price: 10¥\nprice: 20¥\nprice: 30¥"
, 请获取到 price 的值,保存到数组中,结果输出为 ['10', '20', '30']
匹配链接
提取链接传递参数的值
// 提取 str 的 name 和 age
var str = 'https://www.baidu.com?name=jawil&age=23'
// 获取name的值
var regName = /\?name=([^&]*)/
// \? 表示匹配 ? 字符,从该字符开始匹配
// name= 表示匹配 name= 的字符串
// [^&]* 表示匹配字符直到遇到 & 符号为止
// ([^&]*) 表示捕获匹配到的结果
// match 的值为 ["?name=jawil", "jawil"]
var match = regName.exec(str)
console.log(match[1]) // 返回 jawil
复制代码
训练4: 请修改正则表达式,获得到 age 的属性值, 该正则表达式是怎么样的?
数字格式化
把 1234567890 格式化为 1,234,567,890
var str = '1234567890'
var reg = /\B(?=(\d{3})+(?!\d))/g
// \b 匹配单词的开始或结束
// \B 匹配单词的非开始和非结束的位置
// \Bon 匹配 ' on' 为 null,即开头不能为 on
// \Bon 匹配 ' onon' 为 on,匹配后一个 on
// (\d{3}+) 捕获1个或多个的3个连续数字
// (?!\d) 断言: 表示3个数字不允许后面跟着数字
// (\d{3}+)+(?!\d) 表示匹配的后边界跟着 3*n(n>=1)的数字.
// (?=) 断言: 表示后边必须跟着的内容
// \B(?=(\d{3}+)+(?!\d)) 匹配非边界后跟着的3个数字
// 运行过程
// 第一次匹配到为 ["", "890"] 没有匹配到文本,但捕获到3个数字
// 替换后为 1234567,890
// 第二次匹配到为 ["", "567"]
// 替换后为 1234,567,890
// 第三次匹配到为 ["", "234"]
// 替换后为 1,234,567,890
// 最后一次运行结果为 null 替换结束
var format = str.replace(reg, ',')
console.log(format) // 1,234,567,890
复制代码
训练5: 通过正则表达式把字符串'255255255255' 格式化为 ip地址格式 '255.255.255.255'
训练6: 编写正则表达式测试传入的ip地址是否正确,例如传入 1.1.1.1
返回 true, 传入 1.1.1.257
返回 false
零宽断言
如果不想匹配符号,只匹配一个位置,就要用到"零宽断言"。用于帮我们匹配文本,而它自身不会被匹配到内容中去。
var str = 'ttabttdef'
var reg = /tt(?=ab)/
reg.exec(str)
// 匹配到的是 tt, 只有后面跟的是 ab 的才匹配
var reg1 = /tt(?=ab)ab/
reg1.exec(str)
// 匹配到的是 ttab, 断言只匹配位置, 所以断言匹配的这些字符会接下去进行匹配。
// 而不会因为被断言匹配过了就跳过这些字符。
复制代码
训练7: 使用断言匹配出字符串
中的字符串 red blue green
。
训练8: 使用断言,匹配出
中的 任意字符串
, 其中 xxx 表示标签名, 比如匹配出
中的
asdfsdadsasdfa
asdfsd
虚拟 DOM 标签解析
在Vue源码中构建虚拟DOM, 首先把 template 编译成AST语法树, 再转换为 render 函数 最终返回一个VNode(VNode就是Vue的虚拟DOM节点)
而要编译 template, 首先就是要对 template 进行解析。解析的过程中使用到了正则表达式, 比如解析下面代码
var dom = `{{item}}`
// 匹配变量名
const variable = /[a-zA-Z_][\w\-\.]*/
// 匹配标签名
const tagName = /^<([a-zA-Z_][\w\-\.]*)/
console.log(tagName.exec(dom)[1])// 返回 div
// 匹配属性
const attr = /\s*([^\s'"<>/=]+)(?:=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+))/g
// \s*([^\s'"<>/=]+) 匹配属性名
// (?:=) 匹配赋值号 =
// (?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)) 匹配属性值,包括 ""、 '' 和其它形式
var res = attr.exec(dom)
console.log(res[1]) // 返回 :class
console.log(res[2]) // 返回 "c"
// 匹配 {{}} 里的内容
const val = /{{(.*)}}/
console.log(val.exec(dom))// 返回 item
复制代码
答案
训练1:
var str = '() => 1+1'
var reg = /(\(\))\s(=>)\s([^]*)/
var str1 = str.replace(reg, 'function $1 { return $3 }')
复制代码
训练2:
var s = "ab a\nb ab"
s.match(/a\nb/)
复制代码
训练3:
var str = 'price: 10¥\nprice: 20¥\nprice: 30¥'
var reg = /\D*(\d+)/y
var a = []
while((match = reg.exec(str)) !== null) {
a.push(match[1])
}
console.log(a)
复制代码
训练4:
var str = 'https://www.baidu.com?name=jawil&age=23'
var regAge = /\&age=([^&]*)/
var match = regAge.exec(str)
复制代码
训练5:
var pattern = /\B(?=(\d{3})+(?!\d))/g
var str = '255255255255'
str.replace(pattern, '.')
复制代码
训练6:
- 首先要匹配的是 0 ~ 255
- 匹配 50 ~ 55:
5[0-5]{1}
- 匹配 0 ~ 49:
[0-4]\d{1}
- 匹配 200 ~ 255:
2(5[0-5]{1}|[0-4]\d{1})
- 匹配 0 ~ 199:
[0-1]?\d{1,2}
- 完整匹配 0 ~ 255:
(2(5[0-5]{1}|[0-4]\d{1})|[0-1]?\d{1,2})
组合成检测的ip 的正则表达式
var reg1 = /(2(5[0-5]{1}|[0-4]\d{1})|[0-1]?\d{1,2})\.(2(5[0-5]{1}|[0-4]\d{1})|[0-1]?\d{1,2})\.(2(5[0-5]{1}|[0-4]\d{1})|[0-1]?\d{1,2})\.(2(5[0-5]{1}|[0-4]\d{1})|[0-1]?\d{1,2})/
// 把后边3段检测代码进行简化后
var reg2 = /(2(5[0-5]{1}|[0-4]\d{1})|[0-1]?\d{1,2})(\.(2(5[0-5]{1}|[0-4]\d{1})|[0-1]?\d{1,2})){3}/
复制代码
训练7:
var str="red blue green"
var reg = /(.*)(?=<)/
reg.exec(str)[1] // red blue green
复制代码
训练8:
var str = 'asdas
dfadfsdasdf'
var reg = /^<(\w+)>(.*)(?=<\/\1>)/
reg3.exec(str)[2] // asdas
dfadfsdasdf
复制代码
参考资料
- MDN web docs: RegExp
- JavaScript正则进阶之路——活学妙用奇淫正则表达式
- 正则表达式 - 匹配 IP 地址