正则表达式, 看这一篇就够了。

1,920 阅读4分钟

如何在 Javascript 中优雅的使用正则表达式

写一篇文章来总结一下如何在 JS 中使用正则表达式。

RegExp 类型

JS 中可以通过 let expression = / pattern / flags 创建一个正则表达式。这个表达式是通过 RegExp 这个引用类型来支持的。

其中, parttern 可以是任何简单或复杂的正则表达式。 每一个正则表达式可以带有一个或多个 flags, 用以表明表达式的行为。

  • g: 表示全局(global)模式, 即模式将被应用与所有字符串, 而非在发现第一个匹配项立即停止。
  • i: 表示不区分大小写
  • m: 表示多行模式

与其他语言类似, 模式中使用的所有元字符都必须转义,正则表达式中的元字符包括: ( { [ \ ^ $ | ] ? * + . } )

还可以使用 RegExp 的构造函数来声明正则表达式

let pattern1 = /[bc]at/i // 匹配第一个 bat 或者 cat 不区分大小写

let pattern2 = /\[bc\]at/i // 匹配第一个 [bc]at 不区分大小写

等价于

let pattern1 = new RegExp('[bc]at', 'i') // 匹配第一个 bat 或者 cat 不区分大小写

let pattern2 = new RegExp('\\[bc\\]at', 'i') // 匹配第一个 [bc]at 不区分大小写

由于 RegExp 的参数是字符串,所以所有元字符需要进行双重转义

RegExp 的实例方法

RegExp 对象的主要方法是 exec()

exec() 方法接受一个参数(string), 返回包含第一个匹配项信息的数组,在没有匹配项的情况下返回 null。 返回的数组实例包含额外的 indexinput属性。 index 表示匹配项在字符串中的位置, input 表示应用正则表达式的字符串。

举个栗子

栗子中的分组语法可下滑至分组章节查看。

let text = 'mom and dad and baby'
let pattern = /mom( and dad (and baby)?)?/gi
const list = pattern.exec(text)

console.log(list.index)  // 0
console.log(list.input)  // 'mom and dad and baby'
console.log(list[0])  // 'mom and dad and baby'
console.log(list[1])  // ' and dad and baby'
console.log(list[2])  // 'and baby'

RegExp 对象上面的第二个方法是 test()

test()方法接受一个字符串参数, 正则与参数相匹配返回 true 不匹配返回 false

元字符

代码 说明
. 匹配除换行符以外的任意字符
\w 匹配字母数字或下滑线或汉字
\s 匹配任意的空白符
\d 匹配数字
\b 匹配单词的开始或结束
^ 匹配字符串的开始
$ 匹配字符串的结束
/\ba\w*b/.test(/** 匹配以字母 a 开头的单词 */)

重复

代码 说明
* 重复零次或更多次
+ 重复一次或更多次
重复零次或一次
{n} 重复n次
{n,} 重复n次或更多次
{n, m} 重复n到m次

字符串

[aeiou] 匹配任何一个英文元音字母, [.?!] 匹配标点符号 (.或?或!), [0-9] 等价于 /d 匹配一位数字, [a-z0-9A-Z] 等价于 /w(只考虑英文的话)

分枝条件

/0\d{2}-\d{8}|0\d{3}-\d{7}/ 这个表达式能够匹配两种以连字号分隔的电话号码: 一种是三位区号,八位本地号(如: 010-12345678),一种是四位区号,七位本地号(如0376-2233445)

/\(0\d{2}\)[-]?\d{8}|0\d{2}[-]?\d{8}/这个表达式匹配3位区号的电话号码,其中区号可以用小括号括起来,也可以不用,区号与本地号间可以用连字号或空格间隔,也可以没有间隔

分组

我们可以用小括号指定子表达式(也叫做分组), 这样我们可以指定多个字符(子表达式)的重复次数

(\d{1,3}\.){3}\d{1,3} 是一个简单的 IP 地址皮票表达式

反义

代码 说明
\W 匹配任意不是字母,数字,下滑线或汉字的字符
\S 匹配任意不是空白符的字符
\D 匹配任意非数字的字符
\B 匹配不是单词的开始或结束的位置
[^x] 匹配除了 x 以外的任何字符
[^aeiou] 除了 aeiou 这几个字母以外的任意字符

字符串(string)中可以使用正则的方法

String 类型定义了几个用于在字符串中匹配正则的方法, 第一个是 match()

match() 方法跟 exec() 的结果相同。这个方法只接受一个参数(一个正则表达式)。

const text = 'cat, bat, sat, fat, hat'
const pattern = /.at/

const matches = text.match(pattern)

console.log(matches.index)  // 0
console.log(matches[0])  // cat
const text = 'cat, bat, sat, fat, hat'
const pattern = /.at/g

const matches = text.match(pattern)

console.log(matches)  // ['cat', 'bat', 'sat', 'fat', 'hat']

第二个方法是 replace 方法, 这个方法接受两个参数, 第一个参数是正则或者一个字符串, 第二个参数可以是字符串或者函数。如果第一个参数是字符串, 那么只会替换第一个自字符串。如果需要替换所有匹配的自字符串需要提供正则表达式,且指定全局(g)标志。

const text = 'cat, bat, sat, fat'
const result = text.replace('at', 'ond')
console.log(result)  // cond, bat, sat, fat

const text = 'cat, bat, sat, fat'
const result = text.replace(/at/g, 'ond')
console.log(result)  // cond, bond, sond, fond

replace() 方法的第二个参数也可以是一个函数, 在只有一个匹配项的情况下, 会向这个函数传递三个参数, 匹配项, 匹配项在字符串的位置和原始字符串。如果有多个匹配项,那么参数列表为,第一个匹配项, 第二个匹配项, ....., 最后两项仍为匹配项在字符串的位置和原始字符串。这个函数的返回值应为字符串, 表示被替换的匹配项。

面试题集锦

如何使用正则实现 trim()?

const trim = (str) => str.replace(/^\s+|\s+$/g, "")

用正则实现一个简单的 function 转换功能

// 将普通的函数声明方式,转化为 es6 语法的匿名函数声明
// 不用考虑闭包、特殊符号、函数上下文等边际情况

// 入参格式参考1:
const inputFuncStr = "function a () { console.log('transfer') }";
// 出参格式参考1:
const outputFuncStr = "const a = () => { console.log('transfer') }";

// case1
const inputFuncStrCase1 = "function() {}"
// expect1
const outputFuncStrExpect1 = "() => {}";

// case2
const inputFuncStrCase2 = "const a = function() {}"
// expect2
const outputFuncStrExpect2 = "const a = () => {}";

// case3
const inputFuncStrCase3 = "function test_1() {}"
// expect3
const outputFuncStrExpect3 = "const test_1 = () => {}";


export function transfer(normalText) {
  let reg = /(function)([\w ]*)(\([\w ,]*\))/
  return normalText
    .replace(reg, (match, s1, s2, s3) => {
      if (s2) {
        return `const${s2}= ${s3} =>`
      } else {
        return `${s3} =>`
      }
    })
}