正则表达式
正则表表达式是匹配模式, 要么匹配字符,要么匹配位置
正则表达式括号的作用
字符串中的正则和正则对象还是不太一样,需要注意一哈
需要注意: 验证正则和替换正则有可能范围不一样,具体需要看需求
括号提供了分组,以便于我们引用它
主要内容
- 分组和分支结构
- 捕获分组
- 反向引用
- 非捕获分组
- 相关案例
1. 分组和分支结构
这二者是括号最直接的作用也是最原始的功能
1.1 分组
/a+/
表示连续出现 a
,而需要连续出现 ab
,则需要 /(ab)+/
var reg = /(ab)+/g;
var string = 'ababa abbb ababab';
console.log(string.match(reg));
// ["abab", "ab", "ababab"]
1.2 分支结构
在多选分支结构(p1|p2)
中,括号的作用是提供了 子表达式的所有可能
比如匹配: i love javscript 和 i love regular expression
var reg = /^i love (javascript|regular expression)$/
console.log(reg.test('i love javascript'));
console.log(reg.test('i love regular expression'));
// true
// true
显然去掉了括号,就不是想要的结果了
2. 引用分组
一般情况下分组的作用是为了引用或者重复出现分组或者分支结构
在引用分组中,使用括号,我们可以进行数据提取,记忆更强大的替换操作
以日期为例, 假设格式为: yyyy-mm-dd
,先实现一个简单正则 /\d{4}-\d{2}-\d{2}/
加上括号:/(\d{4})-(\d{2})-(\d{2})/
2.1 提取数据
提取年月日
- 使用字符串 match 方法
var reg = /(\d{4})-(\d{2})-(\d{2})/;
var date = '2017-06-07';
console.log(date.match(reg));
// ["2017-06-07", "2017", "06", "07", index: 0, input: "2017-06-07", groups: undefined]
var reg = /(\d{4})-(\d{2})-(\d{2})/g;
var date = '2017-06-07';
console.log(date.match(reg));
// VM372:4 ["2017-06-07"]
match 返回一个数组,没有匹配到返回 null, 第一个元素是匹配的整体结果,然后是各个分组的匹配子串,匹配下标,输入文本(注意 match 是否有 修饰符 g 数组格式不同)
- 使用 正则对象的 exec 方法
var reg = /(\d{4})-(\d{2})-(\d{2})/;
var date = '2017-06-07';
console.log(reg.exec(date));
// VM375:3 (4) ["2017-06-07", "2017", "06", "07", index: 0, input: "2017-06-07", groups: undefined]
exec 和 match 方法结果差不多,exec 没有 修饰符 g 限制
- 使用 构造函数的全局属性 1− 1 − 9 获取
var reg = /(\d{4})-(\d{2})-(\d{2})/;
var date = '2017-06-07';
reg.test(date);// 正则操作即可
// reg.exec(date);
// date.match(reg);
console.log(RegExp.$1);
console.log(RegExp.$2);
console.log(RegExp.$3);
2.2 替换
比如,想把 yyyy-mm-dd 替换成 mm/dd/yyyy格式
var reg = /(\d{4})-(\d{2})-(\d{2})/;
var date = '2017-06-07';
date.replace(reg,'$3/$2/$1');
// "07/06/2017"
在replace中,第二个参数里 1, 1 , 2, $3 表示相应的分组
等价于如下形式:
var reg = /(\d{4})-(\d{2})-(\d{2})/;
var date = '2017-06-07';
date.replace(reg,function () {
return RegExp.$3 + '/' + RegExp.$2 + '/' + RegExp.$1;
});
// "07/06/2017"
也等价于:
var reg = /(\d{4})-(\d{2})-(\d{2})/;
var date = '2017-06-07';
date.replace(reg,function (match, year,month,day) {
console.log(match); // '2017-06-07' input 文本内容
return day + '/' + month + '/' + year;
});
// "07/06/2017"
3. 反向引用
反向引用概念只属于正则里,字符串中可以称作 api 分组引用
除了使用相应 API 来引用分组,也可以在正则本身里引用分组。但只能引用之前出现过的分组,即反向引用
以日期为例
比如: 洗衣歌正则支持如下格式:
2016-06-12
2016/06/12
2016.06.12
var reg = /\d{4}(-|\/|\.)\d{2}(-|\/|\.)\d{2}/
var string1 = '2016-06-12'
var string2 = '2016/06/12'
var string3 = '2016.06.12'
console.log(reg.test(string1)); // true
console.log(reg.test(string2)); // true
console.log(reg.test(string3)); // true
假设我们需要分隔符前后一致, 此时需要使用反向引用
var reg = /\d{4}(-|\/|\.)\d{2}\1\d{2}/
var string1 = '2016-06-12'
var string2 = '2016/06/12'
var string3 = '2016.06.12'
console.log(reg.test(string1)); // true
console.log(reg.test(string2)); // true
console.log(reg.test(string3)); // true
注意里面的 \1 , 表示的引用之前的那个分组(-|\/.), 只能引用前面的分组
3.1 括号套欠怎么办
以左括号(开括号)为准。比如:
var regexp = /^((\d)(\d(\d)))\1\2\3\4$/;
var string = '1231231233';
console.log(regexp.test(string)); // true
console.log(RegExp.$1); // VM118:5 123
console.log(RegExp.$2); // VM118:6 1
console.log(RegExp.$3); // VM118:7 23
console.log(RegExp.$4); // VM118:8 3
分析这个正则匹配模式:
- 第一个数字,第二个数字,第三个数字: 1,2,3
- \1 是第一个分组内容, 那么看第一个开括号(左括号)对应的分组是什么 123
- 接下来是 \2 , 那么找到第二个开括号,对应分组匹配内容是 1
- \3, 找到第三个开括号, 对应分组匹配内容是 23
- 最后是 \4, 找到 第四个开括号,对应分组的匹配内容是 3.
Note: 括号套欠(分组套欠) 只需要找开括号(左括号)即可找到对应的分组
3.2 \10 表示什么?
\10
: 正则表达式中 表示 第10个分组,比较罕见而已
var reg = /(1)(2)(3)(4)(5)(6)(7)(8)(9)(#) \10+/;
var string = '123456789# #####';
console.log(reg.test(string)); // true
3.3 引用不存在的分组会怎么样?
不存在就匹配对应的转义字符
反向引用是引用前面的分组,如果引用的分组不存在,此时正则不会报错,只是匹配反向引用的字符本身。如: \2 ,就匹配 “\2” ,匹配 2 的转义字符
var reg = /\1\2\3\4\5\6\7\8\9/;
var string = '\1\2\3\4\5\6\7\8\9';
console.log(reg.test(string));
console.log(string.split(''));
4. 非捕获分组
之前文中出现的分组,都会捕获它们匹配到的数据,以便后续引用,因此称为捕获型分组
如果只想要括号的原始功能,不进行捕获。即,既不在 API中进行引用,也不在 正则中反向引用,此时可以使用 非捕获分组 (?:p)
这个应该是 在分支结构分组 和 分组多次出现的时候会常用到 非捕获分组
var reg = /(?:ab)+/g;
var string = 'ababa abbb ababab';
console.log(string.match(reg)); // ["abab", "ab", "ababab"]
需要注意的是: 正则修饰符 g 对 match 捕获型分组和非捕获型分组都是有影响的
5. 相关案例
5.1 字符串 trim 方法模拟
trim 方法去掉字符串开头和结尾的空白符号
var reg = /^\s+|\s+$/g;
var string = ' 123213 123123 dsfsdf \r\n';
console.log('origin--', string.length);
console.log('trimmed--', string.replace(reg,'').length);
两种实现思路:
- 匹配开头和结尾的空白符, 然后替换成空格
function trim (str) {
return str.replace(/^\s+|\s+$/g, '');
}
console.log(trim(' foobar '))
- 匹配整个字符串, 然后用引用来提取出相应的数据
function trim(str) {
return str.replace(/^\s*(.*?)\s*$/g, '$1');
}
console.log(trim(' foobar '))
这里使用了惰性匹配 *? , 不然会匹配最后一个空格之前的所有空格,当然前者效率高。应该说 模糊的越多,效率越低,尤其使用 通配符 .
5.2 将每个单词首字母大写
‘hello world’
var string = 'hello world';
var reg = /\b\w/g;
console.log(string.replace(reg, function (match) {
// console.log('match', match);
return match.toUpperCase();
}));
another complement
思路: 找到每个单词首字母,然后toUpperCase
function titleize (str) {
return str.toLowerCase().replace(/(?:^|\s)\w/g,function (c) {
return c.toUpperCase();
})
}
console.log(titleize('my name is epeli'))
5.3 驼峰化
pascal case: 每个单词首字母大写其余字母小写
‘hello-world-lala’
// 分割边界处的 字母大写
function toPascalCase(str) {
return str.toLowerCase().replace(/\b\w/g,function (match,$1) {
return match.toUpperCase();
}).split('-').join('');
}
console.log(toPascalCase('hello-world-lala'));
another complement
这个方法非常好,不仅可以 pascal camel 还可以有 trim 效果
// 真简洁
function camelize (str) {
return str.replace(/[-_\s]+(.)?/g, function (match, c) {
return c ? c.toUpperCase() : '';
})
}
console.log(camelize('-moz-transform')); //MozTransform
第一个方法使用的是位置操作,第二个方法使用的是 实体字符,所以第二个方法可以直接替换掉分隔符。
5.4 中划线
pascal case to 中划线格式
// 定位到大写字母位置
// 替换
function toStrike (str) {
return str.replace(/[A-Z]/g, function (match) {
return '-' + match.toLowerCase();
})
}
console.log(toStrike('MozTransform'));
function dashrize (str) {
return str
// 将 大写字母前面添加 -
.replace(/([A-Z])/g, '-$1')
// 将 空白字符替换成 中划线
.replace(/[-_\s]+/g,'-')
// 全变成小写
.toLowerCase()
}
console.log(dashrize('MozTransform'));
5.5 html 转义和 反转义
// ¢£hello¥€©®<>
function escapeHTML (str) {
var escapeChars = {
'¢': 'cent',
'£': 'pound',
'¥': 'yen',
'€': 'euro',
'©': 'copy',
'®': 'reg',
'<': 'lt',
'>': 'gt',
'"': 'quot',
'\'': '#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 = {
'cent': '¢',
'pound': '£',
'yen': '¥',
'euro': '€',
'copy': '©',
'reg': '®',
'lt': '<',
'gt': '>',
'quot': '"',
'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>
5.6 匹配成对标签
要求匹配:<div>regular expression</div><p>hello javascript</p>
var reg = /<([^>]+)>[\d\D]*<\/\1>/;
var string1 = '<div>regular expression</div>'
var string2 = '<p>hello javascript</p>'
var string3 = '<title>wrong!</div>'
console.log(reg.test(string1)); // true
console.log(reg.test(string2)); // VM338:6 true
console.log(reg.test(string3)); // VM338:7 false
其中 使用了 反向引用,为了前后保持一致;[\d\D]
匹配任意字符