正则表达式

目录

正则表达式处理各种任务:

  • 操作 HTML 节点中的字符串
  • 使用 CSS 选择器表达式定位部分选择器
  • 判断一个元素是否含有特定的样式名称
  • 从 Internet Explorer 的 filter 属性中提取透明度
  • ...

正则表达式解释

通常被称为一个模式(pattern),是一个用简单方式描述或者匹配一系列符合某个句法规则的字符串。表达式本身包含了允许定义这些模式的术语和操作符

var pattern = /test/  // 精确匹配字符串 'test'

注意 正则字面量是用正斜杠('/xxx/')进行界定的

或者我们可以构造一个 RegExp 实例,将正则作为字符串传入: var pattern = new RegExp("test")

注意 如果正则是已知的,则优先选择字面量语法,而构造器方式则是用于在运行时,通过动态构建字符串来构建正则表达式

除了表达本身,还有三个标志可以与正则表达式进行关联

  • i —— 让正则表达式不区分大小写,所以 /test/i 不仅可以匹配 "test",还可以匹配 "TEST","Test"等
  • g —— 匹配模式中的所有实例,而不是默认只匹配第一次出现的结果
  • m —— 允许匹配多个行,比如可以匹配文本区元素(textarea)中的值

术语与操作符

精确匹配

var pattern = /test/

匹配一类字符

匹配一个有限字符集中的某一个字符

var pattern = /[abc]/ // 匹配 a,b,c 中的任何一个字符
var pattern1 = /[^abc]/ // 匹配除了 a,b,c 中的任何一个字符
var pattern2 = /[a-m]/ // 匹配 a 到 m 中的之间的所有字符

转义

并不是所有的字符和其他字符字面量都是等价的。像 $ 和 . 这样的特殊字符,表示的是他们自身以外的东西,或者表示为验证术语的操作符。 在正则里,使用反斜杠可以对任意字符进行转义,让被转义字符作为字符本身进行匹配

var pattern = /\[/  // 匹配 [ 字符,而不是匹配表达式的开括号
// 两个反斜杠(\\)则匹配一个反斜杠

匹配开始与匹配结束

// ^:匹配开头
var start = /^test/ // 匹配以 test 开头的字符串
// $:匹配结尾
var start = /test$/ // 匹配以 test 结尾的字符串

重复出现

  • 在一个字符后面加一个问号(?),可以定义为该字符是可选的。例如:/t?est/ 可以匹配 "test" 和 "est"
  • 如果一个字符要出现一次或多次,可以使用加号(+)。例如:/t+est/ 可以匹配 "test", "ttest", "tttest",但不能匹配 "est"
  • 如果一个字符要出现零次或多次,可以使用星号(*)。例如:/t*est/ 可以匹配 "test", "ttest", "tttest", 以及 "est"
  • 也可以在字符后面的花括号里指定一个数字来表示重复次数。例如:/a{4}/ 表示匹配含有连续四个 "a" 字符的字符串
  • 也可以在字符后面的花括号里指定两个数组(用逗号隔开)来表示重复次数区间。例如:/a{4,,10}/ 表示匹配任何含有连续 4 个至 10 个 "a" 字符的字符串
  • 次数区间的第二个值是可选的(但是要保留逗号),其表示一个开区间。例如:/a{4,}/ 表示匹配任何含有连续 4 个或多余 4 个 "a" 字符的字符串

预定义字符类

预定义术语匹配内容
\t水平制表符
\b空格
\v垂直制表符
\f换页符
\r回车
\n换行符
\cA;\cZ控制符,例如:\cM 匹配一个 Control-M
\x000;\xFFF十六进制 Unicode 码
\x00;\xFF十六进制 ASCII 码
.匹配除了新行(\n)以外的任意字符
\d匹配任意数字,等价于[0-9]
\D匹配任意非数字,等价于[^0-9]
\w匹配包括下划线的任意单词字符,等价于[A-Za-z0-9_]
\W匹配任何非单词字符,等价于[^A-Za-z0-9_]
\s匹配任何空白字符,包括空格、制表符、换页符等
\S匹配任何非空白字符
\b匹配单词边界
\B匹配非单词边界

分组

如果将操作符应用于一组术语,可以像数学表达式一样在该组上使用小括号。例如:/(ab)+/ 匹配一个活多个连续出现的子字符串 "ab"

或操作符(OR)

可以使用竖线( | )字符表示或者的关系。例如:/a|b/ 匹配 "a" 或 "b" 字符;/(ab)+|(cd)+/ 匹配出现一次或多次的 "ab" 或 "cd"

反向引用

在反斜杠后面加一个要引用的捕获的数量,该数字从 1 开始,如 \1、\2等

// 任意一个以 "d" "t" 或 "n" 开头,且后面跟着一个 "a" 字符,并且再后面跟着的是和第一个捕获相同字符的字符串
var pattern = /[dtn]a\1/
// 其中,\1 匹配的字符需要在执行的时候才能确定

// 匹配 XML 类型标记元素,例如:<strong>what</strong>
var pattern1 = /<(\w)>(.+)<\/\1>/

编译正则表达式

正则表达式的两个重要阶段是编译执行。 编译发生在正则表达式第一次被创建的时候;执行时发生在我们使用编译过的正则表达式进行字符串匹配的时候

注意:每个正则表达式都有一个独立的对象表示:每次创建正则表达式(被编译),都会为此创建一个新的正则表达式对象

捕获匹配的片段

正则表达式的实用性表现在捕获已匹配的结果上,这样我们便可以在其中进行处理

简单的捕获

String 对象的 match()方法进行局部正则表达式的匹配。match 返回的数组的第一个索引的值总是该匹配的完整结果,然后是每一个后续捕获的结果 注意:捕获是由正则表达式中的小括号所定义

var pattern = /opacity=([^)]+)/
var filter = 'filter:alpha(opacity=50)'

