js正则基础

176 阅读3分钟

1. 正则

正则表达式(Regular Expression)是用于匹配字符串中字符组合的模式。从它的命名我们可以知道,它是一种用来描述规则的表达式, 目的是为了字符串模式匹配,从而实现搜索和替换功能。

两种方式:字面量、构造函数

let expression = /pattern/flags

let reg1 = new RegExp("[bc]at", "i");
let reg2 = /[bc]at/i; // 等价

使用构造函数,第一个参数为字符串,需要转义,建议使用字面量

2. 基本语法

2.1 正则标记 flag

六种标记

  • g:全局模式(global),表示查找字符串的全部内容,而不是找到第一个匹配的内容就结束。
  • i:不区分大小写(ignoreCase),表示在查找匹配时忽略 pattern字符串的大小写。
  • m:多行模式(multiline),表示查到到一行文本末尾时会继续查找
  • s:dotAll 模式,表示元字符 .匹配任何字符(包括 \n 或 \r )。
  • u:Unicode 模式,使用unicode码的模式进行匹配。
  • y:粘附模式(sticky),表示只查找从 lastIndex 开始及之后的字符串。

粘附模式从头开始进行匹配,后一次匹配都从上一次匹配成功的下一个位置开始,而global只要剩余位置中存在匹配就可。lastIndex表示的索引处为目标字符串匹配。

const str = 'avascript'
const reg1 = /a/g
console.log(reg1.exec(str)) // ['a', index:0]
console.log(reg1.exec(str)) // ['a',index:2]
console.log(reg1.exec(str)) // null
-------------------------------------------------------
const str = 'avascript'
const reg2 = /a/y
console.log(reg2.exec(str)) // ['a', index: 0]
console.log(reg2.lastIndex) // 1
console.log(reg2.exec(str)) // **null**
console.log(reg2.lastIndex) // 0 (匹配失败后重置)

2.2 基本元字符

  • . : 匹配除了换行符之外的任何单个字符
  • \ : 在非特殊字符之前的反斜杠表示下一个字符是特殊的,不能从字面上解释。反斜杠也可以将其后的特殊字符,转义为字面量。w => \w * => \*
  • | : 逻辑或操作符
  • [] :定义一个字符集合,匹配字符集合中的一个字符,在字符集合里面像 .\这些字符都表示其本身
  • [^]:对上面一个集合取非
  • - :定义一个区间,例如[A-Z],其首尾字符在 ASCII 字符集里面

2.3 数量元字符

  • {m,n} :匹配前面一个字符至少 m 次至多 n 次重复,还有{m}表示匹配 m 次,{m,}表示至少 m 次
  • +: 匹配前面一个表达式一次或多次,相当于{1,}
  • *: 匹配前面一个表达式零次或多次,相当于{0,}
  • : 匹配前面一个表达式零次或一次,相当于{0,1}
    • 如果跟在任何量词*,+,?,{}后面的时候将会使量词变为非贪婪模式(尽量匹配少的字符),默认是使用贪婪模式。比如对 "123abc" 应用 /\d+/ 将会返回 "123",如果使用 /\d+?/,那么就只会匹配到 "1"。

2.4 位置元字符

  • ^: 开头
  • $: 结尾
  • \b:匹配单词边界
  • \B:匹配非单词边界
  • (?=p):匹配 p 前面的位置
  • (?!p):匹配不是 p 前面的位置

2.5 特殊元字符

  • \d:digit,[0-9],表示一位数字
  • \D:非digit,[^0-9],表示一位非数字
  • \s:space character,[\t\v\n\r\f],表示空白符,包括空格,水平制表符(\t),垂直制表符(\v),换行符(\n),回车符(\r),换页符(\f)
  • \S[^\t\v\n\r\f],表示非空白符
  • \w:word,[0-9a-zA-Z],表示数字 大小写字母下划线
  • \W[^0-9a-zA-Z],表示非单词字符

3. 正则表达式方法

