javascript 正则表达式学习笔记
正则表达式(Regular Expression 在 js 代码中简写为 regexp),
重点它是一个表达式,正则是修饰表达式的修饰词,意思是符合一个规则的表达式,
主要作用是对字符串进行操作、过滤、以及检索校验的一种规则
一、定义和匹配
通过一个“用户名规则的校验”体验,正则表达式是如何定义和匹配的
定义“正则表达式”的两种方式
1. 直接字面量 /abc/
2. 构造函数 new RegExp('abc')
正则表达式使用的匹配方法
1. 正则表达式上的方法 reg.test(str)
2. 字符串上的方法 str.match(reg)
示例,
输入的用户名,分别是以数字、字母或下划线开头
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>用户名的校验规则</title> </head> <body> <input type="text" id="username"/> <div id="msg"></div> <script> var username = document.getElementById('username'); var msg = document.getElementById("msg"); username.oninput = function(){ checkedFun(this.value); } function checkedFun(str){ var reg = /^[\dA-z_]/; if(str.match(reg)){ msg.innerText = "符合规则"; }else{ msg.innerText = "用户名只能以数字、字母、下划线开头"; } } </script> </body> </html>
监听 oninput 输入事件
获取 input 框输入的值是一个字符串,正则表达式对输入的字符串进行校验
^[\dA-z_]
方括号表示一个字符,字符可以是数字 \d、可以是字母 A-z 或下划线 _
^ 表示以方括号里面的字符开头
str.match(reg)
使用字符串的 match 方法实现正则的匹配
1. 输入 abc 匹配成功,返回的是一个数组 ['a', index: 0, input: 'abc', groups: undefined]
2. 输入空格或中文返回 null 表示不符合匹配规则
用构造函数 RegExp 的方式声明正则表达式,参数传一个字符串是匹配规则
function checkedFun(str){ var reg = new RegExp('^[\\da-zA-Z_]'); if(reg.test(str)){ msg.innerText = "符合规则"; }else{ msg.innerText = "用户名只能以数字、字母、下划线开头"; } }
注意,
new RegExp('^[\\dA-z_]')
参数是字符串,在字符串中 \ 斜杠有特殊的含义,表示转义后面的一个字符,
要想实现 \d 的效果,要在加一个斜杠 \\d 转义一下,表示取消后面第二个 \ 斜杠的含义
reg.text(str)
正则表达式上的匹配方法
1. 参数是要校验的字符串,字符是否符合规则
2. 返回一个布尔值
一些匹配规则
/[3578]$/ 以中括号里面任何一个数字结尾
/[3578]+$/ 至多个任意数字结尾,+号表示1~多个
/^[3578]+$/ 整个字符串就是这样的 3578
/3578/ 整个字符串中含一个整体的 3578
Ps:
input 元素是行级块元素,既可以设置宽高也不会占满一行,修改一下 .msg 元素 display: inline-block 就到一行了
二、原子
什么是原子?
组成正则表达式的最小单位就是原子
原子表
语法 | 描述 |
[abc] | 方括号内的任意一个字符 |
[^abc] | 不在方括号内的字符 |
[0-9] | 0-9 之间的数字 |
[a-z] | 任何小写字母 |
[A-Z] | 任何大写字母 |
[A-z] | 大写和小写字母 |
() | 子表达式 |
\uxxx | 查找十六进制数 xxx 规定的 Unicode 字符 |
[\u4e00-\u9fa5] | 所有中文字符 |
参考图片连接 |
元字符
语法 | 描述 |
. | 代表单个字符,除了换行符和行结束符以外的任意一个字符 |
\w | 小写 w 表示字母、数字、下划线,等价于 [A-Za-z0-9_] |
\W | 非单词字符,就是除了小 \w 以外的字符 |
\d | 数字 |
\D | 非数字 |
\s | 空白字符 |
\S | 非空白字符 |
\b | 单词边界 |
\B | 非单词边界符 |
\n | 换行符 |
\f | 换页符 |
\r | 回车符 |
\t | 制表符 |
\v | 垂直字表符 |
模式修饰符
修饰符 | |
i | 代表 ignorecase 忽略大小写 |
m | 代表 multiline 多行匹配 |
g | 代表 global 全局匹配,匹配出所有符合规则的子字符串 |
. 点
正则表达式中“点”有特殊的含义,表示任意一个字符,除了换行符和行结束符以外,其他的任意一个字符
换行符是 \n
行结束符就是回车 \r
也就是说 \n 和 \r 它匹配不出来
/.ello/ 以任意一个字符开始,后面必须是 ello
var str = "hello world, hello duyi, welcome duyi"; var reg = /.ello/g; console.log(str.match(reg)); // ['hello', 'hello']
修饰符 g 全局匹配,表示把所有符合条件的全部匹配出来
字符串的 match 方法返回是所有符合条件的“子字符串”是一个数组的形式
加了 g 返回的是数组
如果没有加 g 返回的是类数组
var str = "hello world, hello duyi, welcome duyi"; var reg = /.ello/; console.log(str.match(reg)); // ['hello', index: 0, input: 'hello world, hello duyi, welcome duyi', groups: undefined] // [ // 0: "hello" // 匹配成功的字符片段 // groups: undefined // groups代表一个组,这个组代表一个子表达式,没有就是undefined // index: 0 // 当前匹配成功的索引值 // input: "hello world, hello duyi, welcome duyi" // 我们要匹配的整个字符串 // length: 1 // ]
点不能匹配换行符 \n 和结束符 \r,可以匹配出制表符 \t
var str = "第一行\nello world, \rello duyi, \tello lizi"; var reg = /.ello/g; console.log(str.match(reg)); // ['\tello']
alert(str) 弹框里面可以看到换行符、回车符的效果分成了三行,制表符的是一个空白
第一行
ello world,
ello duyi, ello lizi
\w
小写 w 代表大小英文的字母、数字、下划线,
单纯的一个 \w 返回了所有的英文字符,但不包括空格、制表符、换行符、行结束符,逗号
var str = "\rello world, \nello duyi, \tello lizi"; var reg = /\w/g; console.log(str.match(reg)); // ['e', 'l', 'l', 'o', 'w', 'o', 'r', 'l', 'd', 'e', 'l', 'l', 'o', 'd', 'u', 'y', 'i', 'e', 'l', 'l', 'o', 'l', 'i', 'z', 'i']
量词
+ 加号,表示匹配前一个字符的 1 至 多个
* 星号,代表前一个字符的 0 至 多个
元字符 \w 结合量词 + 加号,可以拆分字符串中一个一个的单词,不要逗号、空格、制表符
var str = "hello world, hello duyi, welcome duyi \tello"; var reg = /\w+/g; console.log(str.match(reg)); // ['hello', 'world', 'hello', 'duyi', 'welcome', 'duyi', 'ello']
正则表达式的规则一般遵循“贪婪匹配”就是越多越好,
所以量词 + 号是有几个就匹配几个,不是返回单个的字母了
* 星号表示 0 至 多个,也就是说没有的也能给匹配出来,
匹配出好多个空位,代表 0 个英文字符,
还有空逗号、制表符都匹配成空位了
var str = "hello world, hello duyi, welcome duyi \tello"; var reg = /\w*/g; console.log(str.match(reg)); // ['hello', '', 'world', '', '', 'hello', '', 'duyi', '', '', 'welcome', '', 'duyi', '', '', 'ello', '']
\W
大写 W 匹配非单词字符,空格、逗号、制表符
var str = "\rello world, \nello \tduyi"; var reg = /\W/g; console.log(str.match(reg)); // ['\r', ' ', ',', ' ', '\n', ' ', '\t'] // 0: "\r" // 1: " " // 2: "," // 3: " " // 4: "\n" // 5: " " // 6: "\t"
\d
小写 d 代表数字,数字一个一个的匹配出来了
var str = "hello world123, hello456 duyi, welcome duyi \tello132323"; var reg = /\d/g; console.log(str.match(reg)); // ['1', '2', '3', '4', '5', '6', '1', '3', '2', '3', '2', '3']
配合量词 + 号匹配出连着的数字
var str = "hello world123, hello456 duyi, welcome duyi \tello132323"; var reg = /\d+/g; console.log(str.match(reg)); // ['123', '456', '132323']
\D
大写 D 代表非数字,配合量词 + 号,除数字以外的其他字符都匹配出来了
var str = "hello world123, hello456 duyi, welcome duyi \tello132323"; var reg = /\D+/g; console.log(str.match(reg)); // ['hello world', ', hello', ' duyi, welcome duyi \tello'] // 0: "hello world" // 1: ", hello" // 2: " duyi, welcome duyi \tello"
相当于数字充当了分割符
\s
小写 \s 表示空白字符,其实就是空格,包含 \r、\n、\t
因为有量词 + 号,\n 连着前面的空格
var str = "hello world\r, hello duyi, \nwel comeduyi\t"; var reg = /\s+/g; console.log(str.match(reg)); // [' ', '\r', ' ', ' ', ' \n', ' ', '\t'] // 0: " " // 1: "\r" // 2: " " // 3: " " // 4: " \n" // 5: " " // 6: "\t" // length: 7
\S
大写 S 非空白字符,没有 \r、\n、\t
var str = "hello world, hello duyi,\r welcome \nduyi \tello132323"; var reg = /\S+/g; console.log(str.match(reg)); // ['hello', 'world,', 'hello', 'duyi,', 'welcome', 'duyi', 'ello132323']
\b
匹配单词边界,每一个单词有两个边界,七个单词有14个空白
var str = "hello world, hello duyi,\r welcome \nduyi \tello"; var reg = /\b/g; console.log(str.match(reg)); // ['', '', '', '', '', '', '', '', '', '', '', '', '', '']
\B
非单词边界就是两个挨着的字母之间
var str = "hello world"; var reg = /\B/g; console.log(str.match(reg)); // ['', '', '', '', '', '', '', '']
单词边界和空格的区别
1. 空格有 " " 空字符
2. 整个字符如果连着 -hello- 左右两边是单词边界,也就是说一个单词有两个边界
3. 非单词边界就 h-e-l-l-o 字母之间
var str = "hello"; var reg = /\b/g; console.log(str.replace(reg, '-')); // -hello- var reg = /\B/g; console.log(str.replace(reg, '-')); // h-e-l-l-o
三、量词
量词是用来修饰前面一个字符的个数,就是你想匹配多少个
量词 | 描述 |
n+ | 1次多次 |
n* | 0次1次多次 |
n? | 0次1次 |
n{x} | 前一个字符固定的个数 |
n{x,y} | 代表一个范围 |
n{x,} | |
n$ | |
^n | |
?=n | 正向肯定预查,m(?=n) 匹配任何后面紧接着指定字符串 n 的字符串 |
?!n | 正向否定预查,m(?!n) 匹配后面不是 n 的 m |
?<=n | 反向肯定预查,(?<=n)m 匹配前面是 n 的 m |
?<!n | 反向否定预查,(?<!n)m 匹配前面不是 n 的 m |
? 号
? 号只匹配 0 或 1 个,可以取消贪婪匹配
把英文字母一个一个的拿出来,只取字母不要空格,正常的做法是
1. 全局 g 匹配
2. [A-z] 包括大小写字母
3. 字符串的 match 方法返回一个数组
var str = "hello world"; var reg = /[A-z]/g; console.log(str.match(reg)); // ['h', 'e', 'l', 'l', 'o', 'w', 'o', 'r', 'l', 'd']
+号、*号都是贪婪匹配,就是有多少匹配多少
加上量词 + 号表示 1 次至多次,可以匹配出字符串中的两个单词
var str = "hello world"; var reg = /[A-z]+/g; console.log(str.match(reg)); // ['hello', 'world']
这是正则的贪婪匹配的效果,
就是有多少符合条件的就都匹配出来作为一个串,能匹配多就不匹配少
加上 ? 问号,表示“非贪婪匹配”能少就不多,匹配结果又是一个一个的字母了
var str = "hello world"; var reg = /[A-z]+?/g; console.log(str.match(reg)); // ['h', 'e', 'l', 'l', 'o', 'w', 'o', 'r', 'l', 'd']
{} 花括号
传一个数字表示前面字符的个数
var str = "hello world"; var reg = /[A-z]{3}/g; console.log(str.match(reg)); // ['hel', 'wor']
传两个数字表示范围
var str = "hello world"; var reg = /[A-z]{2,4}/g; console.log(str.match(reg)); // ['hell', 'worl']
注意,
两个数字 {2,4} 之间是逗号隔开,并且不能有空格
把 {2, 4} 放到字符里面,有空格就不是范围了,就当做字符匹配了
var str = "hello world{2, 4}"; var reg = /[A-z]{2, 4}/g; console.log(str.match(reg)); // ['d{2, 4}']
?!n 正向否定预查
a(?!d)
意思是希望匹配的是 a,条件是匹配的 a 后面不能是 d
字符串里面有三个 a,匹配出两个后面两个 a
var str = "adkhfalskdfa"; var reg = /a(?!d)/g; console.log(str.match(reg)); // ['a', 'a']
?<=n 反向肯定预查
(?<=f)a
我们想匹配的 a ,条件是前面是 f 后面的那个 a
var str = "adkhfalskdfa"; var reg = /(?<=f)a/g; console.log(str.match(reg)); // ['a', 'a']
?<!n 反向否定预查
(?<!f)a
匹配的是 a,但是 a 的前面不能是 f,只有第一个 a 满足条件
var str = "adkhfalskdfa"; var reg = /(?<!f)a/g; console.log(str.match(reg)); // ['a']
四、子表达式
子表达式可以单独匹配,并将匹配成功的结果在整个正则表示中单独的引用
有这样一个要求,
匹配出三个相同的字符,比如 aaa、bbb、eee
/(\w){3}/ 相当于 /(\w\w\w)/,而每个 \w 有自己的范围,代表范围内不同的字母,最后一个 dde 不符合要求
var str = "aaabbbddeee"; var reg = /(\w){3}/g; console.log(str.match(reg)); // ['aaa', 'bbb', 'dde']
\1 叫反向引用,是子表达式的编号,
代表第一个子表达式匹配出来的内容,
就是把子表达式匹配的内容拿出来用,所以匹配的内容跟第一个表达式相同
var str = "aaabbbddeee"; var reg = /(\w)\1/g; console.log(str.match(reg)); // ['aa', 'bb', 'dd', 'ee']
匹配三个相同的字符就写两个 (\w)\1\1,
后面两个 \1 都是第一个子表达式 {\w} 匹配成功的字符
var str = "aaabbbddeee"; var reg = /(\w)\1\1/g; console.log(str.match(reg)); // ['aaa', 'bbb', 'eee']
子表达式的“反向引用”在字符串替换方法 replace 里面的应用
参数一,传的是正则表达式
参数二,如果传递的是固定的字符串,会把符合匹配规则的部分进行替换
var str = "aaabbbddeee"; var reg = /(\w)\1\1/g; console.log(str.replace(reg, '*')); // **dd*
replace 方法的第二个参数还可以传一个函数,通过 arguments 打印函数里面的参数
var str = "aaabbbddeee"; var reg = /(\w)\1\1/g; str.replace(reg, function(){ console.log(arguments); }); // Arguments(4) ['aaa', 'a', 0, 'aaabbbddeee', callee: ƒ, Symbol(Symbol.iterator): ƒ] // Arguments(4) ['bbb', 'b', 3, 'aaabbbddeee', callee: ƒ, Symbol(Symbol.iterator): ƒ] // Arguments(4) ['eee', 'e', 8, 'aaabbbddeee', callee: ƒ, Symbol(Symbol.iterator): ƒ]
每一次打印一个数组
[
0: aaa, 是匹配成功的字符
1: a, 第一个子表达式 (\w) 匹配成功的内容
2: 0, 是一个索引值,当前匹配成功的位置
]
写上三个形参,打印成功匹配的结果
var str = "aaabbbddeee"; var reg = /(\w)\1\1/g; str.replace(reg, function(param1, param2, param3){ console.log(param1, param2, param3); }); // aaa a 0 // bbb b 3 // eee e 8
做一个字符串去重,
正则规则 /(\w)\1+/ 匹配1至多个重复的字母,
函数里面 return 的内容,就是替换的内容,
所以函数的第二个参数是子表达式匹配成功的内容,直接返回第二个参数 param2 就可以了
var str = "Aaadddoooooooobbeee"; var reg = /(\w)\1+/gi; var result = str.replace(reg, function(param1, param2, param3){ return param2; }); console.log(result); // Adobe
如果是两个 /1,替换重复的字符
var str = "Aaadddoooooooobbeee"; var reg = /(\w)\1\1/gi; var result = str.replace(reg, function(param1, param2, param3){ return param2; }); console.log(result); // Adoooobbe
中间有 8 个字母 o,三个三个的被替换后变成两个o,加上剩下的两个 o,所以结果是四个 o
做一个搜索关键词高亮显示效果,
原理就是给第一个参数 param1 加一个高亮样式后返回
正则表达式拼接关键词后是一个字符串,用 RegExp 方式的参数传的是字符串,所以用它定义正则表达式
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>搜索关键词高亮</title> <style> </style> </head> <body> <script> var str = '<span style="color:#999">快乐的池塘里面,有一只小青蛙,跳啊跳啊跳</span>'; var pond = '池塘'; var frog = '小青蛙' var jump = '跳'; var keywords = `(${pond}|${frog}|${jump})`; var reg = new RegExp(keywords, 'g'); var result = str.replace(reg, function(param1, param2, param3){ return `<span style="color:#008c8c">${param1}</span>`; }); document.body.innerHTML = result; </script> </body> </html>
五、练习
使用正则拆分 url
var str = "https://www.baidu.com/s?tn=68018901_3_dg&ie=UTF-8&wd=flash"; var reg = /(http|https):\/\/([\w\.]+):?(\d*)([\/\w\.%-]*)\??([\w=&]+)?/mig; console.log(reg.exec(str));
/(http|https):\/\/
协议以http或https开头,
斜线 / 在和正则表达式起始符 / 重复了,要用反 \ 斜线转义
/(http|https):\/\/([\w\.]+)/
域名规则是由多个字母、点、数字组成,
正则表达式里面的“点”表示单个字符,除换行和行结束符以外的所有字符,也需要用反斜杠转义
+ 号表示1至多个
/(http|https):\/\/([a-z\.]+):?(\d*)?/ 可以不要最后一个?号
端口号由冒号和多个数字组成,
: 0次1次
\d 0次1次或多次
/(http|https):\/\/([\w\.]+):?(\d*)?([\/\w\.%-]*)
路径可能有英文字符、-、点、中文、斜杠、中文,中文部分转义后有 %,
abc.com/ 域名后面的斜线是路径,有时候默认不显示,所以可以出现0
路径是多个英文字符,也可能没有,所以用量词 *
/(http|https):\/\/([a-z\.]+):?([\d]+)?\/([\/\w\.%-]*)\??([\w=&]+)?/mig
参数部分以 ? 号开头,? 号是量词也需要转义
exec 方法匹配返回的是类数组,
[0] 是整个正则表达式匹配成功的内容
[1] 第一个子表达式匹配的内容,
[2] 第二个子表达式
[3]
... 是从左往右数,如果有嵌套的小括号,从外往里依次的数
2. 给 10000000000 每三个 0 打一个点变成 10.000.000.000
我们想匹配的非单词边界有一个特点,它后面跟着的是三个数字
\d{3} 代表三个连续的数字 \d\d\d
量词里面有一个“正向预查” ?=n
m(?=n) 匹配后面紧跟着 n 的 m,意思是后面的 n 是条件,匹配的是前面的 m
var str = "10000000000"; var reg = /\B(?=\d{3})/; console.log(str.replace(reg, ".")); // 1.0000000000
全局匹配
var str = "10000000000"; var reg = /\B(?=\d{3})/g; console.log(str.replace(reg, ".")); // 1.0.0.0.0.0.0.0.000
1.0000000000 第一个非单词边界的位置,后面跟着三个数字
1.0.000000000 第二个非单词边界的位置,后面跟着三个数字
1.0.0.00000000 第三个非单词边界的位置,后面跟着三个数字
1.0.0.0.0000000 依次类推
1.0.0.0.0.000000
1.0.0.0.0.0.00000
1.0.0.0.0.0.0.0000
1.0.0.0.0.0.0.0.000 匹配结果
我们需要的是从后往前,每三位数字打一个点
需要加 $ 符号表示从后往前
现在我们匹配的是,以三个数字结尾的单词边界 10000000.000
var str = "10000000000"; var reg = /\B(?=\d{3}$)/g; console.log(str.replace(reg, ".")); // 10000000.000
想再往前面接着每三个数字打一个点,就是3的倍数6
var str = "10000000000"; var reg = /\B(?=(\d{3})+$)/g; console.log(str.replace(reg, ".")); // 10.000.000.000
{3} 是量词,+号也是量词,所以前面的 {3} 要加一个小括号和 + 隔开
(?=..) 都需要加上括号
+? 问号在量词后面的时候不需要加括号,代表非贪婪匹配
6. 统一空格
去掉多余的空格
var str = "Regexp remove string spaces"; var reg = /( )+/g; console.log(str.replace(reg, ' ')); // Regexp remove string spaces
附,面试题
1. 正则表达式实现aabb的形式变成bbaaa
2. 给 10000000000 每三个 0 打一个点变成 10.000.000.000
3. 字符串去重 aaaaaaabbbbcccc 变成 abc
4. 把 the-first-name 转换成小驼峰 theFirstName
5. 匹配结尾的数字
6. 统一空格
7. 判断字符串是不是由数字构成
8. 删除字符串中的空格
9. 身份证号匹配
10. 将字符串
"select student.*, result.* from student inner join result on student.id = result.studentid"
和"select * from student"
中的 student 替换成 key 值