console.log(filter.match(pattern))  // ['opacity=50', '50']
// 第 0 个索引值将是完整的匹配值 opacity=50,与此同时,下一个匹配则是 50

用全局表示式进行匹配

当应用全局表达式(添加一个 g 标记)时,返回值是也是一个数组,但返回的数组包含了全局匹配结果。在这种情况下,每个匹配的捕获结果是不会返回的

var html = "<div class='test'><b>hello</b> <i>world!</i></div>"
var results = html.match(/<(\/?)(\w+)([^>]*?)>/)
console.log(results)  // ["<div class='test'>", '', 'div', " class='test'"]


var results1 = html.match(/<(\/?)(\w+)([^>]*?)>/g)
console.log(results)  // ["<div class='test'>", '<b>', '</b>', '<i>', '</i>', '</div>']

使用正则表达式的 exec() 方法可以对一个正则表达式进行多次调用,每次调用都可以返回下一个匹配的结果。该方法保存了上一次调用的状态

var html = "<div class='test'><b>hello</b> <i>world!</i></div>"
var tag = /<(\/?)(\w+)([^>]*?)>/g

console.log(tag.exec(html))  // ["<div class='test'>", '', 'div', " class='test'"]
console.log(tag.exec(html))  // ['<b>', '', 'b', '']
console.log(tag.exec(html))  // ['</b>', '/', 'b', '']
console.log(tag.exec(html))  // ['<i>', '', 'i', '']
console.log(tag.exec(html))  // ['</i>', '/', 'i', '']
console.log(tag.exec(html))  // ['</div>', '/', 'div', '']
console.log(tag.exec(html))  // null

捕获的引用

有两种方法可以引用捕获到的匹配结果

  1. 自身匹配
  2. 替换字符串: replace()
// 反向引用(\1)
var html = "<div class='test'><b>hello</b> <i>world!</i></div>"
var pattern = /<(\w+)([^>]*)>(.*?)<\/\1>/g

没有捕获的分组

小括号有双重责任:不仅要进行分组操作,还可以指定捕获

// 定义捕获(sword之前的所有字符串)的小括号
// 针对 + 操作符,对 "ninja" 文本进行分组的小括号
var pattern = /((ninja-)+)sword/  // 一切正常,但括号分组的功能,不仅是单一目标捕获了
console.log("ninja-ninja-sword".match(pattern)) // ['ninja-ninja-sword', 'ninja-ninja-', 'ninja-']

// 要想一组括号不进行结果捕获,正则表达式的语法允许我们在开始括号后加一个 ?: 标记(这就是所谓的被动子表达式)
var pattern1 =  /((?:ninja-)+)sword/

console.log("ninja-ninja-sword".match(pattern1))  // ['ninja-ninja-sword', 'ninja-ninja-']
// 被动子表达式阻止不必要的捕获('ninja-')

利用函数进行替换

String 对象的 replace() 方法的第一个参数除了可以是 pattern 以外,还可以是一个函数每个匹配都会调用该函数(全局搜索会在源字符串中匹配所有的模式实例),返回值是即将要替换的值

// 匹配中横线字符后的任意一个字符
function upper(all, letter) {
  return letter.toUpperCase()
}

console.log("border-bottom-width".replace(/-(\w)/g, upper)) // borderBottomWidth

利用正则表达式解决常见问题

修剪字符串

// 删除字符串中前后的空格
function trim(str) {
  return (str || '').replace(/^\s+|\s+$/g, '')
}
console.log(trim('  test trim  '))  // 'test trim'

匹配换行符

点(.)术语:用于匹配除换行符以外的任意字符

// 匹配所有字符,包含换行符
var html = "<b>hello</b>\n<i>world!</i>"

console.log(/.*/.exec(html))  // ['<b>hello</b>'] -> 换行符不会被匹配到
console.log(/[\S\s*]/.exec(html)) // ['<',] -> 匹配所有字符(最佳)
console.log(/(?:.|\s*)/.exec(html)) // ['<'] -> 包含换行符在内的所欲字符

Unicode

// 匹配 Unicode 字符
var text = '\u5FCD\u8005'
var matchAll = /[\w\u0080-\uFFFF_-]+/
console.log(text.match(matchAll)) // ['忍者']

转义字符

编写 CSS 选择器引擎时,要通过转义字符来支持这项功能

总结

  • 正则表达式中,有各种术语和操作符,以及在模式匹配的正则表达式中他们之间的结合
  • 使用正则表达式的 exec() 方法,以及面向正则的 String 方法,如 match() 和 replace()
  • 了解如何利用捕获的片段进行反向引用和替换字符串,以及如何使用被动子表达式避免不必要的捕获
  • 了解如何使用函数动态返回替换值,如字符串修剪、匹配像换行符或 Unicode 字符