正则表达式

276 阅读3分钟

链接

重点关注search matchAll replace这三个api

基本概念

  • RegularExpression <===> RegExp
  • full match 就是按照你给的模式和字符串所匹配得到的结果
  • group1 group2 .. 就是捕获组,什么是捕获后面写了
  • 正则表达式是用于匹配字符串中字符组合的模式,不属于某一种编程语言
  • 在 JavaScript中,正则表达式RegExp也是一种对象的类型
  • 学写模式是一回事,把模式交给JS RegExp正则表达式对象上不同的api去执行是另外一回事,这属于API的学习

正则在线测试器 与 JS中正则相关的API 的关系

  • 之前很疑惑,为什么在线测试器只要模式中有()就总是会返回捕获组groups
  • 而有些API只有当模式中有全局修饰符g时会返回捕获组groups
  • 甚至有些API不会返回捕获组groups?
  • 答案是因为测试器会给比较全的返回结果,而不同的API会根据作者的想法的不同,让返回结果产生多样性而已

分类

点运算符.

字符集[]

简写字符集\w

量词* + ?

限定数量{}

前后锚点$ ^

复杂条件(断言)

  • 断言肯定是要带()的
  • 正先行断言: 模式1 正先行断言 模式2 用于筛选,筛选条件为 模式1之后必须满足模式2 才算匹配成功模式1
  • 负先行断言: 模式1 负先行断言 模式2 用于筛选,筛选条件为 模式1之后不得满足模式2,才算匹配成功模式1
  • 正后发断言: 正后发断言 模式1 模式2 用于筛选,筛选条件为 模式2之前必须满足模式1,才算匹配成功模式2
  • 负后发断言: 负后发断言 模式1 模式2 用于筛选,筛选条件为 模式2之前不得满足模式1,才算匹配成功模式2

贪婪匹配与惰性匹配

  • 贪婪匹配是默认模式
  • 假如有一个模式1 与一串字符串
  • 按照模式1去匹配字符串的话
  • 字符串中存在一个长一些的子字符串以满足模式1,但是在这个长一些的子字符串中包含着另一个短一些的子字符串也可满足模式1
  • 贪婪模式指的是会按模式1匹配一个尽可能长的字符串
  • 而惰性匹配则会按模式1匹配一个尽可能短的字符串

多行修饰符m

  • 模式的结尾加一个m
  • 字符串是完全可以包含换行符的,以形成多行的字符串
  • 默认情况下不加m的话,若模式中使用到后锚点$,则只会把最后的末尾当作是结尾去匹配
  • 但如果你加了m,若模式中使用到后锚点$,则会每行的末尾当作是结尾去匹配
let str="The fat
cat sat
on the mat."

全局修饰符g

  • 修全局搜索匹配的意思是不仅仅返回第一个匹配的,而是返回全部

捕获

  • ()圆括号中的内容会被捕获
  • 也就是在运用某些js的api时可以单独拎出来 通过在线示例详细分析什么是捕获
  • 表达式中有三个圆括号,则会产生三次捕获结果
  • 右侧可以看到这三次捕获结果

举例

b* VS (ab)*

  • 星号是量词元字符,只能匹配*之前一位

c.+t

  • 加号是量词元字符,只能匹配*之前一位
  • 因此整句意思是匹配以首字母c开头以t结尾,中间跟着至少一个字符的字符串

(f|c|m)at.?

  • \是转码特殊字符 . 用以匹配字符串中的.
  • [.] 也表示句号

(at[.])$

  • $用于判断整个字符串的结尾
  • 在线示例
  • ^ 与 $ 号用来判断整个字符串的开头和结尾
  • 在线示例

[]叫做字符集标识

  • . 不同于 [.] 前者表示任意一个字符,后者表示句号

\w之类的叫做简写的字符集

  • \w <===> [a-zA-Z0-9_]

JS中的正则表达式api

结论:简单判断true/false就用test() 或者 search()

结论:全局检索用matchAll!

  • search就是返回true/false
  • matchAll就是返回所有结果并且还返回捕获组

声明一个正则表达式对象RE

var re = /你在这里写入模式/;
//你别以为这就不是对象了,我现在打印给你看
let re=/abc/;
console.dir(re);

lastIndex是RE身上最重要的属性

  • lastIndex本身挂在RE对象上,初始值为0,若RE原型方法api(test/exec)未能找到结果,则会重置为0
  • lastIndex需要搭配global标志一起用,且执行了RE的原型方法test() exec()后lastIndex就会被修改

这个例子对于理解lastIndex非常关键

var regexp = /abcd/g;
var str = 'abcdefg';
alert(regexp.test(str));  //返回true 即匹配到了,因此修改了lastIndex 
alert(regexp.test(str));  //返回false 即沿着上次的lastIndex则匹配不到,且将lastIndex重新置0
alert(regexp.test(str));  //true

String.prototype.search() 用法

  • 找到返回index,找不到返回-1
