JS 正则终极篇

郑则仕

香港有一个叫郑则仕的演员,就是上面这个了,每次我打正则式,都是联想的他的名字。

从开始学习正则到逐渐熟悉,偶尔不用经常会忘记。常常要翻出来查着用,遇到高深的用法常常不知所措,如果你也是这样,那就快快保存这篇博文吧,想不出来了,从头看一遍,保证你回想起全部正则的知识。

装逼 OK,开始开车。

创建 RegExp 对象

直接量

/pattern/attributes

var reg = /\d+/g
var a = { a: 1 }
1
2

这种写法如同直接对象

new 一个

new RegExp(pattern, attributes);

var reg = new RegExp(/\d/, 'i')
var a = new Object({ a: 1 })
1
2

这种写法如同使用构造函数构建

参数 i/g/m 的作用

attributes 作为模式选择,有下面几种可以选择。

参数 含义 作用
i ignore 区分大小写
g global 全局匹配
m multiline 多行匹配

举个栗子:

var reg = /[a-z]/
var reg2 = /[a-z]/g
console.log('abcdefg'.match(reg)) //["a", index: 0, input: "abcdefg"]
console.log('abcdefg'.match(reg2)) //["a", "b", "c", "d", "e", "f", "g"]
1
2
3
4

这里用到了 Sting 下的 match 方法,稍后会说道。这里先看着

RegExp 下的一些方法

方法:test()

返回值: Boolean 类型,true 或 false 举例:

var reg = /\d+/
reg.test({ a: 123 }) //false
reg.test(123) //true
reg.test('manfredhu') //false
1
2
3
4

不难看出,test 方法用在一些测试环境下,比如检测某个字符串是否存在某个特定字符的时候 如上栗子,要监测字符串是否具有数字,只有第二个符合。

方法:exec()

返回值:

  • 被匹配的值,返回一个数组,数组为
    • 匹配串
    • 下标
    • 整个串
  • 没有发现匹配,则返回null

举例:

var reg = /\d+/
reg.exec('d123') //["123", index: 1, input: "d123"]
reg.exec('manfredhu') //null
1
2
3

参数 g 在 exec()方法的工作原理

没有 g 参数的 exec()方法只会执行一次

var reg = /\d+/
var str = 'd123d123d123'
var result = reg.exec(str)
console.log(result, reg)
//["123", index: 1, input: "d123d123d123"] /\d+/
result = reg.exec(str)
console.log(result, reg)
//["123", index: 1, input: "d123d123d123"] /\d+/
1
2
3
4
5
6
7
8

而拥有 g 参数的 exec()方法却是不一样的,工作原理如下

  • 找到第一个 "e",并存储其位置
  • 如果再次运行 exec(),则从存储的位置开始检索,并找到下一个 "e",并存储其位置
var reg = /\d+/g
var str = 'd123d123d123'
var result = reg.exec(str)
console.log(result, reg)
//["123", index: 1, input: "d123d123d123"] /\d+/
result = reg.exec(str)
console.log(result, reg)
//["123", index: 5, input: "d123d123d123"] /\d+/
1
2
3
4
5
6
7
8

这里加了 g 参数,第一次匹配返回匹配开头的下标 1,第二次返回下标 5.

方法:compile()

返回值: 无 compile() 方法用于改变 RegExp。 compile() 既可以改变检索模式,也可以添加或删除第二个参数。

var patt1 = new RegExp('e')
console.log(patt1.test('The best things in life are free')) //true
patt1.compile('d', 'g')
console.log(patt1) // /d/g
console.log(patt1.test('The best things in life are free')) //false
1
2
3
4
5

正则表达式

正则符号比较多,常用的如下:

常用元字符

符号 含义
[abc] 查找方括号之间的任何字符
[^abc] 查找不属于 abc 的任何字符
[A-z] 查找任何从大写 A 到大写 z 的字符
\w 查找单词字符。
\W 查找非单词字符。
\d 查找数字。
\D 查找非数字字符。
\s 查找空白字符。
\S 查找非空白字符。
\b 匹配单词边界。
\B 匹配非单词边界。

量词

如字面意思,就是描述匹配的数量的。