正则表达式可以被用于 RegExpexectest 方法以及 Stringmatchreplacesearchsplit 方法。

3.1. exec

exec() 方法在一个指定字符串中执行一个搜索匹配。返回一个结果数组(包含额外的属性 index 和 input)或 null

regexObj.exec(str)

在设置了 globalsticky 标志位的情况下(如 /foo/g or /foo/y),JavaScript RegExp 对象是有状态的。他们会将上次成功匹配后的位置记录在 lastIndex (在变化)属性中。

如果匹配失败,exec() 方法返回 null,并将 lastIndex 重置为 0 。

const reg = /foo*/g;
const str = 'table football, fooooosball';
console.log(reg.exec(str)) // Array ["foo"]
console.log(reg.exec(str)) // Array ["fooooo"]
const reg = /foo*/y;
const str = 'table foofooooosball';
reg.lastIndex = 6;
console.log(reg.exec(str)) // Array ["foo"]
console.log(reg.lastIndex) // 9
console.log(reg.exec(str)) // Array ["fooooo"]
console.log(reg.lastIndex) // 15
console.log(reg.exec(str)) // null
console.log(reg.lastIndex) // 0

3.2 test

test() 方法执行一个检索,用来查看正则表达式与指定的字符串是否匹配。返回 true 或 false。

regexObj.test(str)

let str = 'hello world!';
let result = /^hello/.test(str);
console.log(result); // true

如果正则表达式设置了全局标志(g),test() 的执行会改变正则表达式 lastIndex属性。连续的执行test()方法,后续的执行将会从 lastIndex 处开始匹配字符串。

var regex = /foo/g
// regex.lastIndex is at 0
regex.test('foo'); // true

// regex.lastIndex is now at 3
regex.test('foo'); // false

3.3 match

match() 方法检索返回一个字符串匹配正则表达式的结果。 str.match(regexp)

  • 使用g标志,则将返回与完整正则表达式匹配的所有结果,但不会返回捕获组
  • 未使用g标志,则仅返回第一个完整匹配及其相关的捕获组。在这种情况下,返回的项目将具有其他属性。
  • 未找到匹配返回null
  • 使用 match 不传参,如果没有给出任何参数并直接使用match() 方法 ,将会得到一 个包含空字符串的 Array :[""]
const paragraph = 'Hello World';
const regex = /[A-Z]/g;
const found = paragraph.match(regex); // Array ["H", "W"]
const paragraph = 'Hello World';
const regex = /[A-Z]/;
const found = paragraph.match(regex); 
// [1] 'H' 为捕获信息
// 以下为附加属性
// **index**: 0 匹配的结果的开始位置
// input**: "Hello World" 搜索的字符串.

3.4 search

search() 方法执行正则表达式和 String 对象之间的一个搜索匹配。

如果匹配成功,则 search() 返回正则表达式在字符串中首次匹配项的索引;否则,返回-1

str.search(regexp)

const str = 'Hello world';
const re0 = /[A-Z]/g;
const re1 = /\d/g;

console.log(str.search(re0)); // 0
console.log(str.search(re1)); // -1

3.5 replace

replace() 方法返回一个由替换值替换部分或所有的模式(pattern)匹配项后的新字符串。模式可以是一个字符串或者一个正则表达式,替换值可以是一个字符串或者一个每次匹配都要调用的回调函数。如果pattern是字符串,则仅替换第一个匹配项。

原字符串不会改变。

const p = 'I have a dog and a dog';
console.log(p.replace('dog', 'cat')); // "I have a cat and a dog"
const regex = /Dog/ig;
console.log(p.replace(regex, 'cat')); // "I have a cat and a cat"

3.6 split

split() 方法使用指定的分隔符字符串将一个String对象分割成子字符串数组,以一个指定的分割字串来决定每个拆分的位置。

str.split([separator[, limit]])

3.7 matchAll

matchAll() 方法返回一个包含所有匹配正则表达式的结果及分组捕获组的迭代器。