var str="i am jay"; 
var res=str.search("jay");
console.log(res); //5
console.log(str.search("hz"); //-1

String.prototype.match() 用法

写global可以返回多个结果,但就不能使用捕获了

var str = "JavaScript JavaScript";
var re = /JavaScript/g
str.match(re); // 有g所以返回两个'JavaScript'

不使用global才能使用捕获

var str = 'see Chapter 3.4.5.1';
var re = /see (chapter)/i;
var found = str.match(re);
console.log(found);

String.prototype.matchAll() 用法

这个api功能是最全的,返回所有结果的同时可以返回捕获

let re = /test(\d?)/g;
let str = 'test1test2';
console.log(str.matchAll(re)) // 输出的是一个不可打印的可迭代对象
let arr=[...str.matchAll(re)] // 这是特殊写法 需要把特殊对象转成数组
console.log(arr)

String.prototype.replace() 方法

  • 从字符串中按照正则找到匹配物然后替换
let p = 'lazy dog.';
let re = /dog/gi;
console.log(p.replace(re, 'ferret'));
var re = /apples/gi;
var str = "Apples are round, and apples are juicy.";
var newstr = str.replace(re, "oranges");

replace方法中支持特殊的脚本

var re = /(\w+)\s(\w+)/;
var str = "John Smith";
var newstr = str.replace(re, "$2, $1");
console.log(newstr);// Smith, John
'abc'.replace(/b/g, "{?$`$&$'}")
// 结果为 "a{$abc}c",即把b换成了{$abc}

replace方法中支持函数,可以在复杂条件中查找后进行数据处理

"1234 2345 3456".replace(/(\d)\d{2}(\d)/g, function (match, p1, p2, index, input) {
// p1 p2指的就是捕获组里的内容 即第一个圆括号的内容 第二个圆括号里的内容
        console.log([match, p1, p2, index, input]);
        });    
// => ["1234", "1", "4", 0, "1234 2345 3456"]
// => ["2345", "2", "5", 5, "1234 2345 3456"]
// => ["3456", "3", "6", 10, "1234 2345 3456"] 
  • 下面这个例子把borderTop ===> border-top
function styleHyphenFormat(propertyName) {
  function upperToHyphenLower(match) {
    return '-' + match.toLowerCase();
  }
  return propertyName.replace(/[A-Z]/g, upperToHyphenLower);
}
styleHyphenFormat('borderTop')// 'border-top'。

String.prototype.split() 方法也支持正则

RegExp.prototype.exec() 用法

  • 本方法若匹配成功则返回一个对象,若匹配失败则返回null
  • RE对象有g则可多次匹配,无g则只返回第一个结果
  • 始终根据lastIndex作为匹配的起始(lastIndex初始0)
let re=/\s/g;
let string='123456'
let result=re.exec(string)
console.log(result) // null 

RegExp.prototype.test() 用法

  • 本方法若匹配成功则返回true,若匹配失败则返回false
  • RE对象有g则可多次匹配,无g则只返回第一个结果
  • 始终根据lastIndex作为匹配的起始(lastIndex初始0)

lastIndex受 exec() 与 test() 的影响

当re有g时,若找到结果,则lastIndex值是紧挨着匹配结果的index,第二次调用exec( ),它将从lastIndex处再次检索,如果exec()没有发现任何匹配结果,lastIndex重置为0

值得注意的是,第一次调用test( )匹配结束后,若手动将lastIndex重置为0,那么第二次调用test( ),同样可以打印true

var str="java JavaScript java";
var reg=/JavaScript/g;
console.log(reg.test(str)); // true
reg.lastIndex=0;
console.log(reg.test(str)); //依旧 true

全局检索 并获得所有匹配位置的index

老版本的全局检索

  • 以前是用reg.exec()+while遍历实现全局检索
var string = "2017.06.27";
var re = /\b(\d+)\b/g;
var result;
let arr=[];
while ( result = re.exec(string) ) { // 写法较特别 
// 只要能找到结果就会一直下去
// 找不到会赋给result=null自然也就停下了
console.log( result, result.index,re.lastIndex);
arr.push(result.index)
}
console.log(arr)

新版本的全局检索

  • matchAll
  • 注意看ABC三处的结果都是一模一样的!!!
let string = "2017.06.27";
let re = /\b(\d+)\b/g;
let arr=[];
let iterator = string.matchAll(re) // 返回一个可迭代对象
console.log(iterator) // RegExpStringIterator,证明直接打印根本无法访问啊

// 如何访问这个可迭代对象?
// 可迭代对象转数组 方式1 
console.log([...iterator])  // A处

// 可迭代对象转数组 方式2
iterator = string.matchAll(re) // 注意可迭代对象取一次就空了,需要再一次去取
console.log(Array.from(iterator)) // B处

iterator = string.matchAll(re) // 注意可迭代对象取一次就空了,需要再一次去取
for (let n of iterator){
	console.log(`匹配到${n[0]} 起始坐标为${n.index} 末尾坐标为${n.index + n[0].length}.`)
    arr.push(n) 
}
console.log(arr) // C处 

可视化工具如何使用的