JS正则表达式学习笔记

220 阅读5分钟

JS正则表达式学习笔记

什么是正则表达式

正则表达式是用于匹配字符串中字符组合的模式。

正则表达式如何创建

  • 字面量:/pattern/flags => /^\d+$/g
  • 构造函数: new RegExp(pattern, [, flags]) => new RegExp('^\d+$', 'g')
  • 工厂方法:RegExp(pattern, [, flags]) => RegExp('^\d+$', 'g')

字面量和构造函数的区别

  • 正则字面量/ab+c/g提供正则的编译状态,当成常量使用,在循环中使用正则字面量,不会进行重复编译
  • 构造函数new RegExp('ab+c', 'g')提供正则的运行时编译状态,一般用在不清楚使用的模式或模式是从另一个源获得如用户输入,这种情况可以使用构造函数
  • 在预先知道pattern的情况下用正则字面量,字面量正则表达式当成常量使用,需要动态创建正则表达式时使用构造函数。

ECMAScript 6开始,创建正则对象或用工厂方法,第一个参数可以不为字符串,可以传递正则表达式字面量。

   new RegExp(/ab+c/, 'gi') 
   // => /ab+c/gi
   new RegExp(/ab+c/g, 'i') 
   // => /ab+c/i 第二个参数指定修饰符,则忽略字面量的修饰符
   new RegExp(/ab+c/g) 
   // => /ab+c/g

正则表达式语法

修饰符

修饰符含义
i忽略大小写
g全局匹配,找到所有匹配而不是匹配到第一个停止

字符类别 (Character Classes)

用于区分不同类型的字符,如区分字母和数字

字符含义
.匹配任意字符,在字符集中.将失效,匹配字面量'.'
\d匹配阿拉伯数字,等价于 [0-9] 如:/\d+/
\w匹配字母数字和下划线,等价于[a-zA-Z0-9_] /\w+/
\s匹配空白字符,如 /Hello\sWorld/
\D匹配非阿拉伯数字,等价于 [^0-9]
\W匹配非拉丁字母表中的字母数字和下划线, 等价于[^a-zA-Z0-9_] 如匹配50%中的%

边界(Boundaries)

表示行和单词的开始和结尾

字符含义
^匹配输入的开始,如/^A/匹配A apple,而不是a Apple
$匹配输入的结束,如/e$/匹配A apple,而不是apple and orange
\b匹配一个零宽单词边界,如 "an noon".match(/\b\w+\b/g); => ['an', 'noon']
\B匹配一个非零宽单词边界,如"an noon".match(/\B\w+\B/g); => ['oo']

字符集合和范围 (Character Sets and Ranges)

字符含义
[xyz]一个字符集合也叫字符组,匹配匹配集合中的任意一个字符,如/[abc]/匹配apple中的a
[^xyz]反义字符集,即xyz取反,反义字符组,匹配不在该集合中的字符,如/[^abc]/匹配apple中的pple
[n-m]n和m可以是字母或数字,表示字符集的范围,/[abc]/等效于/[a-c]/,条件n<mn要小于m,如果n>m则会抛出SyntaxError: Invalid regular expression, 如果n=m则范围失效
[^n-m]反义字符范围,匹配未包含在字符范围里的字符

分组和反向引用 (Groups and back References)

表示表达式字符的分组

字符含义
(x)匹配x并且捕获匹配项,也称为捕获分组括号,在匹配结果中包含匹配项的子字符串通过[1]...[n]引用匹配结果子字符串,或通过RegExp静态属性的$1...$9引用
(?:x)匹配x不捕获匹配项,也称为非捕获分组括号,在匹配结果中不包含匹配项
\n捕获项的引用,n未正整数,获取匹配结果n从1开始

捕获组有性能惩罚,如果不需要返回捕获的子字符串,建议使用非捕获组,进行字符串匹配

  • 捕获分组
"foo bar".match(/(foo)\sbar);
// ['foo bar', 'foo'] 通过 [1]引用捕获结果项
"apple, orange, cherry, peach".match(/apple(,)\sorange\1/)
// ['apple, orange,', ','] 通过\n引用匹配项
  • 非捕获结果
"foo bar".match(/(?:foo)\sbar/)
// ['foo bar'] 结果不返回捕获项

量词 (Quantifiers)

匹配的字符或表达式的数量

字符含义
x*x重复0或多次,等价于 {0,}
x+x重复1或多次,等价于 {1,}
x?x重复0或1次,等价于 {0,1}
`xy`匹配x或y
x{n}x重复n次
x{n,}x至少重复n次
x{n,m}x至少重复n次,最大重复m次

贪婪与非贪婪模式

  • 默认为贪婪模式,尽可能多的匹配,*+默认是贪婪的,会找到最大匹配项
  • 在量词后面添加?,会转变为非贪婪模式,尽可能少的匹配,即找到一个匹配项就停止匹配
