正则表达式

102 阅读11分钟

正则

正则表达式是对字符串执行模式匹配的强大工具。

它是利用事先定义好的一些特定字符或特定字符的组合,组成一个“规则字符串”,表达对字符串的一种过滤逻辑。常用于检查字符串是否含有某个内容,或者替换字符串中某些内容,或者从字符串中取出一段内容。

正则表达式的用途广泛,各编程语言之中都有正则表达式的身影,使用方式又有所不同,本文档只专注于 JavaScript 中正则表达式的使用。

特点

  1. 灵活性、逻辑性和功能性非常的强;
  2. 可以迅速地用极简单的方式达到字符串的复杂控制;
  3. 对于刚接触的人来说,比较晦涩难懂;

创建方式

在 JavaScript 中,正则表达式通过内置的“regExp”类的对象来实现,并与字符串集成。两种创方式:

  1. 字面量形式创建

/pattern/attributes

  1. 构造函数的方式创建

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 返回 nullregExp.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) 的参数是:

  1. str — 匹配项,
  2. p1, p2, ..., pn — 圆括号里的内容(如果有的话),
  3. offset — 匹配项所在的位置,
  4. 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 是完全匹配项,然后是圆括号,然后是 offsets

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$/; // 匹配作为最后一行结尾的 pyvar reg3 = /py$/m; // 开启多行匹配,默认匹配第一个符合行结尾条件的 py
var reg4 = /py$/mg; // 开启多行+全局匹配,匹配所有符合行结尾条件的 pyconsole.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]

捕获组

正则模式的一部分可以用括号括起来 (...),由此构成一个『捕获组』。

这有两个作用:

  1. 当使用 String.matchregExp.exec 方法时,它允许你把匹配到的部分放到一个独立的数组项里面。
  2. 如果我们在括号之后加上量词,那么它会应用到这个整体,而非最后一个字符。

例如:

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]/