量词 描述
n+ 匹配任何包含至少一个 n 的字符串。
n* 匹配任何包含零个或多个 n 的字符串。
n? 匹配任何包含零个或一个 n 的字符串。
n{X} 匹配包含 X 个 n 的序列的字符串。
n{X,Y} 匹配包含 X 至 Y 个 n 的序列的字符串。
n{X,} 匹配包含至少 X 个 n 的序列的字符串。
n$ 匹配任何结尾为 n 的字符串。
^n 匹配任何开头为 n 的字符串。
?=n 匹配任何其后紧接指定字符串 n 的字符串,但是不会出现在匹配结果的字符串里面
?:n 匹配任何其后紧接指定字符串 n 的字符串,并出现在匹配结果字符里面,不作为子匹配返回。
?!n 匹配任何其后没有紧接指定字符串 n 的字符串。

这里的量词还是比较重要的,最后两个估计有的同学没见过。

正向预查与负向预查

  • ?=n 正向预查

    在任何匹配  pattern  的字符串开始处匹配查找字符串。这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。例如, 'Windows (?=95|98|NT|2000)' 能匹配 "Windows 2000" 中的 "Windows" ,但不能匹配 "Windows 3.1" 中的 "Windows"。预查不消耗字符,也就是说,在一个匹配发生后,在最后一次匹配之后立即开始下一次匹配的搜索,而不是从包含预查的字符之后开始。

  • ?!= 负向预查

    在任何不匹配的字符串开始处匹配查找字符串。这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。例如'Windows (?!95|98|NT|2000)' 能匹配 "Windows 3.1" 中的 "Windows",但不能匹配 "Windows 2000" 中的 "Windows"。预查不消耗字符,也就是说,在一个匹配发生后,在最后一次匹配之后立即开始下一次匹配的搜索,而不是从包含预查的字符之后开始

?=和?:的区别

这两个预查都是正向的,区别是(?=)不会作为匹配校验,也不会被捕获而出现在匹配结果字符串里面.而(?:)会作为匹配校验,并被捕获出现在结果字符串里面,它跟(.)不同的地方在于,不被捕获作为子匹配返回.如果想要不捕获字符串的一些值的话,这两个都可以用.

var data = 'windows 98 is ok'
console.log(data.match(/windows (?=\d+)/))
console.log(data.match(/windows (?:\d+)/))
console.log(data.match(/windows (\d+)/))
//["windows ", index: 0, input: "windows 98 is ok"]
//["windows 98", index: 0, input: "windows 98 is ok"]
//["windows 98", "98", index: 0, input: "windows 98 is ok"]
1
2
3
4
5
6
7

RegExp 下的一些属性

属性 描述
global RegExp 对象是否具有标志 g。
ignoreCase RegExp 对象是否具有标志 i。
multiline RegExp 对象是否具有标志 m。
lastIndex or index 一个整数,标示开始下一次匹配的字符位置。
source 正则表达式的源文本。

lastIndex 属性

  • 该属性存放一个整数,它声明的是上一次匹配文本之后的第一个字符的位置。
  • 上次匹配的结果是由方法 RegExp.exec() 和 RegExp.test() 找到的,它们都以 lastIndex 属性所指的位置作为下次检索的起始点。这样,就可以通过反复调用这两个方法来遍历一个字符串中的所有匹配文本。
  • 该属性是可读可写的。只要目标字符串的下一次搜索开始,就可以对它进行设置。当方法 exec() 或 test() 再也找不到可以匹配的文本时,它们会自动把 lastIndex 属性重置为 0。

提示和注释 重要事项:不具有标志 g 和不表示全局模式的 RegExp 对象不能使用 lastIndex 属性。 提示:如果在成功地匹配了某个字符串之后就开始检索另一个新的字符串,需要手动地把这个属性设置为 0。

栗子翻上去看 2.2.1. 参数 g 在 exec()方法的工作原理

String 对象下的一些正则方法

方法:search(regexp)

用途: 检索与正则表达式相匹配的值,可以对比 String.indexOf(string,fromIndex)方法,但是不同的是这里可以传入一个正则表达式作为参数。 参数: 接受一个正则表达式作为参数

返回值:

  • 匹配首字母的下标
  • 没有匹配返回-1

举例:

var str = 'Visit W3School!'
console.log(str.search(/W3School/)) //6
console.log(str.search(/w3school/)) // -1
1
2
3

方法:match(str|regexp)

用途: 找到一个或多个正则表达式的匹配。 参数: 接受一个字符串或者正则式作为参数。 返回值: 返回存放匹配结果的数组。该数组的内容依赖于 regexp 是否具有全局标志 g。

举例:

var str = 'For more information, see Chapter 3.4.5.1'
var re = /(chapter \d+(\.\d)*)/i
var found = str.match(re)
console.log(found)
//["Chapter 3.4.5.1", "Chapter 3.4.5.1", ".1", index: 26, input: "For more information, see Chapter 3.4.5.1"]
1
2
3
4
5

这个例子的返回值可以看看,返回的数组的第一个值为正则式的匹配串,第二个值开始为()捕获的捕获串,这里的正则有两对小括号,所以应该有 2 个捕获串,就是返回值数组的第二个和第三个。倒数第二个参数为第一个捕获串的首字母的下标。倒数第一个参数为整个字符串。

带有 g 参数

var str = '1 plus 2 equal 3'
console.log(str.match(/\d+/g)) //["1", "2", "3"]
1
2

综合起来

var str = 'For more information, see Chapter 3.4.5.1 chapter'
var re = /(chapter \d+(\.\d)*)/gi
var found = str.match(re)
console.log(found) //["Chapter 3.4.5.1"]
1
2
3
4

可以看到带有 g 参数的,返回值只有匹配串。

方法:split(separator,howmany)

用途: 把字符串分割为字符串数组,String.split() 执行的操作与 Array.join 执行的操作是相反的。

参数:

  • 参数 separator 为必需,字符串或正则表达式,从该参数指定的地方分割 string。
  • 参数 howmany 为可选。该参数可指定返回的数组的最大长度。如果设置了该参数,返回的子串不会多于这个参数指定的数组。如果没有设置该参数,整个字符串都会被分割,不考虑它的长度。

返回值: 分割后的字符串数组。

举例:

var str = 'How are you doing today?'
console.log(str.split(' ')) //["How", "are", "you", "doing", "today?"]
console.log(str.split('')) //["H", "o", "w", " ", "a", "r", "e", " ", "y", "o", "u", " ", "d", "o", "i", "n", "g", " ", "t", "o", "d", "a", "y", "?"]
console.log(str.split(' ', 3)) //["How", "are", "you"]
1
2
3
4

方法:replace(regexp,func)

用途: replace() 方法用于在字符串中用一些字符替换另一些字符,或替换一个与正则表达式匹配的子串。

参数:

  • 第一个参数为规定子字符串或要替换的模式的 RegExp 对象
  • 第二个参数为一个字符串值。规定了替换文本或生成替换文本的函数

从参数组合来看有四种组合,下面举例四种情况。

返回值: 处理后的字符串。

举例:

参数为字符串、字符串的替换功能

var str = 'Visit Microsoft!'
console.log(str.replace('Microsoft', 'W3School')) //Visit W3School!
1
2

参数为正则表达式、字符串的更高级的替换功能

var str = 'Visit Microsoft!'
console.log(str.replace(/Microsoft/, 'W3School')) //Visit W3School!
1
2

参数为正则表达式、函数

var str = '<p><a href="{0}">{1}</a><span>{2}</span></p>'
str.replace(/\{(\d+)\}/gi, function(a, b) {
  console.log(arguments)
})
//["{0}", "0", 12, "<p><a href="{0}">{1}</a><span>{2}</span></p>"]
//["{1}", "1", 17, "<p><a href="{0}">{1}</a><span>{2}</span></p>"]
//["{2}", "2", 30, "<p><a href="{0}">{1}</a><span>{2}</span></p>"]
1
2
3
4
5
6
7

这里可以看出,接受函数作为第二个参数的时候,函数会收到的参数:

  • 第一个参数是正则表达式的匹配串
  • 第二个是正则表达式的捕获串,实际上,中间的参数具体有多少个取决于正则表达式中子表达式的个数
  • 倒数第二个参数是捕获到的部分的首字母在整个字符串中的位置索引,从 0 开始
  • 最后一个参数是调用 replace()方法的字符串本身