字符含义
x*?重复0次
x+?重复1次
x??重复0次
x{n}?无意义
x{n,}?重复n次
x{n,m}重复n次

断言 (Assertions)

表示一个匹配在模型条件下发生,断言包括先行断言、后行断言和条件表达式

下列所有断言只匹配x,y不参与匹配

字符含义
x(?=y)正向先行断言,仅匹配被y跟随的x
x(?!y)负向先行断言,仅匹配不被y跟随的x
(?<=y)x正向后行断言,x只有在y后面才匹配
(?<!y)x负向后行断言,x只有不在y后面才匹配

断言示例

// 提取下列字符串中以`ing`结尾的单词,并返回不带`ing`的动词
var text = "climbing, oranges, jumping, flying, carrot"
// (\w+) 捕获组,捕获匹配的动词
// (?=ing) 正向先行断言,筛选以ing结尾的单词
// g 修饰符 全局匹配
text.match(/(\w+)(?=ing)/g);
// => ["climb", "jump", "fly"]

正则方法

test

测试当前正则是否能匹配目标字符串。匹配成功返回true,匹配失败返回false,和字符串方法String.prototype.search()类似

regexObj.test(str)

// 简单校验手机号码
var reg = /^1\d{10}$/
var phone = '18966667877'
reg.test(phone)

exec

在目标字符串中执行一次正则匹配操作

regexObj.exec(str)

RegExp.prototype.test()不同的是,RegExp.prototype.exec()不只是返回匹配结果true/false,而是返回正则匹配结果的更多信息。

单使用全局修饰符g标志时,每执行一次exec,都会更新regex.lastIndex的值

const regex1 = RegExp('foo*', 'g');
const str1 = 'table football, foosball';
let array1;

while ((array1 = regex1.exec(str1)) !== null) {
  console.log(`Found ${array1[0]}. Next starts at ${regex1.lastIndex}.`);
  // expected output: "Found foo. Next starts at 9."
  // expected output: "Found foo. Next starts at 19."
}

字符串方法

match

检索并返回正则表达式匹配的结果

语法

var resultArray = s.match(regexp)

resultArray返回的结果取决于正则表达式regexp是否带修饰符g,而导致返回结果不同。

  • 不带修饰符g,则返回结果与RegExp.exec()的结果一样,只返回第一个匹配子字符串和其捕获组["匹配项字符串", "捕获组子字符串"]
  • g,则返回所有的匹配子字符串,["匹配字符串1", "匹配字符串2", ...]
// 获取下面字符串中的数字
var s = "aa11ad22ca33cc";
s.match(/(\d+)/g); // 带全局修饰符
// ["a11", "a22", "a33"]
s.match(/(\d+)/); // 不带全局修饰符, 等价于 /(\d+)/.exec(s)
//  ["11", "11", index: 2, input: "aa11aa22aa33aa", groups: undefined]

matchAll

var iterator = s.matchAll(regexp)

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

  • matchAll,弥补了match正则使用g全局修饰符,不返还捕获项问题。
  • iterator,该迭代器可转化为二维数组,二维数组里的数组包含捕获组信息和捕获组子字符串。
let regexp = /t(e)(st(\d?))/g;
let str = 'test1test2';
var arr = [...str.matchAll(regexp)]
arr[0]
// ["test1", "e", "st1", "1", index: 0, input: "test1test2", groups: undefined]
arr[1]
// ["test2", "e", "st2", "2", index: 5, input: "test1test2", groups: undefined]

search

var index = s.search(regexp)

返回字符串中,正则首页匹配的子字符串的索引,未匹配则返回-1

var str = "hey JudE";
var re = /[A-Z]/g;
str.search(re); // 4, 大小J在字符串中的索引是4

split

var array = str.split(seprator)

  • seprator分隔符可以是字符串也可以是正则表达式
  • 返回风格后的数组
  • seprator''空字符串,则返回原字符串的每个字符组成的数组
var str = "192:172:112:12"
var str2 = "111+334-90"
var str3 = "abcdefg"
str.split(":"); // ["192", "172", "112", "12"]
str2.split(/[+-]/); // ["111", "334", "90"]
str3.split(''); // ["a", "b", "c", "d", "e", "f", "g"]

replace

str.replace(regexp|substr, newSubStr|function) 详细用法见 String.prototype.replace() - JavaScript | MDN

  • 第一个参数可以是正则表达式或字符串,第一个参数匹配的内容会被第二个参数替换
  • 第二个参数,用于替换第一个参数在原字符串匹配的结果

返回替换后的字符串,原字符串保持不变 需要全局匹配替换,需要在正则表达式使用修饰符g

调换名字顺序

