正则
正则表达式是对字符串执行模式匹配的强大工具。
它是利用事先定义好的一些特定字符或特定字符的组合,组成一个“规则字符串”,表达对字符串的一种过滤逻辑。常用于检查字符串是否含有某个内容,或者替换字符串中某些内容,或者从字符串中取出一段内容。
正则表达式的用途广泛,各编程语言之中都有正则表达式的身影,使用方式又有所不同,本文档只专注于 JavaScript 中正则表达式的使用。
特点
- 灵活性、逻辑性和功能性非常的强;
- 可以迅速地用极简单的方式达到字符串的复杂控制;
- 对于刚接触的人来说,比较晦涩难懂;
创建方式
在 JavaScript 中,正则表达式通过内置的“regExp”类的对象来实现,并与字符串集成。两种创方式:
- 字面量形式创建
/pattern/attributes
- 构造函数的方式创建
new RegExp(pattern, attributes);
参数
- pattern:模式,可以是字符串或其他正则表达式。用于指定匹配的内容
- attribute:属性,可选值: "g"、"i" 、"m"、"u" 和 "y",分别用于指定全局匹配、区分大小写的匹配、多行匹配、开启完整的 unicode 支持和粘滞模式。注意: "u" 在 Firfox 和 Edge 浏览器中缺乏支持
方式选择
一般情况下通过字面量形式创建正则表达式,如果正则匹配的模式是一个变量或者表达式的返回值, 那么需要使用构造函数的方式创建正则。
使用 /.../ 创建正则,里面的内容(...)必须是确切的值。
例如:
// 1. 字面量形式创建 /pattern/attributes
var reg = /a/ig
// 2. 构造函数的方式创建 new RegExp(pattern, attributes);
var reg = new RegExp("a","ig");
如果我们根据用户输入的值填写匹配内容:
var search = prompt("What do you want to search?");
var reg1 = /search/;
var reg2 = new regExp(search);
console.log("直接创建", reg1); // /search/
console.log("构造函数", reg2); // /用户输入内容/
使用方式
str.search(reg)
返回找到的匹配项的索引位置,如果没找到则返回 -1。总是查找第一个匹配项。
var str = "hello world";
console.log(str.search(/o/)); // 4
console.log(str.search(/o/g)); // 4
console.log(str.search(/a/)); // -1
regExp.test(str)
检验字符串是否有符合正则条件的字符串片段,返回 true/false
基本上和
str.search(reg) != -1一样,
var str = "I love JavaScript";
// 这两条语句是一样的
console.log( /love/.test(str) ); // true
console.log( str.search(/love/) != -1 ); // true
str.match(reg)
返回符合正则表达式的字符串片段
-
不使用
"g"修饰符的时候,结果是一个数组,里面有该匹配项和额外的属性:index– 匹配项在字符串中所处在的位置,input– 原始字符串。
-
当使用
"g"修饰符的时候,str.match就会返回由所有匹配项组成的数组
console.log("hello".match(/l/)); // ["l", index: 2, input: "hello", groups: undefined]
console.log("hello".match(/l/g)); // ["l","l"];
regExp.lastIndex
标示正则表达式开始下一次匹配的字符下标,是一个可读写的值。对于带 g 属性的正则的 exec 方法和 y 属性有效,下面会有结合示例介绍。
regExp.exec(str)
检索字符串中指定的值。返回找到的值,并确定其位置
- 如果正则没有
g,那么regExp.exec(str)返回第一个匹配项,也就是str.match(reg)。 - 如果正则有
g,那么regExp.exec(str)返回第一个匹配项,然后在regExp.lastIndex里 记住 该匹配项结束的的位置。下一次调用从regExp.lastIndex开始搜索,并且返回下一个匹配项。如果再没有匹配项了,则regExp.exec返回null,regExp.lastIndex置为0。
var str = "abababa";
var reg = /ab/g;
console.log(reg.exec(str));
console.log(reg.lastIndex);
console.log(reg.exec(str));
console.log(reg.lastIndex);
console.log(reg.exec(str));
console.log(reg.lastIndex);
// 也可以手动设置 lastIndex
reg.lastIndex = 0;
console.log(reg.lastIndex);
console.log(reg.exec(str));
console.log(reg.lastIndex);
// 正则表达式reg中不写 g 属性,exec只返回匹配到的第一次的子串及下标
y 修饰符
y修饰符意味着搜索应该并且只能在属性regExp.lastIndex指定的位置查找匹配项。通常搜索是在整个字符串里搜索匹配的子串的。但是当有了
y修饰符,则只会在regExp.lastIndex(默认是0)指定的位置开始查找。
例如:
var str = "I love JavaScript!";
var reg = /javascript/iy;
console.log( reg.lastIndex ); // 0(默认)
console.log( str.match(reg) ); // null, 没有在位置 0 上找到匹配项
reg.lastIndex = 7;
console.log( str.match(reg) ); // JavaScript(搜索正确,该单词确实在位置 7)
// 对于其它 reg.lastIndex,结果都为 null
y 修饰符我们一般只在提高解析器性能的时候使用
str.split(regExp|substr, limit)
使用 regExp(或子字符串)作为分隔符分隔字符串。
我们之前已经使用过 split 的字符串形式,像下面这样:
alert('12-34-56'.split('-')) // [12, 34, 56]
但是我们也可以传入一个正则表达式:
alert('12-34-56'.split(/-/)) // [12, 34, 56]
str.replace(str|reg, str|func) 注意不改变原字符串
最简单的用处 — 搜索和替换子字符串,就像下面这样:
// 把横线替换成冒号
console.log('12-34-56'.replace("-", ":")) // 12:34-56
当 replace 的第一个参数是字符串时,它只会查找第一个匹配项。
如果要找到所有的横线,我们不能使用字符串 "-",而是 regExp /-/g,带上 g 修饰符。
// 用冒号替换所有的横线
console.log( '12-34-56'.replace( /-/g, ":" ) ) // 12:34:56
第二个参数是要替换的字符串。
我们可以在里面使用特殊符号:
| 符号 | 插入 |
|---|---|
$$ | "$" |
$& | 整个匹配项 |
| `$`` | 匹配项前面的字符串部分 |
$' | 匹配项后面的字符串部分 |
$n | 如果 n 是一个 1-2 位的数字,那么这表示从左到右数第 n 个括号的内容 |
在下面的例子中,使用 $& 将所有的 "John" 替换成 "Mr.John":
var str = "John Doe, John Smith and John Bull.";
// 对于每个 John — 替换成 Mr.John
console.log(str.replace(/John/g, 'Mr.$&'));
// "Mr.John Doe, Mr.John Smith and Mr.John Bull.";
圆括号通常与 $1,$2 一起使用,就像下面的例子:
var str = "John Smith";
console.log(str.replace(/(John) (Smith)/, '$2, $1')) // Smith, John
对于那些需要“智能”替换的场景,第二个参数可以是函数。
它会在每次匹配时被调用,并且其返回值会替换掉匹配项。
例如:
var i = 0;
// 将每个“ho”都替换成函数所返回的结果
console.log("HO-Ho-ho".replace(/ho/gi, function() {
return ++i;
})); // 1-2-3
在上面的示例中,函数每次只返回下一个数字,但通常结果是基于匹配的。
调用该函数 func(str, p1, p2, ..., pn, offset, s) 的参数是:
str— 匹配项,p1, p2, ..., pn— 圆括号里的内容(如果有的话),offset— 匹配项所在的位置,s— 源字符串。
如果在 regExp 中没有圆括号,那么该函数总是有 3 个参数:func(str, offset, s)。
下面的代码展示了匹配的所有信息:
// 显示并且替换所有的匹配项
function replacer(str, offset, s) {
console.log(`Found ${str} at position ${offset} in string ${s}`);
return str.toLowerCase();
}
var result = "HO-Ho-ho".replace(/ho/gi, replacer);
console.log( 'Result: ' + result ); // Result: ho-ho-ho
// shows each match:
// Found HO at position 0 in string HO-Ho-ho
// Found Ho at position 3 in string HO-Ho-ho
// Found ho at position 6 in string HO-Ho-ho
在下面的例子中,有两个圆括号,所以 replacer 有 5 个参数:str 是完全匹配项,然后是圆括号,然后是 offset 和 s:
function replacer(str, name, surname, offset, s) {
// name is the first parentheses, surname is the second one
return surname + ", " + name;
}
let str = "John Smith";
alert(str.replace(/(John) (Smith)/, replacer)) // Smith, John
使用函数赋予了我们终极替换大招,因为这能获取所有的匹配信息,并且能够访问外部变量,可以做任何事情。
元字符
正则表达式提供了一些拥有特殊含义的字符,称为元字符,常见的元字符如下:
| 名称 | 含义 |
|---|---|
| . | 查找单个字符,除了换行和行结束符。表示真实的点用\转译 |
| \w | 查找单词字符。英语字母表中的一个字母或者一个数字或一个下划线 |
| \W | 查找非单词字符。 |
| \d | 查找数字。 |
| \D | 查找非数字字符。 |
| \s | 查找空白字符。 |
| \S | 查找非空白字符。 |
示例:
var str = "asljdfi a\n\t4564E+/-KPJEO5487985";
console.log(str.match(/./g));
console.log(str.match(/\d/g));
console.log(str.match(/\D/g));
console.log(str.match(/\w/g));
console.log(str.match(/\W/g));
组合
假如有这样一段字符串:var str = "abcjCIUI789a123sdjfi133r45FDEEE",我们想匹配其中所有的字母 a 和数字 3:
var reg = /a3/g
console.log(str.match(reg)); // null
运行后发现并没有内容被匹配出来,这是因为正则在匹配时会将 a3 作为一个整体进行匹配,因此找不到符合条件的内容。
正则表达式使用 [] 来确定一些内容组合,匹配的内容只要在括号中存在,就会被选中。将上例改动
var reg = /[a3]/g;
console.log(str.match(/[abc ]/g); // ["a", "a", "3", "3", "3"]
正则表达式也提供了匹配内容简写,如下表:
| 表达式 | 介绍 可以支持拆分写 [5-8] |
|---|---|
| [0-9] | 查找任何从 0 至 9 的数字。 |
| [a-z] | 查找任何从小写 a 到小写 z 的字符。 |
| [A-Z] | 查找任何从大写 A 到大写 Z 的字符。 |
| [A-z] | 查找任何从大写 A 到小写 z 的字符。包含下划线 |
上面的区间可以自己定义长度,英文组合请按照字母歌进行截断,数字按照从小到大进行截断。
var str = "abcjCIUI789a123sdjfi133r45FDEEE";
var reg = /a3/;
var reg1 = /[a3]/g;
var reg2 = /[afUI3]/g;
var reg3 = /[a-z]/g;
var reg4 = /[A-Z]/g;
var reg5 = /[A-z]/g;
var reg6 = /[A-Z]/ig;
var reg7 = /[a-j]/ig; // 自定义长度
var reg8 = /[3-6]/ig; // 自定义长度
console.log(reg, str.match(reg)); // null
console.log(reg1, str.match(reg1)); // [a,a,3,3]
console.log(reg2, str.match(reg2));
console.log(reg3, str.match(reg3));
console.log(reg4, str.match(reg4));
console.log(reg5, str.match(reg5));
console.log(reg6, str.match(reg6));
console.log(reg7, str.match(reg7));
console.log(reg8, str.match(reg8));
console.log("abcd".match(/[^a]/g)); // [b,c,d]
如果在组合中使用 ^ ,表示非
var str = "abcd";
console.log(str.match(/[^a]/g)); // [b,c,d]
选择
在正则中,组合实现了类似于或的需求,但是组合只能用于单个单词的匹配,无法用于几个单词的组合进行或的筛选。
例如我们希望在一段字符串中选择 ab 或者 cd
// 匹配ab或者cd
var str = "abadcacd";
var reg = /[abcd]/g;
console.log(str.match(reg)); // ["a", "b", "a", "d", "c", "a", "c", "d"]
想要达成目的,我们需要使用 | 来完成:
var reg1 = /ab|cd/g;
console.log(str.match(reg1)); // ["ab", "cd"]
边界
正则中有两种边界筛选条件
行边界
语法 ^xx 符号匹配以XX内容为第一行开始,使用 xx$ 符号匹配以XX内容为最后一行结尾。如果想要匹配多行内容的所有行开头,请使用 m 修饰符。
注意: ^ 必须写在正则的最开始才能生效, $ 必须写在正则的末尾才能生效。
例如:
var str = "happy\nhaha\nhppy";
var reg1 = /^ha/; // 匹配作为第一行开头的 ha
var reg2 = /py$/; // 匹配作为最后一行结尾的 py
var reg3 = /py$/m; // 开启多行匹配,默认匹配第一个符合行结尾条件的 py
var reg4 = /py$/mg; // 开启多行+全局匹配,匹配所有符合行结尾条件的 py
console.log(reg1, str.match(reg1)); // [ha,0]
console.log(reg2, str.match(reg2)); // [py, 13]
console.log(reg3, str.match(reg3)); // [py, 3]
console.log(reg4, str.match(reg4)); // [py, py]
单词边界
首先明确两个概念:
- 单词边界:表示前或后跟着的是非单词,使用 \b 表示 前后不是单词(包括数字也算单词)
- 非单词边界:表示前或后跟着的是单词,使用 \B 表示
- 正则中的单词表示为 英文字母 或者 数字
| 语法 | 解释 |
|---|---|
| \bxx | 匹配前面为单词边界的xx |
| xx\b | 匹配后面为单词边界的xx |
| \Bxx | 匹配前面为非单词边界的xx |
| xx\B | 匹配后面为非单词边界的xx |
示例:
var str = "hap,ahat-2hayx*haxyat+cat";
// 匹配前面为单词边界的 ha+单词
console.log(str.match(/\bha\w/g)); // [hap, hax]
// 匹配后面为单词边界的 ha+单词
console.log(str.match(/ha\w\b/g)); // [hap,hat]
// 匹配前面为非单词边界的 ha+单词
console.log(str.match(/\Bha\w/g)); // [hat, hay]
// 匹配后面为非单词边界的 ha+单词
console.log(str.match(/ha\w\B/g)); // [hay, hax]
量词
正则中表示匹配内容数量的字符有:
| 字符 | 说明 |
|---|---|
| * | 表示匹配内容出现次数 >= 0次 |
| + | 表示匹配内容出现次数 >= 1次 |
| ? | 表示匹配内容出现次数为 0次或者 1次 |
| {n} | 表示匹配内容出现 n 次 |
| {n,} | 表示匹配内容出现次数 >= n次 |
| {n,m} | 表示匹配内容出现次数为 [n,m] 之间 |
正则表达式遵循贪婪匹配原则,即在某条规则适用于到尽可能多的内容时,返回匹配结果就会一次容纳更多内容。
示例:
var str = "haaaaaaaaaaaaaa";//注意贪婪模式是以每个字符都走一遍,没有就是"" 最后结束换行也会多一个""
var str1 = "haasd dsd";
console.log(str1.match(/a*/g));["", "aa", "", "", "", "", "", "", ""]
console.log(str.match(/a*/g)); // ["","14个a",""]
console.log(str.match(/a+/g)); // ["a*n"]
console.log(str.match(/a?/g)); // ["","a"*14,""]
console.log(str.match(/a{3}/g)); // ["aaa"*4]
console.log(str.match(/a{3,}/g)); // ["a*14"]
console.log(str.match(/a{3,5}/g)); // ["aaaaa","aaaaa","aaaa"]
如果想要打破贪婪匹配,即能匹配到尽可能少的内容,就返回匹配到最少的内容,使用语法:量词后面 + ?
// 能匹配到3个,就不一次选5个
console.log(str.match(/a{3,5}?/g)); // ["aaa"*4]
捕获组
正则模式的一部分可以用括号括起来 (...),由此构成一个『捕获组』。
这有两个作用:
- 当使用
String.match或regExp.exec方法时,它允许你把匹配到的部分放到一个独立的数组项里面。 - 如果我们在括号之后加上量词,那么它会应用到这个整体,而非最后一个字符。
例如:
var str = "Hahaha welcome";
console.log(str.match(/(ha)+/i)); // ["Hahaha", "ha"]
如果没有括号,模式 /ha+/ 则表示 h 之后跟上一个或多个 a。比如: haaaaa,捕获括号将 ha 划为了一组。
捕获结果的首项是完整的匹配结果,之后就是各个捕获组,从左往右依次排开。如果某个捕获组是可选的,且在匹配中没有找到对应的项,那么在相应的匹配结果中,该项依然会存在(值为 undefined) 。 注意加全局g只会返回匹配到的完整内容,不会返回捕获到的值
例如:
var str = "a";
console.log(str.match(/a(b)?(c)?/)); // ["a", undefined, undefined]
var str1 = "ac";
console.log(str.match(/a(b)?(c)?/)); // ["ac", undefined, "c"]
非捕获组 ?:
某些时候,我们会希望使用括号来正确设置量词,但是并不希望其内容出现在结果数组中。可以通过在开头加上 ?: 从而在结果中排除该组。
例如,我们希望找到 (ha)+,但是并不希望其内容(ha)出现在单独的数组项中,那么我们可以这样写:(?:ha)+。
var str = "Hahaha interesting";
var reg = /(?:ha)+/i;
var reg1 = /(?:ha)+ (\w+)/i;
console.log(str.match(reg)); // ["Hahaha"]
console.log(str.match(reg1)) // ["Hahaha interesting", "interesting"]
反向引用 \N
我们不仅可以在结果或替换字符串中使用捕获组 (...) 的内容,还可以在模式本身中使用它们。使用 \N 在模式中引用一个组,其中 N 是组号。正则表达式引擎会找到第一个捕获组并记住其内容。
\1 在模式中进一步的含义是“查找与第一(捕获)分组相同的文本”,与此类似,\2 表示第二(捕获)分组的内容,\3 – 第三分组,依此类推。
例如:
// 我们要匹配一对儿以单引号或者双引号包裹的内容 '...' 或 "..."
var str = `He said: "She's the one!".`;
var regExp = /['"](.*?)['"]/g;
// 不是我们想要的结果
alert( str.match(regExp) ); // "She'
// 使用反向引用
let regExp = /(['"])(.*?)\1/g;
// 这里 \1 为前面匹配到的内容
alert( str.match(regExp) ); // "She's the one!"
按命名反向引用:\k
如果正则表达式中有很多括号对(注:捕获组),给它们起个名字方便引用。
要引用命名组,我们可以使用:\k<name>。
在下面的示例中引号组命名为 ?<quote>,因此反向引用为 \k<quote>:
let str = `He said: "She's the one!".`;
let regExp = /(?<quote>['"])(.*?)\k<quote>/g;
alert( str.match(regExp) ); // "She's the one!"
注意:如果我们在组中使用 ?:,那么我们将无法引用它。用 (?:...) 捕获的组被排除,引擎不会存储。
环视断言
当我们想根据前面/后面的上下文筛选出一些东西的时候,前瞻断言和后瞻断言(通常被称为“环视断言”)对于简单的正则表达式就很有用。
环视断言类型:
| 模式 | 类型 | 匹配 |
|---|---|---|
x(?=y) | 前瞻肯定断言 | 匹配 x ,仅当后面跟着 y |
x(?!y) | 前瞻否定断言 | 匹配 x ,仅当后面不跟 y |
(?<=y)x | 后瞻肯定断言 | 匹配 x ,仅当跟在 y 后面 |
(?<!y)x | 后瞻否定断言 | 匹配 x ,仅当不跟在 y 后面 |
例如:
var str = "$200 can buy 30Kg dongbei rice";
// 匹配后面跟着 Kg 的数字
console.log(str.match(/\d+(?=Kg)/g)); // [30]
// 匹配后面不跟着 Kg 的数字
console.log(str.match(/\d+(?!\d*Kg)/g)); // [200]
console.log("39", str.match(/\d+(?!Kg)\b/g)); // [200]
// 匹配前面跟着 $ 的数字
console.log(str.match(/(?<=$)\d+/g)); // [200]
// 匹配前面不跟着 $ 的数字
console.log(str.match(/(?<!$\d*)\d+/g)); // [30]
console.log("46", str.match(/\b(?<!$)\d+/g)); // [30]
判断是否为中文:reg = /[\u4e00-\u9fa5]/