str.matchAll(regexp)

RegExp必须是设置了全局模式g的形式,否则会抛出异常TypeError。

const regexp = /t(e)(st(\d?))/g;
const str = 'test1test2';
const array = [...str.matchAll(regexp)];
console.log(array[0]);
// expected output: Array ["test1", "e", "st1", "1"]
console.log(array[1]);
// expected output: Array ["test2", "e", "st2", "2"]

4. 实例

4.1 字符匹配

4.1.1 模糊匹配

4.1.1.1 横向模糊匹配

横向模糊指的是,一个正则可匹配的字符串的长度不是固定的,可以是多种情况的。

其实现的方式是使用量词。譬如{m,n},表示连续出现最少m次,最多n次。

var regex = /ab{2,5}c/g;

var string = "abc abbc abbbc abbbbc abbbbbc abbbbbbc";

console.log( string.match(regex) ); // => ["abbc", "abbbc", "abbbbc", "abbbbbc"]
4.1.1.2 横向模糊匹配

纵向模糊指的是,一个正则匹配的字符串,具体到某一位字符时,它可以不是某个确定的字符,可以有多种可能。

其实现的方式是使用字符组。譬如[abc],表示该字符是可以字符“a”、“b”、“c”中的任何一个。

var regex = /a[123]b/g;

var string = "a0b a1b a2b a3b a4b";

console.log( string.match(regex) ); // => ["a1b", "a2b", "a3b"]

4.1.2 字符组

4.1.2.1 范围表示法

比如[123456abcdefGHIJKLM],可以写成[1-6a-fG-M]。用连字符-来省略和简写。

因为连字符有特殊用途,那么要匹配“a”、“-”、“z”这三者中任意一个字符,该怎么做呢?

不能写成[a-z],因为其表示小写字符中的任何一个字符。

可以写成如下的方式:[-az][az-][a\-z]。即要么放在开头,要么放在结尾,要么转义。总之不会让引擎认为是范围表示法就行了

4.1.2.1 排除字符组

[^abc],表示是一个除"a"、"b"、"c"之外的任意一个字符。字符组的第一位放^(脱字符),表示求反的概念。

4.1.3 量词

4.1.3.1 简写形式

{m,} {m} ? + * ...

4.1.3.2 贪婪匹配和惰性匹配

var regex = /\d{2,5}/g;

var string = "123 1234 12345 123456";

console.log( string.match(regex) );

// => ["123", "1234", "12345", "12345"]

其中正则/\d{2,5}/,表示数字连续出现2到5次。会匹配2位、3位、4位、5位连续数字。

上述例子为贪婪匹配,它会尽可能多的匹配,只要在能力范围内,越多越好。

而惰性匹配,就是尽可能少的匹配:

var regex = /\d{2,5}?/g;

var string = "123 1234 12345 123456";

console.log( string.match(regex) );

// => ["12", "12", "34", "12", "34", "12", "34", "56"]

其中/\d{2,5}?/表示,虽然2到5次都行,当2个就够的时候,就不再往下尝试了。

4.1.3.3 多选分支

多选分支可以支持多个子模式任选其一。

(p1|p2|p3),其中p1、p2和p3是子模式,用|(管道符)分隔,表示其中任何之一。

var regex = /good|nice/g;

var string = "good idea, nice try.";

console.log( string.match(regex) );

// => ["good", "nice"]

但有个点应该注意,比如我用/good|goodbye/,去匹配"goodbye"字符串时,结果是"good":

var regex = /good|goodbye/g;

var string = "goodbye";

console.log( string.match(regex) );

// => ["good"]

而把正则改成/goodbye|good/,结果是:

var regex = /goodbye|good/g;

var string = "goodbye";

console.log( string.match(regex) );

// => ["goodbye"]

分支结构也是惰性的,即当前面的匹配上了,后面的就不再尝试了。

4.2 位置匹配

4.2.1 ^和$