var name = "John Smith"
var reg = /(\w+)\s(\w+)/
// $1是引用第一个捕获组子字符串 $2是访问第二个捕获组的子字符串
var newname = name.replace(reg, '$2 $1')
// => "Smith John"

第二个参数是函数

function styleHyphenFormat(propertyName) {
  // 把匹配的子串转换成小写,并在前面加上连接符‘-’
  function upperToHyphenLower(match) {
    return '-' + match.toLowerCase();
  }
  return propertyName.replace(/[A-Z]/g, upperToHyphenLower);
}
// 属性名转换函数
styleHyphenFormat("paddingTop")
// => 'padding-top'

正则应用案例

银行号码添加空格

给银行号码添空格,4位分隔符 6216906123453724904 => 6216 9061 2345 3724 904

替换法

  • /(\d{4})/g:给字符串分组,从左往右4个一组
  • str.replace(/(\d{4})/g, '$1 '):引用捕获组,在后面添加空格字符串
  • str.replace(/(\d{4}\B)/g, '$1 '):如果刚好分组到最后一个字符串,则添加一个非单词边界\B,使其不匹配
"6216906123453724904".replace(/(\d{4}\B)/g, '$1 ')
// 6216 9061 2345 3724 904

插入法

插入法的难点在于,要匹配到插入位置,找到要用哪个断言,进行加工

  • 1、由于要匹配4位数字后面的位置,即空白需要用到后行断言(?<=y)xx为空白可不写,y为4位数字吗,正则雏形如下:
const str = "6216906123453724904"
const regexp = /(?<=(\d{4}))/g;
str.replace(regexp, ' ')
// "6216 9 0 6 1 2 3 4 5 3 7 2 4 9 0 4 "
  • 2、使用'^',标记分组的开始
const str = "6216906123453724904"
const regexp = /(?<=^(\d{4}))/g;
str.replace(regexp, ' ')
// "6216 906123453724904"
  • 3、四位数字分组有多组,则在后面加上量词+
const str = "6216906123453724904"
const regexp = /(?<=^(\d{4})+)/g;
str.replace(regexp, ' ')
// "6216 9061 2345 3724 904"
  • 4、如果刚好匹配到最后一个会在结尾添加一个空格,使用\B添加非单词边界,限定匹配最后一组
const str = "62169061234537249048"
const regexp = /(?<=^(\d{4})+)/g;
str.replace(regexp, ' ')
// "6216 9061 2345 3724 9048 "

const regexp2 =  /(?<=^(\d{4}\B)+)/g;
str.replace(regexp, ' ')
// "6216 9061 2345 3724 9048"
  • 5、最后得出/(?<=^(\d{4}\B)+)/g正则表达式

千分位分隔符

把整数字符串添加千分位分隔符,如12193322=>12,193,322

var str = '12193322'
var reg = /(?=(\B\d{3})+$)/g
str.replace(reg, ',');
// => '12,193,322'

如果不用正则还可以使用Stirng.prototype.toLocaleString('en-US') 快速转换数字为千分位分割的字符串

如果是带小数的字符串,如何用正则转换呢89887.12211=>89,887.1211

// 参考
var reg = /\B(?=(\d{3})+(?=\b))(?<=\b(?<!\.)\d*)/g

str.replace(reg, ',')

全国统一社会信用代码

按照编码规则: 统一代码为18位,统一代码由十八位的数字或大写英文字母(不适用I、O、Z、S、V)组成,由五个部分组成:

  • 第一部分(第1位)为登记管理部门代码,9表示工商部门;(数字或大写英文字母)
  • 第二部分(第2位)为机构类别代码;(数字或大写英文字母)
  • 第三部分(第3-8位)为登记管理机关行政区划码;(数字)
  • 第四部分(第9-17位)为全国组织机构代码;(数字或大写英文字母)
  • 第五部分(第18位)为校验码(数字或大写英文字母)
var a = /[^_IOZSVa-z\W]{2}\d{6}[^_IOZSVa-z\W]{10}$/g;
var b ="91440101739737166A"
console.log(a.test(b))

参考链接:www.cnblogs.com/MyOceansWeb…

规则:参考链接: www.bbsmax.com/A/GBJreMxRz…

姓名

  • 中文姓名包括小数民族 /^[\u4E00-\u9FA5\uf900-\ufa2d·s]{2,20}$/
  • 中文姓名+英文姓名 /^[\u4E00-\u9FA5A-Za-z\s]+(·[\u4E00-\u9FA5A-Za-z]+)*$/

兼容性问题

截屏2020-06-01 下午3.38.28.png

Safari 浏览器不支持断言查找(前瞻后顾),会出现如上错误。 如果正则表达式中包含零宽断言的话 , 在安卓手机上正常 , 但是在ios上会报错

正则可视化工具

参考资料