正则
一本小书,看完非常有收获。下载地址
基础概念
结构 | 说明 |
---|---|
字面量 | 匹配一个具体字符,包括不用转义的和需要转义的。比如 a 匹配字符 “a”,又比如 \n 匹配换行符,又比如 . 匹配小数点 |
字符组 | 匹配一个字符,可以是多种可能之一,比如 【0-9】,表示匹配一个数字。 也有 \d 的简写形式 另外还有反义字符组,表示可以是除了特定字符之外任何一个字符,比如【^0-9】,表示一个非数字字符,也有 \D 的简写形式 |
量词 | 表示一个字符连续出现,比如 a{1,3} 表示 “a” 字符连续出现 3 次。另外还有常见的简写形式,比如 a+ 表示 “a” 字符连续出现至少一次 |
锚 | 匹配一个位置,而不是字符。比如 ^ 匹配字符串的开头,又比如 \b 匹配单词边界,又比如 (?=\d) 表示数字前面的位置。 |
分组 | 用括号表示一个整体,比如 (ab)+ ,表示 “ab” 两个字符连续出现多次,也可以使用非捕获分组 (?:ab)+。 |
分支 | 多个子表达式多选一,比如 abc|bcd,表达式匹配 “abc” 或者 “bcd” 字符子串。反向引用,比如 \2,表示引用第 2 个分组 |
操作符描述 | 操作符 | 优先级 |
---|---|---|
转义符 | \ | 1 |
括号和方括号 | (...)、(?:...)、(?=...)、(?!...)、[...] | 2 |
量词限定符 | {m} {m, n} {m,} ? * + | 3 |
位置和序列 | ^ $ \元字符一般字符 | 4 |
管道符(竖杠) | | | 5 |
常用的字符组合简写形式
一般来说,大写和小写的区别是,前者表示非。
\w :word 数字字母下划线
\W :非数字字母下划线
\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#"
模式匹配
(?=p),其中 p 是一个子模式,即 p 前面的位置,或者说,该位置后面的字符要匹配 p。
var result = "hello".replace(/(?=l)/g, "#");
console.log(result);
// => "he#l#lo"
而 (?!p) 就是 (?=p) 的反面意思,比如:
var result = "hello".replace(/(?!l)/g, "#");
console.log(result);
// => "#h#ell#o#"
二者的学名分别是 positive lookahead 和 negative lookahead。
中文翻译分别是正向先行断言和负向先行断言。
ES5 之后的版本,会支持 positive lookbehind 和 negative lookbehind。
具体是 (?<=p) 和 (?<!p)。
使用 ()
表示 捕获分组,使用 (?:)
表示 非捕获分组
常用正则
- 字符串 trim 方法模拟(第二种性能更好)
// 第一种
function trim(str) {
return str.replace(/^\s+|\s+$/g, "");
}
// 第二种
function trim(str) {
return str.replace(/^\s*(.*?)\s*$/g, "$1");
}
- 将每个单词的首字母转换为大写
function titleize(str) {
return str.toLowerCase().replace(/(?:^|\s)\w/g, function (c) {
return c.toUpperCase();
});
}
- 驼峰化
function camelize(str) {
return str.replace(/[-_\s]+(.)?/g, function (match, c) {
return c ? c.toUpperCase() : "";
});
}
- 中划线化
function dasherize(str) {
return str
.replace(/([A-Z])/g, "-$1")
.replace(/[-_\s]+/g, "-")
.toLowerCase();
}
- 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>";
反转义:
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
其他
贪婪量词:买衣服砍价。价格高了、便宜点、不行,再便宜点。 惰性量词:卖东西加价。给少了,再给点。 分支结构:货比三家。
要匹配字符串 "{3,5}",只需要把正则写成 /\{3,5}/ 即可
效率 tips:
- 使用具体型字符组来代替通配符,来消除回溯
- 使用非捕获型分组
- 独立出确定分组
- 提取分支公共部分
- 减少分支的数量,缩小它们的范围