文章目录
1. 字符匹配
1.1 两种模糊匹配
-
横向:字符串的长度不是固定的
示例:
/ ab{2,5}c/
-
纵向:字符串的内容不是确定的
示例:
/a[123]b/
1.2 字符组
- 范围
示例:
/[1-4d-g]/
- 排除
示例:
/[\^abc]/
- 常见简写
+\d
:等价于[0-9],表示一位数字-
\D
:等价于[^0-9],表示一位非数字的任意字符 -
\w
:等价于[0-9a-zA-z_],表示一位数字或大小写字母或下划线 -
\W
:等价于[^0-9a-zA-z_],表示一位非单词的任意字符 -
\s
:等价于[ \t\v\n\r\f],表示一位空白符 -
\S
:等价于[^ \t\v\n\r\f],表示一位非空白符,包括空格、水平制表符、垂直制表符、换行符、回车符、换页符 -
.
:等价于 [^\n\r\u2028\u2029],表示通配符。换行符、回车符、行分隔符和段分隔符
除外任意字符: [\d\D]、[\w\W]、[\s\S] 和 [^] 中任何的一个
[]
:只匹配一位字符
-
1.3 量词
-
?
:等价于{0,1},贪婪模式 -
+
:等价于{1,},贪 -
*
:等价于{0,},贪 -
??
:等价于{0,1}?,惰性模式 -
+?
:等价于{1,}?,惰 -
*?
:等价于{0,}?,惰贪婪模式:会匹配尽可能长的子串(默认)
惰性模式:会匹配尽可能短的子串(使用 ? 将贪婪匹配模式转化为惰性匹配模式)
1.4 多选分支
-
形式:
p1|p2|p3
- 其中 p1、p2 和 p3 是子模式
- 用
|
(管道符)分隔,表示其中任何之一
-
分支结构是惰性的,即当前面的匹配上了,后面的就不再尝试
示例:
/a|b|c/
1.5 案例
-
16 进制颜色
/^#([0-9a-fA-F]{6}|[0-9a-fA-F]{3})$/
-
匹配时间(24小时)
\/^([01][0-9]|2[0-3]):[0-5][0-9]$\/
/^(0?\d|1\d|2[0-3]):(0?\d|[1-5]\d$)/
-
日期
/\^[0-9]{1,4}-(0?[1-9]|1[012])-(0?[1-9]|[12][0-9]|3[01])$/
2. 位置匹配
2.0 修饰符
-
g:全局匹配(global)
-
i:忽略大小写(insensitive)
-
m:多行模式(multi line)
1.把
^
$
变成行开头和行结尾****
2.没有该修饰符会把多行文本当成一整个字符串
2.1 如何匹配位置
-
^ 和 $
-
^
(脱字符)匹配开头 -
$
(美元符号)匹配结尾 -
示例
var result = "hello".replace(/^|$/g, '#'); console.log(result); // => "#hello#"
-
-
\b 和 \B
-
\b
是单词边界1.具体就是 \w 与 \W 之间的位置
2.包括 \w 与 ^或$ 之间的位置 -
\B
就是 非单词边界- 在字符串中所有位置中,扣掉 \b,剩下的都是 \B 的。
-
示例
var result1 = "[JS] Lesson_01.mp4".replace(/\b/g, '#'); console.log(result1); // => "[#JS#] #Lesson_01#.#mp4#" var result2 = "[JS] Lesson_01.mp4".replace(/\B/g, '#'); console.log(result2); // => "#[J#S]# L#e#s#s#o#n#_#0#1.m#p#4"
-
-
(?=p) 和 (?!p)
-
(?=p)
- 其中 p 是一个子模式
- 即 p 前面的位置
- 或者说该位置后面的字符要匹配 p
- 正向先行断言(positive lookahead)
-
(?!p)
- 就是 (?=p) 的反面意思
- 表示该位置后面的字符不能匹配 p
- 负向先行断言(negative lookahead)
-
示例
var result1 = "hello".replace(/(?=l)/g, '#'); console.log(result1); // => "he#l#lo" var result2 = "hello".replace(/(?!l)/g, '#'); console.log(result2); // => "#h#ell#o#"
-
2.2 理解位置
-
把位置当作空字符
-
任意多个空字符与一个空字符效果等价
-
示例
var result = /(?=he)^^he(?=\w)llo$\b\b$/.test("hello"); console.log(result); // => true
TIP :把位置理解空字符,是对位置非常有效的理解方式
2.3 案例
-
数字千分位表示法
分析
1. 把对应位置替换为,
2. 当刚好有3倍数位时,不需要开头的逗号实现
1./(?=\d{3}$)/g
:匹配以三个数字结尾的位置
2./(?=(\d{3})+$)/g
:匹配以三n数字结尾的位置
3./(?!^)(?=(\d{3})+$)/g
:不匹配开头的位置
4./\B(?=(\d{3})+\b)/g
:支持非连续的数字字符串function format (num) { return num.toFixed(2).replace(/\B(?=(\d{3})+\b)/g, ",").replace(/^/, "$$ "); }; console.log( format(1888) ); // => "$ 1,888.00"
-
验证密码
密码规则
1.长度 6-12 位
2.由数字、小写字符和大写字母组成
3.至少包括 2 种字符- 分析:
- 不考虑第三条规则:
/^[0-9A-Za-z]{6,12}$/
- 必须包含数字或字母:
/(?=.*[0-9])|(?=.*[a-z])|(?=.*[A-Z])/
- 同时包含三种字符:
/(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])/
- 实现方法一:考虑所有情况
/((?=.*[0-9])(?=.*[a-z])|(?=.*[0-9])(?=.*[A-Z])|(?=.*[a-z])(?=.*[A-Z]))^[0-9A-Za-z]{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}$/
- 不能全是数字:
- 分析:
3. 括号的作用
3.1 分组与分支结构
- 分组
var regex = /(ab)+/g; var string = "ababa abbb ababab"; console.log( string.match(regex) ); // => ["abab", "ab", "ababab"]
- 分支结构
3.2 分组引用
-
提取数据
-
替换
- 字符串的replace方法第二参数可以使用 $1-$9 指代相应的分组
-
代码
var regex = /(\d{4})-(\d{2})-(\d{2})/; var string = "2017-06-12"; console.log( string.match(regex) ); console.log( regex.exec(string) ); // => ["2017-06-12", "2017", "06", "12", index: 0, input: "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" var result = string.replace(regex, "$2/$3/$1"); console.log(result); // => "06/12/2017" // 等价于如下形式 var result2 = string.replace(regex, function () { return RegExp.$2 + "/" + RegExp.$3 + "/" + RegExp.$1; }); var result3 = string.replace(regex, function (match, year, month, day) { return month + "/" + day + "/" + year; });
3.3 反向引用
-
概念
-
在正则本身里引用之前出现的分组
-
示例:
/\d{4}(-|\/|\.)\d{2}\1\d{2}/
\1
表示的引用之前的那个分组 (-|/|.),管它匹配到什么(比如 -),\1 都匹配那个同样的具体某个字符
-
-
括号嵌套
以左括号为准
-
引用不存在的分组
不报错,此时
\
起转义作用 -
分组后的量词
分组最终捕获的是最后一次的匹配
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
3.4 非捕获括号
(?:p)
和(?:p1|p2|p3)
- 既不在 API 里引用,也不在正则里反向引用
3.5 案例
-
字符串 trim 方法模拟
// 方法1:匹配到开头和结尾的空白符,替换为空白符 function trim1(str) { return str.replace(/^\s+ | \s+$/g, '') } // 方法2:匹配整个字符串,然后应用分组提取数据 function trim2(str) { // 使用了惰性匹配 *?,否则会匹配最后一个空格前的所有空格 return str.replace(/^\s*(.*?)\s*$/g, '$1') }
方法1 效率更高
-
首字母转大写
// 思路:匹配到首字母(开头或空格后的第一个字母)后转换 function titleize(str) { return str.toLowerCase().replace(/(?:^|\s)\w/g, (match) => match.toUpperCase()) // /(?:^|\s)\w/g 实际匹配到的是空格或起始位置加一个字母 // replace 第二个参数为一个回调函数 // 其第一个参数为匹配到的字串 // 对每一个匹配到的子字符串执行该回调函数 } console.log( titleize('my name is epeli') ); // => "My Name Is Epeli"
-
驼峰化
// 要求:-moz-transform => MozTransform // moz transform => MozTransform // moz_transform => MozTransform // 思路:匹配到开头和结尾的空白符,替换为空白符 function camelize(str) { return str.replace(/[-_\s]+([a-zA-z])?/g, // match => match[match.length-1].toUpperCase()) // (match, p1) => p1.toUpperCase()) // 以上方法无法处理到末尾的 - _ // '-moz__transform - -' => // 改进如下: (match, p1) => p1 ? p1.toUpperCase() : '') } console.log( camelize('-moz-transform- -') ); // => "MozTransform"
-
中划线化
// 驼峰化的逆过程 function dasherize (str) { return str.replace(/([A-Z])/g, '-$1').replace(/[-_\s]+/g, '-').toLowerCase(); } console.log( dasherize('MozTransform') ); // => "-moz-transform"
-
HTML 转义与反转义
// 将HTML特殊字符转换成等值的实体 function escapeHTML (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>Blah blah blah</div>') ); // => "<div>Blah blah blah</div>"; // 实体字符转换为等值的HTML。 function unescapeHTML (str) { var htmlEntities = { nbsp: ' ', lt: '<', gt: '>', quot: '"', amp: '&', apos: '\'' }; return str.replace(/\&([^;]+);/g, function (match, key) { if (key in htmlEntities) { return htmlEntities[key]; } return match; }); } console.log( unescapeHTML('<div>Blah blah blah</div>') ); // => "<div>Blah blah blah</div>"
-
匹配成对标签
// var regex = /<([^>]+)>[\d\D]*<\/\1>/; var string1 = "<title>regular expression</title>"; var string2 = "<p>laoyao bye bye</p>"; var string3 = "<title>wrong!</p>"; console.log( regex.test(string1) ); // true console.log( regex.test(string2) ); // true console.log( regex.test(string3) ); // false
4. 回溯法原理
4.1 没有回溯的匹配
- 正则:
/ab{1,3}c/
- 匹配:abbbc
- 过程
4.2 有回溯的匹配
- 正则:
/ab{1,3}c/
- 匹配:abbc
- 过程
- 分析:第5步匹配不成功,就退回到之前的状态(即第6步与第4步一样),第6步即回溯
4.3 常见回溯形式
- 回溯本质上就是深度优先搜索算法
- 贪婪量词
- “吃了的,有时候需要吐出来”
- 多个贪婪量词并存:“先下手为强”
- 惰性量词
- 不贪,但是也可能有回溯
- 你不能多拿,但我可以多给你
- 分支结构
- 惰性,存在回溯
- 讲究先入为主,一个不行再试下一个
5. 拆分
5.1 操作符优先级
- 分析
5.2 注意要点
- 管道符优先级最低,适当使用括号进行分组提高优先级
- 量词连缀
- 元字符转义
- 元字符,就是正则中有特殊含义的字符
- 总共19个:
^、$、.、*、+、?、|、\、/、(、)、[、]、{、}、=、!、:、-
5.3 案例
- 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])$/
6. 构建
6.1 平衡法则
- 匹配预期的字符串
- 不匹配非预期的字符串
- 可读性和可维护性
- 效率
6.2 构建前提
- 有选择的使用,而不是走向极端
- 不一定要设计复杂的正则:复杂的正则可以分解为几个小正则
6.3 准确性
-
匹配浮点数
要求:1.23、+1.23、-1.23
0、+10、-10
.2、+.2、-.2/^[+-]?(\d+\.\d+|\d+|\.\d+)$/
6.4 效率
- 使用具体型字符组来代替通配符,来消除回溯
- 使用非捕获型分组
- 独立出确定字符
- 提取分支公共部分
- 减少分支的数量,缩小它们的范围
7. 应用
7.1 字符串的四种操作
-
验证)
- 匹配的本质是查找
- 判断是否能匹配 成功即验证
- 实现:字符串方法(search/match)、正则表达式方法(test、exec)
- test方法最常用
var regex = /\d/; var string = "abc123"; console.log( !!~string.search(regex) ); console.log( regex.test(string) ); console.log( !!string.match(regex) ); console.log( !!regex.exec(string) );
-
切分(split)
- 把目标字符串分成一段一段
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"] // => ["2017", "06", "26"] // => ["2017", "06", "26"]
-
提取
- 方法:match/exec/test/search/replace
- match最常用
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"] console.log( regex.exec(string) ); // =>["2017-06-26", "2017", "06", "26", index: 0, input: "2017-06-26"] regex.test(string); console.log( RegExp.$1, RegExp.$2, RegExp.$3 ); // => "2017" "06" "26" var regex = /^(\d{4})\D(\d{2})\D(\d{2})$/; var string = "2017-06-26"; string.search(regex); console.log( RegExp.$1, RegExp.$2, RegExp.$3 ); // => "2017" "06" "26" var date = []; string.replace(regex, function (match, year, month, day) { date.push(year, month, day); }); console.log(date); // => ["2017", "06", "26"]
-
替换(replace)
replace
比你想象的更加强大
7.2 相关 API 注意要点
-
字符串正则方法参数
ES6 将 match()、replace()、search()和split() 四个方法,在语言内部全部调用RegExp的实例方法,从而做到所有与正则相关的方法,全都定义在RegExp对象上
-
match 返回值
- 无
g
,返回标准匹配格式(捕获组,额外属性index
和input
) - 有
g
,返回所有匹配内容(不捕获组,无额外属性) - 不匹配时返回
null
- 无
-
exec 比 match 更强大
exec 方法接着上一次匹配后继续匹配
其中正则实例 lastIndex 属性,表示下一次匹配开始的位置 -
修饰符 g,对 exex 和 test 的影响
字符串的四个方法,每次匹配时,都是从 0 开始的,即 lastIndex 属性始终不变
exec、test,当正则是全局匹配时,每一次匹配完成后,都会修改 lastIndex
-
split 相关注意事项
可以有第二个参数,表示结果数组的最大长度
正则使用分组时,结果数组中是包含分隔符的
var string = "html,css,javascript"; console.log( string.split(/(,)/), 4 ); // =>["html", ",", "css", ","]
-
强大的replace
强大之处在于第二个参数可以是字符串也可以是函数,同时可以进行替换操作
- 第二个参数为字符串
+ 如下字符串有特殊含义
- 第二个参数为函数
该回调函数有多个参数
会把匹配的字串替换为回调函数的返回值
非全局模式只替换第一个匹配到的字串
全局模式替换所有匹配的字串- 示例
// 把匹配到的字符串替换为第二个参数 var result1 = "2,3,5".replace(/(\d+),(\d+),(\d+)/, "$3=$1+$2"); // "5=2+3" var result2 = "2,3,5".replace(/(\d+)/g, "$&$&$&"); // "222,333,555" var result3 = "2+3=5".replace(/=/, "$&$`$&$'$&"); // "2+3=2+3=5=5" // 把匹配到的字串替换为回调函数的返回值 "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"]
- 第二个参数为字符串
-
正则构造函数和字面量正则
一般不推荐使用构造函数生成正则,而应该优先使用字面量。因为用构造函数会多写很多
\
字面量正则创建的RegExp对象共享一个单例 -
正则对象实例的属性
1.global:布尔值,是否全局
2.ignoreCase:布尔值,是否忽略大小写
3.multiline:布尔值是否多行匹配
4.lastIndex:默认0,下一次匹配开始位置
5.source:字符串,正则表达式源码(不包含修饰符)/<>?:"{}[],.\/\\/.source // "<>?:\"{}[],.\\/\\\\"
7.3 案例
-
正则测试器
<!DOCTYPE html> <html> <head> <title>正则测试器</title> <style> section { display: flex; flex-direction: column; justify-content: space-around; height: 300px; padding: 0 200px; } section * { min-height: 30px; } #err { color: red; } #result { line-height: 30px; } .info { background: #00c5ff; padding: 2px; margin: 2px; display: inline-block; } </style> </head> <body> <section> <div id="err"></div> <input id="regex" placeholder="请输入正则表达式"> <input id="text" placeholder="请输入测试文本"> <button id="run">测试一下</button> <div id="result"></div> </section> </body> <script> (function() { // 获取相应dom元素 var regexInput = document.getElementById("regex"); var textInput = document.getElementById("text"); var runBtn = document.getElementById("run"); var errBox = document.getElementById("err"); var resultBox = document.getElementById("result"); // 绑定点击事件 runBtn.onclick = function() { // 清除错误和结果 errBox.innerHTML = ""; resultBox.innerHTML = ""; // 获取正则和文本 var text = textInput.value; var regex = regexInput.value; if (regex == "") { errBox.innerHTML = "请输入正则表达式"; } else if (text == "") { errBox.innerHTML = "请输入测试文本"; } else { regex = createRegex(regex); if (!regex) return; var result, results = []; // 没有修饰符g的话,会死循环 if (regex.global) { while (result = regex.exec(text)) { results.push(result); } } else { results.push(regex.exec(text)); } if (results[0] == null) { resultBox.innerHTML = "匹配到0个结果"; return; } // 倒序是有必要的 for (var i = results.length - 1; i >= 0; i--) { var result = results[i]; var match = result[0]; var prefix = text.substr(0, result.index); var suffix = text.substr(result.index + match.length); text = prefix + '<span class="info">' + match + '</span>' + suffix; } resultBox.innerHTML = "匹配到" + results.length + "个结果:<br>" + text; } }; // 生成正则表达式,核心函数 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 regex; } catch (e) { errBox.innerHTML = "无效的正则表达式"; return false; } } })(); </script> </html>
-
效果图
-