正则表达式
正则表达式是匹配模式,要么匹配字符,要么匹配位置。
正则表达式编程
什么叫做知识,能知道我们实践的东西才叫做知识。
我们在实践中如何使用正则呢? 要点在哪里?
主要内容
- 正则表达式的四种操作
- 相关 API 注意点
- 真实案例
1. 正则表达式的四种操作
正则表达式是匹配模式,不管如何使用正则,首先都要进行匹配。
在这一操作之后才有: 验证,切分,提取,替换。
1.1 验证
验证是正则表达式的最直接应用,比如表单验证。
我们首先说清楚匹配概念, 所谓匹配就是看目标字符串中是否有满足匹配模式的子串。因此匹配就是查找。
有没有匹配,是否匹配上,判断是否的操作,称作 “验证”
这里举一个例子: 比如,判断一个字符串中是否含有数字。
- 使用 search
- 使用 test
- 使用 match
- 使用 exec
var reg = /\d/;
var string = 'abc123';
console.log(!!~string.search(reg)); // true
console.log(reg.test(string)); // true
console.log(!!string.match(reg)); // true
console.log(!!reg.exec(string)); // true
-1 的位运算 ‘异或运算’
~
结果是 0
其中最常用的是 test
1.2 切分
匹配上了,就可以进行一些操作,比如切分
所谓 ‘切分’, 就是将目标字符串,切成一段一段,在js 中使用 split 方法
比如,目标字符串 html,css,javadcript
安逗号切分
var reg = /,/;
var string = 'html,css,javadcript';
console.log(string.split(',')); //["html", "css", "javadcript"]
又比如,如下日期格式:
2017/06/26
2017.06.26
2017-06-26
var reg = /\D/;
console.log('2017/06/26'.split(reg));
console.log('2017.06.26'.split(reg));
console.log('2017-06-26'.split(reg));
// (3) ["2017", "06", "26"]
// VM186:3 (3) ["2017", "06", "26"]
// VM186:4 (3) ["2017", "06", "26"]
1.3 提取
虽然整体匹配了,但是有时候需要提取部分匹配的数据。
此时正则需要使用分组引用(分组捕获)功能。
这里以日期为例:
- match
- exec
- test
- search
- repalce
var reg = /^(\d{4})\D(\d{2})\D(\d{2})$/;
var string = '2017-06-26';
console.log(string.match(reg));
console.log(reg.exec(string));
reg.test(string);
console.log(RegExp.$1,RegExp.$2,RegExp.$3); // 注意最多 $9,跟反向引用没关系,反向引用 无限个
string.search(reg);
console.log(RegExp.$1,RegExp.$2,RegExp.$3); // 注意最多 $9,跟反向引用没关系,反向引用 无限个
var date = [];
string.replace(reg, function (match,year,month,day) {
date.push(year,month,day);
})
其中最常用 match
1.4 替换
找,往往不是目的,最常用的应该是 替换了,在 js 中使用 replace 进行替换
比如把 日期格式 yyyy-mm-dd
替换成 yyyy/mm/dd
var reg = /-/g;
console.log('2018-05-04'.replace(reg, '/'))
replace 功能超级强大。
2. 相关 API 注意点
关于正则的使用, js 中 共有 6个方法, 字符串实例 4个, 正则实例 2 个:
String#search
String#match
String#replace
String#split
RegExp#test
RegExp#exec
2.1 search 和 match 的参数问题
字符串实例的 4个方法都支持正则和字符串(简单具体字符匹配使用字符串;涉及到字符类时候一般使用正则表示)
但 search 和 match 会把字符串转换成正则。
2.2 match 返回结果的格式问题
match 返回结果的格式和正则对象是否有 修饰符 g 有关
var string = '2018.05.04';
var reg1 = /\b(\d+)\b/;
var reg2 = /\b(\d+)\b/g;
console.log(string.match(reg1));
console.log(string.match(reg2));
// ["2018", "2018", index: 0, input: "2018.05.04", groups: undefined]
// VM236:5 (3) ["2018", "05", "04"]
没有 g 返回的是标准格式,即数组的第一个元素整体匹配内容,接下来是分组捕获的内容,然后是 整体匹配的第一个下标,最后是输入的目标字符串。
有 g 只返回所有的分组捕获内容
当没有匹配的时候,有无 g 都会返回 null.
2.3 exec 比 match 更强大
正则没有g 时候, match 返回信息较多,但是 有 g 后,就没有关键信息 index 了。
而exec 方法可以解决这个问题,可以 根据上一次的匹配继续匹配
var string = '2018.05.04';
var reg = /\b(\d+)\b/g;
console.log(reg.exec(string));
console.log(reg.lastIndex);
console.log(reg.exec(string));
console.log(reg.lastIndex);
console.log(reg.exec(string));
console.log(reg.lastIndex);
console.log(reg.exec(string));
console.log(reg.lastIndex);
console.log(reg.exec(string));
console.log(reg.lastIndex);
//
// (2) ["2018", "2018", index: 0, input: "2018.05.04", groups: undefined]
// VM242:6 4
// VM242:7 (2) ["05", "05", index: 5, input: "2018.05.04", groups: undefined]
// VM242:8 7
// VM242:9 (2) ["04", "04", index: 8, input: "2018.05.04", groups: undefined]
// VM242:10 10
// VM242:11 null
// VM242:12 0
// VM242:13 (2) ["2018", "2018", index: 0, input: "2018.05.04", groups: undefined]
// VM242:14
exec 只匹配一次,并且记录下次匹配开始的位置
正则实例的lastIndex属性 表示下一次匹配的开始位置(初始值 为0)
我们在使用 exec 的时候常结合 while 来使用
var string = '2018.05.04';
var reg = /\b(\d+)\b/g;
while(result = reg.exec(string)) {
console.log(result, reg.lastIndex);
}
//
// (2) ["2018", "2018", index: 0, input: "2018.05.04", groups: undefined] 4
// VM244:5 (2) ["05", "05", index: 5, input: "2018.05.04", groups: undefined] 7
// VM244:5 (2) ["04", "04", index: 8, input: "2018.05.04", groups: undefined] 10
这个方法不错,记录下次继续匹配的位置,匹配不到返回 null
2.4 修饰符 g 对 exec 和 test 影响
上述的正则实例 的lastIndex属性,表示尝试匹配的时候,从lastIndex 位置开始进行匹配。
字符串的4个方法每次匹配的时候,都是从 0开始的,即lastIndex 属性始终不变
而正则的 exec , test 方法,当正则匹配全局的时候,每次匹配完成都会 修改 lastIndex。例如:
var reg = /a/g;
console.log(reg.test('a'),reg.lastIndex);
console.log(reg.test('aba'),reg.lastIndex);
console.log(reg.test('ababc'),reg.lastIndex);
// true 1
// VM251:3 true 3
// VM251:4 false 0
如果没有 g ,都是从字符串的 0 index 位置开始尝试匹配(正则实例lastIndex属性不会被改变,始终为0)
var reg = /a/;
console.log(reg.test('a'),reg.lastIndex);
console.log(reg.test('aba'),reg.lastIndex);
console.log(reg.test('ababc'),reg.lastIndex);
// true 0
// VM253:3 true 0
// VM253:4 true 0
2.5 test 整体匹配的时候需要使用 ^ 和 $
- test 匹配目标字符串的部分子串
- 整体匹配需要加上开头和结尾 锚字符
2.6 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.7 replace 功能很强大
- exec 功能超级强大
- 我认为 replace 强大且常用
Syntax: str.replace(regexp|substr, newSubstr|function)
当第二个参数 为字符串的时候,,如下的字符有特殊含义:
使用 美元符号的时候需要对正则先分组,然后才能使用$number 进行引用
$n,$&, $
,
$`
$n: 表示分组引用的东西(用的比较多)
$`: inserts the portion of the string that precedes the matched substring.(插入匹配子字符串之前的字符串部分)
$’: Inserts the portion of the string that follows the matched substring.(插入匹配子字符串后面的字符串部分)
$&: Inserts the matched substring.(插入匹配的子字符串)
- 这个我理解是, $&表示匹配字符和其位置,在这个匹配的子串位置两侧可以插入东西
2.7.1 ‘2,3,5’ 变成 ‘5=2+3’
$n:引用分组
var reg = /(\d+),(\d+),(\d+)/;
console.log('2,3,5'.replace(reg,'$3 = $1 + $2')); // 5 = 2 + 3
2.7.2 ‘2,3,5’ 变成 ‘222,333,555’
$&: Inserts the matched substring
var reg = /\d+/g;
console.log('2,3,5'.replace(reg,'$&$&')); // 22,33,55
或者
// 注意这里使用的时候需要分组
var reg = /(\d+)/g;
// 下面这样也能够实现
console.log('2,3,5'.replace(reg,'$1$1')); // 22,33,55
2.7.3 ‘2+3=5’ 变成 ‘2+3=2+3=5=5’
’
var reg = /=/;
console.log('2+3=5'.replace(reg, "$&$`$&$'$&")); // 2+3=2+3=5=5
2.7.4 当第二个参数为 function 时候
回调函数中是形参可以不使用 2.
"1234 2345 3456".replace(/(\d)\d{2}(\d)/g, function (match, $1, $2, index, input) {
console.log([match, $1, $2, index, input]);
})
// (5) ["1234", "1", "4", 0, "1234 2345 3456"]
// VM321:2 (5) ["2345", "2", "5", 5, "1234 2345 3456"]
// VM321:2 (5) ["3456", "3", "6", 10, "1234 2345 3456"]
index和input 我用的少,还不知道有这个操作呢,以后会注意的
2.8 使用构造函数时候需要注意
一般不推荐使用构造函数生成正则,优先使用字面量形式
一般就是正则构造函数 first param 是字符串,转义字符需要转义, 如: new RegExp('\\d','g')
,
容易错误写法: new RegExp('\d','g')
。
2.9 修饰符
ES5 中,共 3 个修饰符
g 全局匹配, 即匹配出所有子串, global
i 匹配过程中忽略字母大小写, ignoreCase
m 多行匹配, 只影响 ^ 和 $ ,行首和行尾,multiline
正则也有只读属性:
var reg = /\w/img;
console.log(reg.ignoreCase); // true
console.log(reg.multiline); // true
console.log(reg.global); // true
3. 真实案例
3.1 使用构造函数生成正则表达式
正则表达式主体不确定的时候一般需要使用构造函数拼接参数来生成正则
一般,我们优先使用字面量创建正则但是在正则表达式主题不确定的时候,此时可以使用构造函数来创建。模拟 getElementsByClassName方法。
实现思路:
- 比如要获取 clasName 为 ‘high’ 的dom 元素
- 首先生成正则:
/(^|\s)high(\s|$)/
- 然后再用其逐一验证页面上的所有 dom 元素的类名,拿到满足匹配的元素即可
<p class="high">1122</p>
<p class="high">112233</p>
<script>
getElementsByClassName(className) {
var elements = document.getElementsByTagName('*');
var result = [];
var reg = new RegExp('(^|\\s)'+ className +'(\\s|$)');
for (var i = 0;i < elemnts.length; i++) {
if (reg.test(elements[i].className)) {
result.push(elements[i]);
}
}
return result;
}
var highs = getElementsByClassName('high');
highs.forEach(function (v,k) {
v.style.color = 'red';
});
</script>
3.2 使用字符串保存数据
这中实现判断数据类型的方式不错
in a general, 我们都愿意使用数组保存数据,但是有的框架中使用字符串保存数据。
使用时候,仍需要将字符串切分成数组。
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])); // true
console.log(utils.isString('123')); // VM486:1 true
3.3 if 语句中 使用这则代替 &&
比如,模拟ready 函数,即加载完毕后再执行回调(不兼容ie)
The readyState of a document can be one of following:
- loading :The document is still loading.
- interactive: The document has finished loading and the document has been parsed but sub-resources such as images, stylesheets and frames are still loading.
- complete: The document and all sub-resources have finished loading. The state indicates that the load event is about to fire.
When the value of this property changes a readystatechange event fires on the document object.
var readyRE = /complete|loading|interactive/;
function ready(callback) {
if (readyRE.test(document.readyState) && document.body) {
callback();
} else {
document.addEventListener('DOMContentLoaded', function () {
callback();
}, false);
}
}
ready(function () {
alert('加载完毕!');
});
3.4 使用强大的 replace
我们有时候使用 replace 不是为了替换而是用来拿到其匹配到的信息做文章。
这里以查询字符串(querystring) 压缩技术为例。
function compress (source) {
var keys = {};
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('&');
}
console.log(compress('a=1,b=2,a=3,c=4')); //"a=1,3&b=2&c=4"
或
使用 exec 来处理
function compress (source) {
var keys = {};
var reg = /([^&=])=([^&])/g;
var arr = [];
while (result = reg.exec(source)) {
keys[result[1]] = (keys[result[1]] ? keys[result[1]] + ',' : '') + result[2];
}
var result = [];
for(var key in keys ) {
result.push(key + '=' + keys[key]);
}
return result.join('&');
}
console.log(compress('a=1,b=2,a=3,c=4')); //"a=1,3&b=2&c=4"
3.5 综合运用
实现一个正则测试器:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<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-color: #00c5ff;
padding: 2px;
margin: 2px;
display: inline-block;
}
</style>
</head>
<body>
<section>
<div id="err"></div>
<input type="text" id="regex" placeholder="请输入正则表达式">
<input type="text" placeholder="请输入测试文本" id="text">
<button id="run">测试一下</button>
<div id="result"></div>
</section>
<script>
(function () {
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.replace(/^\s+|\s+$/g, '');
if (text === '') {
errBox.innerHTML = '请输入文本';
} else if (regex === '') {
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--) {
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 + '个结果:<bar>' + text;
};
// 生成正则表达式
function createRegex(regex) {
try {
if (regex[0] == '/') {
regex = regex.split('/');
regex.shift();
var flags = regex.pop();
// Qs
regex = regex.join('/')
regex = new RegExp(regex, flags);
} else {
regex = new RegExp(regex, 'g');
}
return regex;
} catch (e) {
errBox.innerHTML = '无效的正则表达式';
return false;
}
}
}());
</script>
</body>
</html>
supplement
exec g 修饰符貌似是用来改变 lastIndex 的
字符串 4 个关于正则的 API
- search 用来查找 字符的位置(index)
- match 用来匹配出字符内容
- replace 用来替换匹配出的字符
- split 用来在对应字符位置进行分割字符串