^匹配开头,在多行匹配中匹配行开头。

$匹配结尾,在多行匹配中匹配行结尾。

var result = "hello".replace(/^|$/g, '#');

console.log(result); // => "#hello#"

4.2.2 \b和\B

\b是单词边界,具体就是\w和\W之间的位置,也包括\w和^之间的位置,也包括\w和$之间的位置。

var result = "[JS] Lesson_01.mp4".replace(/\b/g, '#');

console.log(result); // => "[#JS#] #Lesson_01#.#mp4#"

4.2.3 x(?=y)和x(?!y)

(?=p),其中p是一个子模式,即p前面的位置。

中文翻译分别是正向先行断言负向先行断言

var result = "hello".replace(/(?=l)/g, '#');

console.log(result); // => "he#l#lo"
const str = 'I like java and javascript'

let reg1 = /java/g

console.log(str.replace(reg1, 'coffee')) // I like coffee and coffeescript
let reg2 = /java(?=script)/g
console.log(str.replace(reg2, 'coffee')) // I like java and coffeescript

let reg3 = /java(?!script)/g

console.log(str.replace(reg3, 'coffee')) // I like coffee and javascript

而(?!p)就是(?=p)的反面意思,比如:

var result = "hello".replace(/(?!l)/g, '#');

console.log(result); // => "#h#ell#o#"

4.3 括号的应用

4.3.1 分组和分支结构

4.3.1.1 分组

括号可提供分组功能,使量词+作用于括号内的整体,例如:

// 我们知道/a+/匹配连续出现的“a”,而要匹配连续出现的“ab”时,需要使用/(ab)+/
var regex = /(ab)+/g;
var string = "ababa abbb ababab";
console.log( string.match(regex) ); // => ["abab", "ab", "ababab"]

4.3.1.2 分支结构

var regex = /^I love (JavaScript|Regular Expression)$/;
console.log(regex.test("I love JavaScript") );
console.log(regex.test("I love Regular Expression") );
// => true
// => true

如果去掉正则中的括号,即/^I love JavaScript|Regular Expression$/,匹配字符串是"I love JavaScript"和"Regular Expression"

4.3.2 引用分组

这是括号一个重要的作用,有了它,我们就可以进行数据提取,以及更强大的替换操作。而要使用它带来的好处,必须配合使用实现环境的API。

以日期为例。假设格式是yyyy-mm-dd的,我们可以先写一个简单的正则:

var regex = /\d{4}-\d{2}-\d{2}/;

然后再修改成括号版的:

var regex = /(\d{4})-(\d{2})-(\d{2})/;

4.3.2.1 提取数据

var regex = /(\d{4})-(\d{2})-(\d{2})/;
var string = "2017-06-12";
console.log( string.match(regex) );
// => ["2017-06-12", "2017", "06", "12", index: 0, input: "2017-06-12"]

match返回的一个数组,第一个元素是整体匹配结果,然后是各个分组(括号里)匹配的内容,然后是匹配下标,最后是输入的文本。

同时,也可以使用构造函数的全局属性$1$9来获取:

var regex = /(\d{4})-(\d{2})-(\d{2})/;
var string = "2017-06-12";
regex.test(string);
console.log(RegExp.$1); // "2017"
console.log(RegExp.$2); // "06"
console.log(RegExp.$3); // "12"

4.3.2.2 替换

比如,想把yyyy-mm-dd格式,替换成mm/dd/yyyy

var regex = /(\d{4})-(\d{2})-(\d{2})/;
var string = "2017-06-12";
var result = string.replace(regex, "$2/$3/$1");
console.log(result);
// => "06/12/2017"

11、2、$3指代相应的分组。等价于如下的形式:

var regex = /(\d{4})-(\d{2})-(\d{2})/;
var string = "2017-06-12";
var result = string.replace(regex, function() {
return RegExp.$2 + "/" + RegExp.$3 + "/" + RegExp.$1;
});
console.log(result);
// => "06/12/2017"