正则表达式
正则表达式的强大之处就可以模糊描述某一段或者某一类字符串。其实生活中,我们经常在用“正则表达式”。比如说我们想去广州越秀山,不认识路,跟路人说,有五羊石像的景区怎么走;这个描述其实就是一种正则表达,我们在用另外一种方式描述我们想找的东西
-
两种模糊查询
(1) 横向模糊查询
横向模糊指的是,一个正则可匹配的字符串的局部的长度不是固定的,可以是多种情况的
比如说: 在一条村里面找出有3到5片田的人
则匹配模式就是 /田{3,5}/
(2) 纵向模糊指的是,一个正则匹配的字符串,具体到某一位字符时,它可以不是某个确定的字符,可以有多种可能
比如说:在一个班里面找出有男朋友或者有女朋友的人
则匹配模式就是 /[(有男朋友) (有女朋友) ]/
-
字符组
虽叫字符组(字符类),但只是其中一个字符
例如 [abc],表示匹配一个字符,它可以是 "a"、"b"、"c" 之一
(1) 范围表示法
[123456abcdefGHIJKLM],可以写成 [1-6a-fG-M]。用连字符 - 来省略和简写
注意:因为连字符有特殊用途 ,要使用时需要转义
(2) 排除字符组
排除字符组(反义字符组)的概念。例如 [^abc],表示是一个除 "a"、"b"、"c"之外的任意一个字 符。字符组的第一位放 ^(脱字符),表示求反的概念
(3) 常见的简写形式
如果要匹配任意字符怎么办?可以使用 [\d\D]、[\w\W]、[\s\S] 和 [^] 中任何的一个
-
量词
量词也称重复。掌握 {m,n} 的准确含义后,只需要记住一些简写形式
-
贪婪匹配
正则默认是贪婪的,它会尽可能多的匹配
(function () { var regex = /\d{2,5}/g; var string = "123 1234 12345 123456"; console.log(string.match(regex)); // => ["123", "1234", "12345", "12345"] })();(function () { let people = "你老板问你想要多少钱/月,你说想要10000000000"; console.log(people.match(/你老板问你想要多少钱\/月,你说想要10+/g)); //["你老板问你想要多少钱/月,你说想要10000000000"] })(); -
惰性匹配
在量词后面添加? 表示惰性匹配,在准确的情况下会匹配最少
var regex = /\d{2,5}?/g; var string = "123 1234 12345 123456"; console.log( string.match(regex) ); // => ["12", "12", "34", "12", "34", "12", "34", "56"](function () { let people = "你老板说想想就好了,我只能给你月薪10000000000"; console.log(people.match(/你老板说想想就好了,我只能给你月薪10{3,}?/g)); //["你老板说想想就好了,我只能给你月薪1000"] })();通过在量词后面加个问号就能实现惰性匹配,因此所有惰性匹配情形如下:
-
多选分支
一个模式可以实现横向和纵向模糊匹配。而多选分支可以支持多个子模式任选其一。 具体形式如下:(p1|p2|p3),其中 p1、p2 和 p3 是子模式,用 |(管道符)分隔,表示其中任何之一
var regex = /good|nice/g; var string = "good idea, nice try."; console.log( string.match(regex) ); // => ["good", "nice"]分支结构也是惰性的,第一个分支可以匹配先给第一个匹配,不能匹配则给第二分支匹配
(function () { var regex = /good|goodbye/g; var str = "goodbye goodbye xiaoming"; console.log(str.match(regex));//["good","good"] })();(function () { var regex = /goodbye|good/g; var string = "goodbye goodbye xiaoming"; console.log(string.match(regex)); //["goodbye", "goodbye"] })();(function () { var regex = /goodbye|good/g; var string = "goodidea goodbye xiaoming"; console.log(string.match(regex)); //["good", "goodbye"] })(); -
匹配 16 进制颜色值
#ffbbad #Fc01DF #FFF #ffE
var regex = /#([0-9a-fA-F]{6}|[0-9a-fA-F]{3})/g; -
匹配时间
23:09 02:07
分析: 共 4 位数字,第一位数字可以为 [0-2]。 当第 1 位为 "2" 时,第 2 位可以为 [0-3],其他情况时,第 2 位为 [0-9]。 第 3 位数字为 [0-5],第4位为 [0-9]。
var regex = /^([01][0-9]|[2][0-3]):[0-5][0-9]$/; console.log( regex.test("23:59") ); console.log( regex.test("02:07") );如果也要求匹配 "7:9",也就是说时分前面的 "0" 可以省略 则为
-
匹配日期
2017-06-10
年,四位数字即可,可用 [0-9]{4}。 月,共 12 个月,分两种情况 "01"、"02"、…、"09" 和 "10"、"11"、"12",可用 (0[1-9]|1[0-2])。 日,最大 31 天,可用 (0[1-9]|[12][0-9] | 3 [01])
-
window 操作系统文件路径
F:\study\javascript\regex\regular expression.pdf F:\study\javascript\regex
F:\study\javascript F:\
(1) 匹配 "F:\\",需要使用 [a-zA-Z]:\\\\,其中盘符不区分大小写,注意 \ 字符需要转义。
(2) 文件名或者文件夹名,不能包含一些特殊字符,此时我们需要排除字符组 \[^\\\\:*<>|"? \r \n/ ] 来表示合法字符。
(3) 另外它们的名字不能为空名,至少有一个字符,也就是要使用量词 +。因此匹配 文件夹 \\,可用\[^\\\\:*<>|"?\\r\\n/]+\\\\。
(4) 另外 文件夹\\,可以出现任意次。也就是 (\[^\\\\:* <>|"?\r\n/]+)?
-
匹配id
var regex = /id="[^"]*"/ var string = '<div id="container" class="main"></div>'; console.log(string.match(regex)[0]); // => id="container" -
正则匹配位置
正则表达式是匹配模式,要么匹配字符,要么匹配位置
什么是位置呢? 比如,下图中箭头所指的地方:
(1) ^(脱字符)匹配开头,在多行匹配中匹配行开头。 (2) $(美元符号)匹配结尾,在多行匹配中匹配行结尾。
比如我们把字符串的开头和结尾用 "#" 替换(位置可以替换成字符的!):
var result = "hello".replace(/^|$/g, '#'); console.log(result); // => "#hello#"多行匹配模式(即有修饰符 m)时,二者是行的概念,这一点需要我们注意:
var result = "I\nlove\njavascript".replace(/^|$/gm, '#'); console.log(result); /* #I# #love# #javascript# */(3) \b 是单词边界,具体就是 \w(字母数字或者下划线 ) 与 \W 之间的位置,也包括 \w 与 ^ 之间的位置,和 \w 与 $ 之间的位置
比如考察文件名 "[JS] Lesson_01.mp4" 中的 \b,如下:
var result = "[JS] Lesson_01.mp4".replace(/\b/g, '#'); console.log(result); // => "[#JS#] #Lesson_01#.#mp4#"(4) \B 就是 \b 的反面的意思,非单词边界 ,比如具体说来就是 \w 与 \w、 \W 与 \W、^ 与 \W,\W 与 $ 之间的位置
var result = "[JS] Lesson_01.mp4".replace(/\B/g, '#'); console.log(result); // => "#[J#S]# L#e#s#s#o#n#_#0#1.m#p#4"(5) 正向先行断言 (?=p),其中 p 是一个子模式,即 内容为p 的字符/段的 前面的位置
var result = "hello".replace(/(?=l)/g, '#'); console.log(result); // => "he#l#lo"(6) 负向先行断言 (?!p) 后面不能出现 p 指定的内容,即 不是内容为p 的字符/段的 前面的位置
var result = "hello".replace(/(?!l)/g, '#'); console.log(result); // => "#h#ell#o#"(7) 零宽后行断言 (?<=exp) 匹配 内容exp 后面的位置
var result = "hello".replace(/(?<=l)/g, '#'); console.log(result); // => hel#l#o(9) 零宽负向后行断言(?<!exp) 匹配 不是exp 后面的位置
let hd = "hello"; let reg = /(?<!l)/g; let result = hd.replace(reg,"#"); console.log(result)//#h#e#llo#< 表示在exp或者非epx的后面 的位置
-
数字的千位分隔符表示法
比如把 "12345678",变成 "12,345,678"
var regex = /(?!^)(?=(\d{3})+$)/g;//不是在开头的前面的位置 且 是在结尾前面 且 是至少出现一次三位数前面 var result = "12345678".replace(regex, ',') console.log(result); // => "12,345,678" result = "123456789".replace(regex, ','); console.log(result); // => "123,456,789" -
验证密码
必须包含数字的原子组 (?=.*[0-9])
(?=.*[0-9]) 表示该位置后面的字符匹配 .*[0-9],即,有任何多个任意字符,后面再跟个数字
必须包含小写字母的原子组 (?=.*[a-z])
-
括号
强调括号内的正则是一个整体,即提供子表达式
/a+/ 匹配连续出现的 "a",而要匹配连续出现的 "ab" 时,需要使用 /(ab)+/
(1) 分组引用
以日期为例。假设格式是 yyyy-mm-dd 的,我们可以先写一个简单的正则
var regex = /\d{4}-\d{2}-\d{2}/;然后再修改成括号版的:
var regex = /(\d{4})-(\d{2})-(\d{2})/;对比这两个可视化图片,与前者相比,后者多了分组编号,如 Group #1。 正则引擎在匹配过程中,会给每一个分组都开辟一个空间,用来存储每一个分组匹配到的数据。 既然分组可以捕获数据,那么我们就可以使用它们
(2) 提取数据 (没有完善,怎么提取)
比如提取出年、月、日,可以这么做:
var regex = /(\d{4})-(\d{2})-(\d{2})/; var string = "2017-06-12"; console.log( string.match(regex) ); // => ["2017-06-12", "2017", "06", "12", index: 0, input: "2017-06-12"]var regex = /(\d{4})-(\d{2})-(\d{2})/; var string = "2017-06-12"; regex.test(string); // 正则操作即可,例如 //regex.exec(string); //string.match(regex); console.log(RegExp.$1); // "2017" console.log(RegExp.$2); // "06" console.log(RegExp.$3); // "12"(3) 替换
比如,想把 yyyy-mm-dd 格式,替换成 mm/dd/yyyy
var regex = /(\d{4})-(\d{2})-(\d{2})/; var string = "2017-06-12"; var result = string.replace(regex, "$2/$3/$1"); console.log(result); // => "06/12/2017"其中 replace 中的,第二个参数里用 2、$3 指代相应的分组。等价于如下的形式:
var regex = /(\d{4})-(\d{2})-(\d{2})/; var string = "2017-06-12"; var result = string.replace(regex, function () { return RegExp.$2 + "/" + RegExp.$3 + "/" + RegExp.$1; }); console.log(result); // => "06/12/2017也等价于:
var regex = /(\d{4})-(\d{2})-(\d{2})/; var string = "2017-06-12"; var result = string.replace(regex, function (match, year, month, day) { return month + "/" + day + "/" + year; }); console.log(result); // => "06/12/2017 -
反向引用
正则本身里可以引用分组,但只能引用之前出现的分组,即反向引用
比如要写一个正则支持匹配如下三种格式:
2016-06-12 2016/06/12 2016.06.12
假设我们想要求分割符前后一致怎么办?此时需要使用反向引用:
var regex = /\d{4}(-|\/|\.)\d{2}\1\d{2}/; var string1 = "2017-06-12"; var string2 = "2017/06/12"; var string3 = "2017.06.12"; var string4 = "2016-06/12"; console.log( regex.test(string1) ); // true console.log( regex.test(string2) ); // true console.log( regex.test(string3) ); // true console.log( regex.test(string4) ); // false注意里面的 \1,表示的引用之前的那个分组 (-|\/|\.),不管分组1匹配到什么(比如 -),\1 都匹配和分组1同样的具体某个字符
(2) 括号嵌套
var regex = /^((\d)(\d(\d)))\1\2\3\4$/; var string = "1231231233"; console.log( regex.test(string) ); // true console.log( RegExp.$1 ); // 123 console.log( RegExp.$2 ); // 1 console.log( RegExp.$3 ); // 23 console.log( RegExp.$4 ); // 3第一个是 \1,是第一个分组内容,那么看第一个开括号对应的分组是什么,是 "123"
接下来的是 \2,找到第2个开括号,对应的分组,匹配的内容是 "1"
接下来的是 \3,找到第3个开括号,对应的分组,匹配的内容是 "23"
最后的是 \4,找到第3个开括号,对应的分组,匹配的内容是 "3"
(3) 分组后面有量词
分组后面有量词的话,分组最终捕获到的数据是最后一次的匹配
var regex = /(\d)+/; var string = "12345"; console.log( string.match(regex) ); // => ["12345", "5", index: 0, input: "12345"]同理对于反向引用,也是这样的。测试如下
var regex = /(\d)+ \1/; console.log( regex.test("12345 1") ); // => false console.log( regex.test("12345 5") ); // => true -
非捕获括号
使用括号,会捕获它们匹配到的数据,以便后续引用,因此也称它们是捕获型分组和捕获型分 组
如果只想要括号最原始的功能,但不会引用它,即,既不在 API 里引用,也不在正则里反向引用。 此时可以使用非捕获括号 (?:p) 和 (?:p1|p2|p3)
var regex = /(?:ab)+/g; var string = "ababa abbb ababab"; console.log( string.match(regex) ); // => ["abab", "ab", "ababab"] -
操作符的优先级
竖杠优先级最低
-
正则表达式的四种操作
(1) 验证
var regex = /\d/; var string = "abc123"; console.log(regex.test(regex))//true(2) 切分
var regex = /\D/; console.log("2017/06/26".split(regex)); console.log("2017.06.26".split(regex)); console.log("2017-06-26".split(regex)); // => ["2017", "06", "26"](3) 提取
正则通常要使用分组引用(分组捕获)功能,还需要配合使用相关 API
var regex = /^(\d{4})\D(\d{2})\D(\d{2})$/; var string = "2017-06-26"; console.log( string.match(regex) ); // =>["2017-06-26", "2017", "06", "26", index: 0, input: "2017-06-26"]使用excu全局匹配
var string = "2017.06.27"; var regex2 = /\b(\d+)\b/g; var result; //配合while循环使用(全局匹配常用) while ( result = regex2.exec(string) ) { console.log( result, regex2.lastIndex ); } // => ["2017", "2017", index: 0, input: "2017.06.27"] 4 // => ["06", "06", index: 5, input: "2017.06.27"] 7 // => ["27", "27", index: 8, input: "2017.06.27"] 10使用
match全局获取页面中标签内容,但并不会返回匹配细节,使用matchAll方法可以返回迭代对象,显示更多的细节
let str = "houdunren"; let reg = /[a-z]/ig; for (const iterator of str.matchAll(reg)) { console.log(iterator); }(4) 替换
var string = "2017-06-26"; var today = new Date( string.replace(/-/g, "/") ); console.log( today ); // => Mon Jun 26 2017 00:00:00 GMT+0800 (中国标准时间)(5) 查找
var regex = /\d/; var string = "abc123"; console.log(regex.search(regex))//3 -
配合字符串方法
(1) split(分隔符,分隔出来的字段的最大长度)方法
var string = "html,css,javascript"; console.log( string.split(/,/, 2) ); // =>["html", "css"]正则使用分组时,结果数组中是包含分隔符的:
var string = "html,css,javascript"; console.log( string.split(/(,)/) ); // =>["html", ",", "css", ",", "javascript"](2) replace(正则表达式,字符串)
当第二个参数是字符串时,如下的字符有特殊的含义
属性 描述 2,…,$99 匹配第 1-99 个 分组里捕获的文本 $& 匹配到的子串文本 $ ` 匹配到的子串的左边文本 $ ' 匹配到的子串的右边文本 $$ 美元符号 例如,把 "2,3,5",变成 "5=2+3":
var result = "2,3,5".replace(/(\d+),(\d+),(\d+)/, "$3=$1+$2"); console.log(result); // => "5=2+3"又例如,把 "2,3,5",变成 "222,333,555":
var result = "2,3,5".replace(/(\d+)/g, "$&$&$&"); console.log(result); // => "222,333,555"再例如,把 "2+3=5",变成 "2+3=2+3=5=5":
var result = "2+3=5".replace(/=/, "$&$`$&$'$&"); console.log(result); // => "2+3=2+3=5=5"说明: 要把 "2+3=5",变成 "2+3=2+3=5=5",其实就是想办法把 = 替换成 =2+3=5=,其中,$& 匹配的是 =, $` 匹配的是 2+3,' 匹配的是 5。因此使用 "\&&&" 便达成了目的。
(3) replace(正则表达式,回调函数)
"1234 2345 3456".replace(/(\d)\d{2}(\d)/g, function (match, $1, $2, index, input) { console.log([match, $1, $2, index, input]); }); // => ["1234", "1", "4", 0, "1234 2345 3456"] // => ["2345", "2", "5", 5, "1234 2345 3456"] // => ["3456", "3", "6", 10, "1234 2345 3456"] -
修饰符
修饰符 描述 g 全局匹配,即找到所有匹配的,单词是 global i 匹配的时候不区分字母大小写,单词是 ingoreCase m 多行匹配,除了字符串的开头和结尾。元字符” 的匹配范围) s 视为单行忽略换行符,使用 .可以匹配所有字符(包括换行符),单词是Singleline (本质改变了. 的匹配配范围)lastIndex 从 regexp.lastIndex开始匹配u 正确处理四个字符的 UTF-16 编码 y (1)g 全局匹配
不使用g,将匹配成功一次就停止
let str = "AaaaaAAAcC"; let result = str.replace(/a/, "s"); console.log(result);//AsaaaAAAcC使用g
let str = "AaaaaAAAcC"; let result = str.replace(/a/g, "s"); console.log(result);//AssssAAAcC(2) i 忽略大小写
let str = "AaaaaAAAcC"; let result = str.replace(/a/gi, "s"); console.log(result);//sssssssscC(3) m多行匹配,在默认状态下,一个字符串无论是否换行只有一个开始^和结尾
① 没有使用m,下面代码不能够匹配字符串"an",尽管"an"后面已经换行了,但是并没有采用多行匹配,所以不是字符串行的结尾
var str = "This is an\n antzone good"; var reg = /an$/; console.log(str.match(reg));//null② 使用m(改变了)
var reg = /^b/m; var str = 'test\nbbs'; console.log(str.match(reg)); //["b", index: 5, input: "test↵bbs", groups: undefined](4) s单行匹配
var reg = /.*/; var str = 'test\nbbs'; console.log(str.match(reg)); // ["test", "", "bbs", ""]var reg = /.*/gs; var str = 'test\nbbs'; console.log(str.match(reg)); //["test↵bbs", ""](5) u模式
let p = "Where there is a will, there is a way-有志者,事竟成!"; //匹配所有的标点符号 console.log(p.match(/\p{P}/gu)); //匹配所有的字母 console.log(p.match(/\p{L}/gu)); //匹配中文 console.log(p.match(/\p{sc=Han}/gu))//["有", "志", "者", "事", "竟", "成"] //使用 u 模式可以正确处理四个字符的 UTF-16 字节编码 let str = "𝒳𝒴"; console.table(str.match(/[𝒳𝒴]/)); //结果为乱字符"�" console.table(str.match(/[𝒳𝒴]/u)); //结果正确 "𝒳"(6) lastIndex
RegExp对象lastIndex 属性可以返回或者设置正则表达式开始匹配的位置
必须结合 g 修饰符使用
对 exec 方法有效
匹配完成时,lastIndex 会被重置为0
let str = "Where there is a will, there is a way." let reg = /\w/g; reg.lastIndex = 10; console.log(reg.exec(str)); //=> ["e", index: 10, input: "Where there is a will, there is a way.", groups: undefined] console.log(reg.lastIndex);//11 console.log(reg.exec(str)); //=>["i", index: 12, input: "Where there is a will, there is a way.", groups: undefined] console.log(reg.lastIndex);//12(7) y模式
//使用y 模式后如果从 lastIndex 开始匹配不成功就不继续匹配了 let str = `紧急电话有:110,119,120`; let reg = /(\d+),?/y; reg.lastIndex = 7;//必须从第7、11、15个字符串开始才匹配成功 while (res = reg.exec(str)) console.log(res); -
正则的属性
模式属性
var regex = /\w/img; console.log( regex.global ); console.log( regex.ignoreCase ); console.log( regex.multiline ); // => true // => true // => truesource属性(打印字面量模式字符串)
var className = "high"; var regex = new RegExp("(^|\\s)" + className + "(\\s|$)"); console.log( regex.source ) // => (^|\s)high(\s|$)