正则表达式( Regular Expression )是用于匹配字符串中字符组合的模式。
- 在 JavaScript中,正则表达式也是对象。
- 正则表达式通常被用来检索、替换那些符合某个模式(规则)的文本,例如验证表单:用户名表单只能输入英文字母、数字或者下划线, 昵称输入框中可以输入中文(匹配)。此外,正则表达式还常用于过滤掉页面内容中的一些敏感词(替换),或从字符串中获取我们想要的特定部分(提取)等 。
- 一个正则表达式可以由简单的字符构成,比如 /abc/,也可以是简单字符和特殊字符的组合,比如 /ab*c/ 。其中特殊字符也被称为元字符,在正则表达式中是具有特殊意义的专用符号,如 ^ 、$ 、+ 等。
- 正则表达式里面不需要加引号 不管是数字型还是字符串型
- 正则表达式学习网址: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Regular_Expressions
- 正则表达式测试网址:http://tool.oschina.net/regex
1. 1 使用场景
-
RegExp
的exec
和test
方法-
exec
-
语法:
// reg:RegExp实例 // str:要匹配正则表达式的字符串 reg.exec(str)
-
返回值:
如果匹配成功,
exec()
方法返回一个数组,包含额外的属性index
和input
,并更新正则表达式对象的lastIndex
属性。完全匹配成功的文本将作为返回数组的第一项,从第二项起,后续每项都对应正则表达式内捕获括号里匹配成功的文本。如果匹配失败,
exec()
方法返回null
,并将lastIndex
重置为0 -
例子
var re = /quick\s(brown).+?(jumps)/ig; var result = re.exec('The Quick Brown Fox Jumps Over The Lazy Dog.');
结果如下表:
对象 属性/索引 描述 例子 result [0] 匹配的全部字符串 Quick Brown Fox Jumps [1],...[n] 括号中的分组捕获 result[1] = Brown result[2] = Jumps index 匹配到的字符位于原始字符串的基于0的索引值 4 input 原始字符串 The Quick Brown Fox Jumps Over The Lazy Dog re lastIndex 下一次匹配开始的位置 25 ignoreCase 是否使用了 "i" 标记使正则匹配忽略大小写 true global 是否使用了 "g" 标记来进行全局的匹配 true multiline 是否使用了 "m" 标记使正则工作在多行模式(也就是,^ 和 $ 可以匹配字符串中每一行的开始和结束(行是由 \n 或 \r 分割的),而不只是整个输入字符串的最开始和最末尾处。) false source 正则匹配的字符串 quick\s(brown).+?(jumps)
-
-
test
-
语法:
// reg:RegExp实例 // str:要匹配正则表达式的字符串 reg.test(str)
-
返回值:
test()
用于检测字符串是否符合该规则,匹配成功返回true
,否则返回false
-
例子:
var rg = /abc/; console.log(rg.test('abc')); //true console.log(rg.test('aabcd')); //true console.log(rg.test('abcd')); //true console.log(rg.test('abbc')); //false
-
-
-
String
的match
matchAll
replace
search
split
方法-
match
-
语法:
str.match(reg)
-
返回值:
-
如果使用g标志,则将返回与完整正则表达式匹配的所有结果,不会返回捕获组。
-
如果未使用g标志,则仅返回第一个完整匹配及其相关的捕获组(
Array
)。 在这种情况下,返回的项目将具有如下所述的其他属性:groups
: 一个捕获组数组 或undefined
(如果没有定义命名捕获组)。index
: 匹配的结果的开始位置input
: 搜索的字符串.
事实上,如果正则表达式不包含
g
,str.match(reg)
将返回与reg.exec(str)
相同的结果.
-
-
例子:
var str = 'For more information, see Chapter 3.4.5.1'; var re = /see (chapter \d+(\.\d)*)/i; var found = str.match(re); console.log(found); // logs [ 'see Chapter 3.4.5.1', // 'Chapter 3.4.5.1', // '.1', // index: 22, // input: 'For more information, see Chapter 3.4.5.1' ] // 'see Chapter 3.4.5.1' 是整个匹配。 // 'Chapter 3.4.5.1' 被'(chapter \d+(\.\d)*)'捕获。 // '.1' 是被'(\.\d)'捕获的最后一个值。 // 'index' 属性(22) 是整个匹配从零开始的索引。 // 'input' 属性是被解析的原始字符串。
-
-
matchAll
-
语法:
// reg:正则表达式对象,且必须是设置了全局`g`的形式,否则会抛出异常`TypeError` str.matchAll(reg)
-
返回值:
返回一个迭代器,可用
for
或Array.from
来遍历 -
说明:
-
对比
reg.exec()
和str.matchAll()
:在
matchAll
出现之前,通过在循环中调用reg.exec()
来获取所有匹配项信息(reg
需使用/g
标志):const reg = RegExp('foo[a-z]*','g'); const str = 'table football, foosball'; let match; while ((match = reg.exec(str)) !== null) { console.log(`Find: ${match[0]} start: ${match.index} end: ${reg.lastIndex}`); // Find: football start: 6 end: 14 // test.html:93 Find: foosball start: 16 end: 24 }
如果使用
matchAll
,就可以不必使用 while 循环加 exec 方式(且正则表达式需使用/g
标志)。使用matchAll
会得到一个迭代器的返回值,配合for...of
,array spread
, 或者Array.from()
可以更方便实现功能:const reg = RegExp('foo[a-z]*','g'); const str = 'table football, foosball'; const match = str.matchAll(reg); for (const item of match) { console.log(`Find ${item[0]} start: ${item.index} end: ${item.index + item[0].length}`); // Find: football start: 6 end: 14 // test.html:93 Find: foosball start: 16 end: 24 } const arr = Array.from(str.matchAll(reg), m => m[0]); console.log(arr); // ["football", "foosball"]
-
matchAll
内部做了一个reg
的复制,所以不像reg.exec
,lastIndex
在字符串扫描时不会改变const regexp = RegExp('[a-c]','g'); regexp.lastIndex = 1; const str = 'abc'; Array.from(str.matchAll(regexp), m => `${regexp.lastIndex} ${m[0]}`); // Array [ "1 b", "1 c" ]
-
matchAll
的另外一个亮点是更好地获取捕获组。因为当使用match()
和/g
标志方式获取匹配信息时,捕获组会被忽略:var regexp = /t(e)(st(\d?))/g; var str = 'test1test2'; str.match(regexp); // Array ['test1', 'test2']
使用
matchAll
可以通过如下方式获取分组捕获:let array = [...str.matchAll(regexp)]; array[0]; // ['test1', 'e', 'st1', '1', index: 0, input: 'test1test2', length: 4] array[1]; // ['test2', 'e', 'st2', '2', index: 5, input: 'test1test2', length: 4]
-
-
-
replace
-
语法:
// reg:一个RegExp对象或者其字面量.该正则所匹配的内容会被第二个参数的返回值替换 // substr:一个将被newSubstr替换的字符串.仅第一个匹配项会被替换. // newSubstr:用于替换掉第一个参数在原字符串中的匹配部分的字符串。该字符串中可以内插一些特殊的变量名。 // replaceFunction:一个用来创建新子字符串的函数,该函数的返回值将替换掉第一个参数匹配到的结果 str.replace(reg|substr, newSubstr|replaceFunction)
-
返回值:
该方法并不改变调用它的字符串本身,而只是返回一个新的替换后的字符串。
-
第二个参数说明:
-
newSubstr
可插入下面的特殊变量名:变量名 代表的值 $$
插入一个 "$"
$&
插入匹配的子串 $` 插入当前匹配的子串左边的内容 $'
插入当前匹配的子串右边的内容 $n
当第一个参数是 RegExp
对象且包含捕获组,$n
对应第n
个捕获组匹配的子串$<name>
这里* Name
* 是一个分组名称。如果在正则表达式中并不存在分组(或者没有匹配),这个变量将被处理为空字符串。 -
replaceFunction
第二个参数可以指定为一个函数,函数的返回值作为替换的字符串.注意,每执行一次匹配,该函数就会执行一次(所以当第一个参数是正则表达式,并且设置了全局匹配
g
,那么replaceFunction
会被多次调用,每次匹配都用被调用),如下,第一个aa
被替换为0,第二个aa
被替换为1const str = 'aabbaaccdd'; const reg = /aa/g; let i = 0; function fn (match) { return i++; } console.log(str.replace(reg, fn)); // 0bb1ccdd
以下是该函数的参数:
变量名 代表的值 match
匹配的子串(对应于上述的 $&
)p1,p2,...
对应上述的 &1,&2,...
offset
匹配到的子字符串在原字符串中的偏移量.(比如,如果原字符串是 abcd
,匹配到的子字符串是bc
,那么偏移量就是1)string
被匹配的原字符串 NamedCaptureGroup
命名捕获组匹配的对象
-
-
例子:
-
下面的例子将会使
newString
变成'abc - 12345 - #$*%'
:function replacer(match, p1, p2, p3, offset, string) { // p1 is nondigits, p2 digits, and p3 non-alphanumerics return [p1, p2, p3].join(' - '); } var newString = 'abc12345#$*%'.replace(/([^\d]*)(\d*)([^\w]*)/, replacer); console.log(newString); // abc - 12345 - #$*%
-
交换字符串中的两个单词:
var re = /(\w+)\s(\w+)/; var str = "John Smith"; var newstr = str.replace(re, "$2, $1"); // Smith, John console.log(newstr);
-
使用行内函数来修改匹配到的字符:
function upperToHyphenLower(match){ return '-' + match.toLowerCase(); } console.log('borderTop'.replace(/[A-Z]/g, upperToHyphenLower)); // border-top
因为我们想在最终的替换中进一步转变匹配结果(比如这里使用
toLowerCase
方法进行转换),所以我们必须使用一个函数.如果我们不使用一个函数进行匹配,那么toLowerCase()
方法不会起效.如:var newString = propertyName.replace(/[A-Z]/g, '-' + '$&'.toLowerCase()); // won't work
这是因为
'$&'.toLowerCase()
会先被解析成字符串字面量(这会导致相同的’$&’)而不是当作一个模式
-
-
-
search
-
语法:
str.search(reg)
-
返回值:
如果匹配成功,则
search()
返回正则表达式在字符串中首次匹配项的索引;否则,返回-1
。事实上,
search
相对于match
,就像test
相对于exec
.当仅仅想知道字符串中是否存在某个pattern
时,可使用search
和test
,而当想获取更多的匹配信息时,可使用match
和exec
(会更慢一些). -
例子:
var str = "hey JudE"; var re = /[A-Z]/g; var re2 = /[.]/g; console.log(str.search(re)); // 4 console.log(str.search(re2)); // -1
-
-
split
-
语法:
// separator:分隔符 // limit:限制返回数组的项数最大值 str.split([separator[, limit]]);
-
返回值:
separator
为字符串,返回str
以separator
作为分隔符切割的子字符串的数组;separator
为正则表达式,返回str
匹配separator
作为分隔符切割的子字符串的数组;如果分隔符是包含捕获括号的正则表达式,则每次分隔符匹配时,捕获括号的结果(包括任何未定义的结果)将被拼接到输出数组中。- 如果
str
没有找到separator
或者省略了separator
.则返回包含str
的数组;
-
例子:
const str = 'aabbccbbddee'; console.log(str.split()); // ['aabbccbbddee'] console.log(str.split('')); // ["a", "a", "b", "b", "c", "c", "b", "b", "d", "d", "e", "e"] console.log(str.split('', 4)); // ["a", "a", "b", "b"] console.log(str.split(/b+/)); // ["aa", "cc", "ddee"] console.log(str.split(/(b+)/)); // ["aa", "bb", "cc", "bb", "ddee"] console.log('ca,bc,a,bca,bca,bc'.split(['a','b'])); // ["c", "c,", "c", "c", "c"]
-
-
1.2 创建正则表达式
-
通过 RegExp 对象的构造函数创建:
在脚本运行过程中,用构造函数创建的正则表达式会被编译。如果正则表达式将会改变,或者它将会从用户输入等来源中动态地产生,就需要使用构造函数来创建正则表达式。
var 变量名 = new RegExp(/表达式/);
-
通过字面量创建
脚本加载后,正则表达式字面量就会被编译。当正则表达式保持不变时,使用此方法可获得更好的性能。
var 变量名 = /表达式/;
1.3 特殊字符
量词符
字符 | 用法 |
---|---|
* | 匹配前一个表达式 0 次或多次.等价于 {0,} 例如, /bo*/ 会匹配"A ghost boooooed" 中的 'booooo' 和 "A bird warbled" 中的 'b' ,但是在 "A goat grunted" 中不会匹配任何内容 |
+ | 匹配前面一个表达式 1 次或者多次.等价于 {1,} 例如, /a+/ 会匹配 "candy" 中的'a' 和 "caaaaaaandy" 中所有的'a' ,但是在 "cndy" 中不会匹配任何内容 |
? | 1. 匹配前面一个表达式 0 次或者 1 次.等价于 {0,1} 例如, /e?le?/ 匹配 "angel" 中的 'el' 、"angle" 中的'le' 以及"also' 中的 'l' ;2. 如果紧跟在任何量词 * + ? 或者{} 的后面,会使量词变得非贪婪(匹配尽量少的字符),和缺省使用的贪婪模式(匹配尽可能多的字符)相反.如,对 "123abc" 使用/\d+/ 会匹配"123" ,而使用/\d+?/ 则只会匹配到" "`3. 还用于先行断言 |
{n} | n 是一个正整数,匹配了前面一个字符刚好出现了 n 次。 比如, /a{2}/ 不会匹配“candy”中的’a’,但是会匹配“caandy”中所有的 a,以及“caaandy”中的前两个’a’。 |
{n,} | n是一个正整数,匹配前一个字符至少出现了n次。 例如, /a{2,}/ 匹配 “aa”, “aaaa” 和 “aaaaa” 但是不匹配 “a”。 |
{n,m} | n 和 m 都是整数。匹配前面的字符至少n次,最多m次。如果 n 或者 m 的值是0, 这个值被忽略。 例如,/a{1, 3}/ 并不匹配“cndy”中的任意字符,匹配“candy”中的a,匹配“caandy”中的前两个a,也匹配“caaaaaaandy”中的前三个a。注意,当匹配”caaaaaaandy“时,匹配的值是“aaa”,即使原始的字符串中有更多的a。 |
边界符
字符 | 用法 |
---|---|
^ | 1. 匹配输入的开始,如果多行标志被设置为true ,那么也匹配换行符后紧跟的位置.如 /^A/ 并不会匹配"an A" 中的'A' ,但是会匹配 "An E" 中的'A' 2. 反向字符集合 |
$ | 匹配输入的结束.如果多行标志被设置为 true ,那么也匹配换行符前的位置。例如, /t$/ 并不会匹配 "eater" 中的't' ,但是会匹配 "eat" 中的't' |
如果 ^ 和 $ 在一起,表示必须是精确匹配:
var reg1 = /^abc$/; // 精确匹配 要求必须是 abc字符串才符合规范
console.log(reg1.test('abc')); // true
console.log(reg1.test('abcd')); // false
console.log(reg1.test('aabcd')); // false
console.log(reg1.test('abcabc')); // false
括号
-
小括号-捕获括号:
字符 用法 (x) 像下面的例子展示的那样,它会匹配 x
并且记住匹配项。其中括号被称为捕获括号。
模式/(foo) (bar) \1 \2/
中的 ‘(foo)
’ 和 ‘(bar)
’ 匹配并记住字符串 “foo bar foo bar” 中前两个单词。模式中的\1
和\2
表示第一个和第二个被捕获括号匹配的子字符串,即foo
和bar
,匹配了原字符串中的后两个单词。注意\1
、\2
、…、\n
是用在正则表达式的匹配环节,详情可以参阅后文的 \n 条目。而在正则表达式的替换环节,则要使用像$1
、$2
、…、$n
这样的语法,例如,'bar foo'.replace(/(...) (...)/, '$2 $1')
。$&
表示整个用于匹配的原字符串。(?:x) 匹配 ‘x’ 但是不记住匹配项。这种括号叫作非捕获括号,使得你能够定义与正则表达式运算符一起使用的子表达式。
如/(?:foo){1,2}/
。如果表达式是/foo{1,2}/
,{1,2}
将只应用于 ‘foo’ 的最后一个字符 ‘o’。如果使用非捕获括号,则{1,2}
会应用于整个 ‘foo’ 单词 -
中括号:
字符 用法 [xyz] 一个字符集合。匹配方括号中的任意字符,包括转义序列。你可以使用破折号(-)来指定一个字符范围。对于点(.)和星号(*)这样的特殊符号在一个字符集中没有特殊的意义。他们不必进行转义,不过转义也是起作用的。
例如,[abcd] 和[a-d]是一样的。他们都匹配"brisket"中的‘b’,也都匹配“city”中的‘c’。/[a-z.]+/ 和/[\w.]+/与字符串“test.i.ng”匹配。[^xyz] 一个反向字符集。也就是说, 它匹配任何没有包含在方括号中的字符。你可以使用破折号(-)来指定一个字符范围。任何普通字符在这里都是起作用的。
例如,[^abc]
和[^a-c]
是一样的。他们匹配"brisket"中的‘r’,也匹配“chop”中的‘h’。 -
大括号:量词符
转义符
字符 | 用法 |
---|---|
\ | 1. 在非特殊字符前,表示下一个字符是特殊字符,如\s 匹配空白字符,而不是匹配小写字母s ;2.在特殊字符前,表示下一个字符是普通字符,即转义,如 \* 匹配* 字符;3.在字符串字面量中, \ 是转义字符,所以如果要在这种模式下添加反斜杠,需要双重转义.如 /[a-z]\s/i 和 new RegExp("[a-z]\\s", "i") 创建了相同的正则表达式再如 /[a-z]:\\/i 和 new RegExp("[a-z]:\\\\","i") 会创建相同的表达式 |
关于转义:
-
转义特殊字符为对应的字面值,如匹配
a*b
,使用/a\*b/
; -
转义斜杆
/
,如果正则表达式文字需要匹配斜杠/
,需要进行转义,因为/
在正则中是终止符.如匹配C:/
,使用/[A-Z]:\//
; -
转义反斜杠
\
,如果正则表达式文字需要匹配反斜杠\
,需要进行转义.如匹配C:\
,使用/[A-Z]:\\/
; -
在
RegExp
构造函数中,在字符串中使用正则表达式,请记住反斜杠是字符串文字中的转义,因此要在正则表达式中使用它,需要在字符串文字级别转义它。/a\*b/
和new RegExp("a\\*b")
创建的表达式是相同的 -
将用户输入转义为正则表达式中的一个字面字符串:
function escapeRegExp(inputString) { //$&表示整个被匹配的字符串 return inputString.replace(/[.*+?^&{}()|[\]\\]/g, "\\$&"); }
断言
字符 | 用法 |
---|---|
x(?=y) | 匹配’x’仅仅当’x’后面跟着’y’.这种叫做先行断言。 例如,/Jack(?=Sprat)/会匹配到’Jack’仅当它后面跟着’Sprat’。/Jack(?=Sprat|Frost)/匹配‘Jack’仅当它后面跟着’Sprat’或者是‘Frost’。但是‘Sprat’和‘Frost’都不是匹配结果的一部分。 |
(?<=y)x | 匹配’x’仅当’x’前面是’y’.这种叫做后行断言。 例如,/(?<=Jack)Sprat/会匹配到’ Sprat ‘仅仅当它前面是’ Jack '。/(?<=Jack|Tom)Sprat/匹配‘ Sprat ’仅仅当它前面是’Jack’或者是‘Tom’。但是‘Jack’和‘Tom’都不是匹配结果的一部分。 |
x(?!y) | 仅仅当’x’后面不跟着’y’时匹配’x’,这被称为正向否定查找。 例如,仅仅当这个数字后面没有跟小数点的时候,/\d+(?!.)/ 匹配一个数字。正则表达式/\d+(?!.)/.exec(“3.141”)匹配‘141’而不是‘3.141’ |
(?<!y)x | 仅仅当’x’前面不是’y’时匹配’x’,这被称为反向否定查找。 例如, 仅仅当这个数字前面没有负号的时候, /(?<!-)\d+/ 匹配一个数字。/(?<!-)\d+/.exec('3') 匹配到 “3”./(?<!-)\d+/.exec('-3') 因为这个数字前有负号,所以没有匹配到。 |
预定义类
字符 | 用法 |
---|---|
\b | 匹配一个词的边界. 如 /\blu/ 匹配lujiafeng 中的lu /lu\b/ 匹配lujiafeng 中的lu |
\B | |
\d | 匹配一个数字。``等价于[0-9] 。 |
\D | 匹配一个非数字字符。``等价于[^0-9] 。 |
\f | 匹配一个换页符 (U+000C) |
\n | 匹配一个换行符 (U+000A)。 |
\r | 匹配一个回车符 (U+000D)。 |
\s | 匹配一个空白字符,包括空格、制表符、换页符和换行符。等价于[ \f\n\r\t\v\u00a0\u1680\u180e\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff] 。例如, /\s\w*/ 匹配"foo bar."中的’ bar’。 |
\S | 匹配一个非空白字符。等价于 [^ \f\n\r\t\v\u00a0\u1680\u180e\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff] 。例如, /\S\w*/ 匹配"foo bar."中的’foo’。 |
\t | 匹配一个水平制表符 (U+0009)。 |
\v | 匹配一个垂直制表符 (U+000B)。 |
\w | 匹配一个单字字符(字母、数字或者下划线)。等价于 [A-Za-z0-9_] 。例如, /\w/ 匹配 “apple,” 中的 ‘a’,"$5.28,"中的 ‘5’ 和 “3D.” 中的 ‘3’。 |
\W | 匹配一个非单字字符。等价于 [^A-Za-z0-9_] 。例如, /\W/ 或者 /[^A-Za-z0-9_]/ 匹配 “50%.” 中的 ‘%’。 |
\n(这里的n指的是数字) | 在正则表达式中,它返回最后的第n个子捕获匹配的子字符串(捕获的数目以左括号计数)。 比如 /apple(,)\sorange\1/ 匹配"apple, orange, cherry, peach."中的’apple, orange,’ 。 |
\0 | 匹配 NULL(U+0000)字符, 不要在这后面跟其它小数,因为 \0<digits> 是一个八进制转义序列。 |
[\b] | 匹配一个退格(U+0008)。(不要和\b混淆了。) |
其他
字符 | 用法 |
---|---|
. | 1. (小数点)默认匹配除换行符之外的任何单个字符。 例如, /.n/ 将会匹配 "nay, an apple is on the tree" 中的'an' 和'on' ,但是不会匹配 'nay' 。2. 如果 ``s ("dotAll") 标志位被设为 true ,它也会匹配换行符。 |
x|y | 匹配‘x’或者‘y’。 |
1.4 通过标志进行高级搜索
正则表达式有六个可选参数 (flags
) 允许全局和不分大小写搜索等。这些参数既可以单独使用也能以任意顺序一起使用, 并且被包含在正则表达式实例中。
标志 | 描述 |
---|---|
g | 全局搜索 |
i | 不区分大小写搜索 |
m | 多行搜索 |
s | 允许. 匹配换行符 |
u | 使用unicode 码的模式进行匹配 |
y | 执行粘性(sticky) 搜索,匹配从目标字符串的当前位置开始 |