- 进修正则表达式
- 编译正则表达式
- 使用正则表达式进行捕获
- 使用常见的正则表达式
在主流的 JavaScript 库中, 可以看到开发者大量使用正则表达式解决各种任务。
- 操作 HTML 节点中的字符串。
- 使用 CSS 选择器表达式定位部分选择器。
- 判断一个元素是否具有指定的类名(class)。
- 输入校验。
- 其他人物。
通正则表达式需要大量的练习。可以使用网站如 JSBin 很方便地练习。有许多网站致力于测试正则表达式: JavaScript 正则表达式测试 和 egex101。regex101 对于新手是特别有用的网站,可以自动生成正则表达式的解读。
当正则表达式在开发环境是明确的,推荐优先使用字面量语法;当需要在运行时动态创建字符串来构建正则表达式时,则使用构造函数的方式。
除了表达式本身,还可以使用 5 个修饰符:
- i —— 对大小写不敏感。
- g —— 查找所有匹配项,在查找到第一个匹配时不会停止,会继续查找下一个匹配项。
- m —— 允许多行匹配,对获取 textarea 元素的值很有用。
- y —— 开启粘连匹配。正则表达式执行粘连匹配时试图从最后一个匹配的位置开始。
- u —— 允许使用 Unicode 点转义符(\u{...})。
- 精确匹配:
/test/.test('test')
- 匹配字符集:
/[abc]/
匹配 a、b、c 任意/^abc/
除 abc 以外/[a-z]/
a-z 之间所有字符
- 起止符号:
/^test/
匹配字符串的开始(注意,这只是字符的重载,尖角号^
还可以表示非)/test$/
匹配字符串的结束,同时使用^
与$
表示匹配整个字符串
- 重复出现:
/aaaa/
匹配 4 个连续的字符 a- 匹配任意数量的相同的字符
- 指定可选字符(可以出现 0 次或 1 次),在字符后添加 ? ,例如,
/t?est/
可以同时匹配 test 与 est。 - 指定字符必须出现 1 次或多次,使用 + ,如
/t+est/
可匹配 test、ttest、 tttest 等。 - 指定字符出现 0 次或 1 次或多次,使用 * ,如
/t*est/
匹配 test、ttest、tttest 以及 est。 - 指定重复次数,使用括号指定重复次数,例如
/a{4}/
,匹配 4 个连续的字符 a。 - 指定循环次数的范围,使用逗号分隔,例如
/a{4,10}/
匹配 4~10 个连续的字符 a。 - 指定开放区间,省略第 2 个值,保留逗号。例如
/a{4,}/
匹配 4 个或更多个连续的字符 a。 - 些运算符都可以是贪婪的或非贪婪的。默认是贪婪模式,可以匹配所有可能的字符。在运算符后添加
?
,例如a+?
,使得运算符为非贪婪模式,只进行最小限度的匹配。
- 指定可选字符(可以出现 0 次或 1 次),在字符后添加 ? ,例如,
- 预定义字符集
\t
水平制表符\b
空格\r
回车符\f
换页符\h
换行符\cA:\cZ
控制字符\u0000:\uFFFF
十六进制 Unicode 码\x00:\xFF
十六进制 ASCII 码.
匹配除换行字符(\n
、\r
、\u2028
和\u2029
)之外的任意字符\d
匹配任意十进制数字,等价于[0-9]\D
匹配除了十进制数字外的任意字符,等价于[^0-9]\w
匹配任何字母、数字和下划线,等价于[A-Za-z0-9_]\W
匹配除了字母、数字和下划线之外的字符,等价于[^a-za-z0-9_]\s
匹配任意空白字符(包括空格、制表符、换页符等)\S
匹配除空白字符外的任意字符\b
匹配单词边界\B
匹配非单词边界(单词内部)
- 分组
/(ab)+/
匹配一个或多个连续的ab
- 或操作符(OR)
/a|b/
可以匹配 a 或者 b/(ab)+|(cd)+/
可以匹配一个或多个 ab 或 cd
- 反向引用
^([dtn])a\1
匹配任意从 d 或 t 或 n 开始的,连续有 a 的字符串,连续匹配第一个分组中捕获的内容,字符\1
表示直到相等才匹配。/<(\w+)>(.+)<\/\1>/
匹配<strong>whatever</strong>
- 编译
- 发生在正则表达式被创建的时期
- 执行
- 发生在使用编译之后的正则表 达式进行匹配字符串的时期
在运行时编译一个供稍后使用的正则表达式
<div class="samurai ninja"></div>
<div class="ninja samurai"></div>
<div></div>
<span class="samurai ninja ronin"></span>
<script>
function findClassInElements(className, type) {
const elems = document.getElementsByTagName(type || "*");
const regex = new RegExp("(^|\\s)" + className + "(\\s|$)");
const results = [];
for (let i = 0, length = elems.length; i < length; i++) {
if (regex.test(elems[i].className)) {
results.push(elems[i]);
}
}
return results;
}
console.log(findClassInElements("ninja", "div").length === 2);
console.log(findClassInElements("ninja", "span").length === 1);
console.log(findClassInElements("ninja").length === 3);
</script>
<div id="square" style="transform:translateY(15px);"></div>
<script>
function getTranslateY(elem){
const transformValue = elem.style.transform;
if(transformValue){
const match = transformValue.match(/translateY\(([^\)]+)\)/);
return match ? match[1] : "";
}
return "";
}
const square = document.getElementById("square");
console.log(getTranslateY(square) === "15px");
</script>
全局匹配与局部匹配查找时的区别:
const html = "<div class='test'><b>Hello</b> <i>world!</i></div>";
const results = html.match(/<(\/?)(\w+)([^>]*?)>/);
assert(results[0] === "<div class='test'>", 'The entire match.');
assert(results[1] === '', 'The (missing) slash.');
assert(results[2] === 'div', 'The tag name.');
assert(results[3] === " class='test'", 'The attributes.');
const all = html.match(/<(\/?)(\w+)([^>]*?)>/g);
assert(all[0] === "<div class='test'>", 'Opening div tag.');
assert(all[1] === '<b>', 'Opening b tag.');
assert(all[2] === '</b>', 'Closing b tag.');
assert(all[3] === '<i>', 'Opening i tag.');
assert(all[4] === '</i>', 'Closing i tag.');
assert(all[5] === '</div>', 'Closing div tag.');
如果捕获结果对我们来说很重要,那么可以在全局匹配中使用正则表达式的 exec 方法。可多次对一个正则表达式调用 exec 方法,每次调用都可以返回下一个匹配的结果。
const html = "<div class='test'><b>Hello</b> <i>world!</i></div>";
const tag = /<(\/?)(\w+)([^>]*?)>/g;
let match,
num = 0;
while ((match = tag.exec(html)) !== null) {
assert(match.length === 4, 'Every match finds each tag and 3 captures.');
num++;
}
assert(num === 6, '3 opening and 3 closing tags found.');
该方法保留前一次调用的结果,这样后续每次调用都可以使用全局匹配。每次调用返回的都是下一次的匹配及捕获结果。通过使用 match
或 exec
方法,可以精确查找匹配项(和捕获结果)。但是如果希望在正则表达式中引用某个捕获的内容,需要进一步研究。
使用反向引用匹配 HTML 标记的内容
const html = "<b class='hello'>Hello</b> <i>world!</i>";
const pattern = /<(\w+)([^>]*)>(.*?)<\/\1>/g;
let match = pattern.exec(html);
assert(
match[0] === "<b class='hello'>Hello</b>",
'The entire tag, start to finish.'
);
assert(match[1] === 'b', 'The tag name.');
assert(match[2] === " class='hello'", 'The tag attributes.');
assert(match[3] === 'Hello', 'The contents of the tag.');
match = pattern.exec(html);
assert(match[0] === '<i>world!</i>', 'The entire tag, start to finish.');
assert(match[1] === 'i', 'The tag name.');
assert(match[2] === '', 'The tag attributes.');
assert(match[3] === 'world!', 'The contents of the tag.');
但是 replace
最重要的特性是不仅支持替换值,而且支持替换函数作为参数。当第 2 个参数是函数时,对每一个所匹配到的值都会调用一遍(全局匹配会返回匹配到的全部内容)。
- 全文匹配。
- 匹配时的捕获。
- 在原始字符串匹配的索引。
- 源字符串。
- 从函数返回的值作为替换值。
将短横线连接的字符串转换为“驼峰式”字符串
function upper(all, letter) {
return letter.toUpperCase();
}
console.log(
'border-bottom-width'.replace(/-(\w)/g, upper) === 'borderBottomWidth'
);
一种查询字符串压缩技术
例如,假设需要将查询字符串转换为符合我们所需格式的字符串,需要将查询字符串
foo=1&foo=2&blah=a&blah=b&foo=3
转换为
foo=1,2,3&blah=a,b"
function compress(source) {
const keys = {}; // 存储目标 key
source.replace(/([^=&]+)=([^&]*)/g, function(full, key, value) {
// 提取键值对信息
keys[key] = (keys[key] ? keys[key] + ',' : '') + value;
return '';
});
const result = [];
for (let key in keys) {
result.push(key + '=' + keys[key]); // 收集key信息
}
return result.join('&'); // 使用&符号链接结果
}
assert(
compress('foo=1&foo=2&blah=a&blah=b&foo=3') === 'foo=1,2,3&blah=a,b',
'Compression is OK!'
);
示例代码首先声明变量 key
,用于存储在查询字符串中匹配的 key
和 value
。然后对 source
字符串调用 replace
方法,传入匹配键值对(Hash 结构)的正则表达式,并捕获对应的键值对。同时传入一个函数,接收 3 个参数:full
、key
、value
。捕获的值存储在变量 key
中供后续引用。注意到,返回空字符串是因为我们不关心对 source
字符串的替换,只是使用 replace
方法的副作用,而不关心 replace
方法的执行结果。
当 replace
执行返回时,声明一个数组 result
,遍历对象 keys
的属性,将 keys
的属性以及对应的属性值使用“=”符号链接,依次 push
到 result
数组中。最后,使用&
连接 result
数组中的字符串,并返回结果。
匹配所有字符,包括换行符
const html = '<b>Hello</b>\n<i>world!</i>';
assert(
/.*/.exec(html)[0] === '<b>Hello</b>',
"A normal capture doesn't handle endlines."
);
assert(
/[\S\s]*/.exec(html)[0] === '<b>Hello</b>\n<i>world!</i>',
'Matching everything with a character set.'
);
assert(
/(?:.|\s)*/.exec(html)[0] === '<b>Hello</b>\n<i>world!</i>',
'Using a non-capturing group to match everything.'
);
const text = '\u5FCD\u8005\u30D1\u30EF\u30FC';
const matchAll = /[\w\u0080-\uFFFF_-]+/;
assert(text.match(matchAll), 'Our regexp matches non-ASCII!');
在 CSS 选择器中匹配转义字符:
const pattern = /^((\w+)|(\\.))+$/;
const tests = [
'formUpdate',
'form\\.update\\.whatever',
'form\\:update',
'\\f\\o\\r\\m\\u\\p\\d\\a\\t\\e',
'form:update'
];
for (let n = 0; n < tests.length; n++) {
assert(pattern.test(tests[n]), tests[n] + ' is a valid identifier');
}
- 正则表达式是一个强大的工具,贯穿于现代JavaScript开发中,几乎可用于任意类型的匹配,主要取决于如何在各方面使用正则表达式。本章所介绍的概念能够很好地让你理解高级正则表达式,受益于正则表达式,可以很自信地面对任何具有挑战性的代码。
- 创建正则表达式可以使用正则表达式字面量
(/test/)
或正则构造函数RegExp(new RegExp("test"))
。对于在开发环境明确的推荐使用正则字面量,在运行时则推荐使用构造函数。 - 每个正则都可以使用 5 个标识符:i——大小写敏感,g——全局匹配,m——支持多行匹配,y——支持粘连匹配,u——支持 Unicode 转义。在正则后面添加标志位如
/test/ig
,或作为构造函数的第 2 个参数传入,如new RegExp("test", "i")
。 - 使用
section
指定一组待匹配的字符。 - 使用
^
表示匹配字符串的起始位置,$
表示字符串的结束位置。 - 使用
?
表示可选项,+
表示必须出现 1 次或多次,*
表示可以出现 0 次、1 次或多次。 - 使用
.
匹配任何字符。 - 使用反斜线
(\)
转义特殊字符。 - 使用圆括号
()
对多个术语分组,使用竖线|
表示 或。 通过反斜线+数字如(\1
,\2
, 等),可以对匹配的字符串进行反向引用。每个字符串可以使用match
函数,match
函数的传入参数是正则表达式,返回值是匹配到的全部字符串以及全部捕获。使用replace
函数,可以对固定字符串进行替换。