从 例子 开始 入门 正则 表达式(二)

241 阅读4分钟

前言

  在上一篇中我们大概讲述了一些基本的基础案例,在这些 正则的 例子中,大部分是验证相关的规则,比如 手机号,比如 邮箱, 比如时间这一类的例子,尽然我们之前说要啃这块骨头,那硬着头皮 我也想继续更新下去,所以今天主要讲一些比较有算难度的例子。
  首先我们先看这个需求:

在字符串的首尾加上 ";" 号,如:'hello' 变 ';hello;';

我们先试一下 js 是怎么解决的:

const addSymbol = (symbol, val) => `${symbol}${val}${symbol}`;
addSymbol(';', 'hello'); //";hello;"

好像也还可以的。并不是很麻烦。可用正则 怎么 解决。这时候,可能想到的是 replace。没错,思路是对的。这时候需要了解一个东西,就是正则的位置。 在上一节中我学习到了这两个关于位置的特殊字符。

  • ^(脱字符)匹配开头,在多行匹配中匹配行开头。

  • $(美元符号)匹配结尾,在多行匹配中匹配行结尾。

可是开头真的是第一个字符串,结尾真的是最后一个字符串吗,其实不是的。而是开头的第一个字符串之前的,结尾最后一个字符串之后的, 这么说可能有点晕,我大概讲下,我的理解,就用 hello举个例子:

  • hello ====>>> △h△e△l△l△o△ 三角的地方其实就是位置。那我们的 ^ 和 $ 其实也就是指 第一个△ 和 最后一个 △

所以 hello 其实在正则里其实也等价于 ''+'h'+''+'e'+''+'l'+''+'l'+''+'o'+ '';
那我们再来回头看看这个例子。要求我们做一个首尾的替换。

所以我们这个需求的 正则 其实应该是 这样写的:

var result = "hello".replace(/^|$/g, ';');
console.log(result); // ";hello;"

分析: 我们看正则其实也很简单 : 首先先抛出一个概念,叫 g 这个参数这个参数的意思是全局匹配。稍后我们详解一下

  • ^匹配开头,$匹配结尾。
  • | 或者

那也就是说 这里代表的是 开头 或者 结尾 全局匹配。进行替换 所以,导致我们的结果是 ";hello;";

我当时很困惑,这个 | 和 g 究竟为什么,那我们来看看 g 这个参数。上述例子弱不加 g 会怎么样。

var regex = /^|$/;
"hello".replace(regex, ';') // ";hello"

可以看到,没有参数 g 的 时候只匹配了 开头,而忽略了 结尾,也就是说他是惰性的,匹配到一个,就不会继续向下匹配了。再看个例子 :

var reg = /ab/g
var string = 'dssaabsdsdabasdab';
console.log(string.match(reg)); //["ab", "ab", "ab"]

但如果是这样:

var reg = /ab/
var string = 'dssaabsdsdabasdab';
console.log(string.match(reg)); //["ab"]

所以我们大概明白了,g 参数是惰性匹配, 一旦匹配到一个符合结果的,就不会再向下匹配了。所以再 回手 掏。。不对,回首看之前的例子,大概就了解了,是全局搜索, 开头的匹配或者结尾的匹配。

我们在看下一个例子。

要求多行匹配,每行首尾加入“;”

var result = "hello\nworld".replace(/^|$/gm, ';');
console.log(result);
/**
;hello;
;world;
*/
  • m的参数 代表换行匹配

我们刚刚 说了 开头和 结尾匹配插入,那么中间的位置应该怎么办,我们再来看一个例子,比如说,

[music]abc.mp3

var result = "[music]abc.mp3".replace(/\b/g, '#');
console.log(result); //[#music#]#abc#.#mp3#

/b 什么东西,我们来大概看看。

  • \b是单词边界,具体就是\w和\W之间的位置,\w和\W的位置,也包括\w和^之间的位置,也包括\w和$之间的位置。

那有 \b 当然就有 \B 了。就是取反了

var result = "[music]abc.mp3".replace(/\B/g, '#');
console.log(result);//"#[m#u#s#i#c]a#b#c.m#p#3"
  • 注意

此处,我们需要注意一个坑,也是我无意间实验出来的。
看这个例子:

//中文
var result = "[music]男孩.mp3".replace(/\b/g, '#');
console.log(result); //[#music#]男孩.#mp3#

//英文
var result2 = "[music]abc.mp3".replace(/\b/g, '#');
console.log(result2);// [#music#]#abc#.#mp3#

我们发现了一些问题,之前说 \b 是 \w和\W之间的位置,可是换成中文后,发现并没有 # 号, 也就是说 他是 \W 和 \W。其实很好理解,我们在看下 \w 代表什么:

  • \w就是[0-9a-zA-Z_]。表示数字、大小写字母和下划线。记忆方式:w是word的简写,也称单词字符。

而这里的中文的是 \u0391-\uFFE5 所以 是 \W。

这时候我们再来看一个例子,如果我没有记错的 话 ,这个 🌰 是我刚刚入职公司那时的笔试面试题,解这道题的时候我当时并不会正则,是用 js 去解决的,嗯还是很怀念,那我再用js先解一遍。

change函数 ,完成金额的划分。比如 12345678 => 12,345,678 再如 123456789 => 123,456,789

//比如 12345678 => 12,345,678 再如 123456789 => 123,456,789
		const changeNumber = (number) => {
			//先转成字符串
			const strNum = String(number);
			//处理字符添加 ,
			const addSymbol = (val) => {
				let str = '';
				for (var i = 0; i < val.length; i++) {
					if ((i+1) % 3 === 0) {
						str += `${val.charAt(i)},`
					} else {
						str += val.charAt(i);
					}
				}
				return str.substring(0, str.length -1 );
			}

			const main = (num) => fn => {
				const startNum = num.length % 3;
				let valNum = num;
				let startStr = '';
				if (startNum !== 0) {
					valNum = num.substring(startNum, num.length);
					startStr = `${num.substring(0, startNum)},`;
				} 
				const result = `${startStr}${fn(valNum)}`;

				return result;
			}

			return main(strNum)(addSymbol);
		}

		console.log(changeNumber(12345678)); //12,345,678
		console.log(changeNumber(123456789)); //123,456,789

js 大概是这样实现的,可能大家还有更好的解法,但是鄙人不才,暂时就这样写了,可以看到这种需求用 js 来写的话真的非常非常麻烦,如果用 正则 的话, 其实只要 一行就可以了。

var result = "12345678".replace(/(?=(\d{3})+$)/g, ',')
console.log(result); // => "12,345,678"

分析

这个正则看起来一头雾水,没关系我们来一起慢慢分析。

  • ?=,如 ?=p 表达的意思是就是 p 前面的位置。如:
var result = "apple".replace(/(?=p)/g, '#');
console.log(result); //a#p#ple

所以这里的 ?=(\d{3})的意思是 ?=([0-9]{3,3}) 其实就是说在3个为一组,至少出现一次 所以用了 + 号,一直到 结尾。全局匹配。但到这我其实这里有个问题,就是为什么正常会 这么智能, 为什么是 12,345,678 逆向的 , 而不是 123,456,78 正向的去替换。原因其实在这里 ?=***$:

  • (?=exp) 匹配exp前面的位置
  • (?<=exp) 匹配exp后面的位置

看完了这个概念,大概有想法了,看下面这个需求,匹配 :

比如 12345678 => 123,456,78 再如 123456789 => 123,456,789

首先我们第一个想到的 肯定是 ?<= 我们来看一下

"12345678".replace(/(?<=^(\d{3})+)/g, ',')//"123,456,78"

我们分析一下:

  • ?<= 查找向后的位置。
  • ^ 开头开始查找
  • +至少出现一次

这时候我们还会发现有个问题 例如 123456789

"123456789".replace(/(?<=^(\d{3})+)/g, ',')//"123,456,789,"
"123456789".replace(/(?=(\d{3})+$)/g, ',')//",123,456,789"

发现还有多一个 “,” 号,这时候我们想办法把他干掉:

"123456789".replace(/(?!$)(?<=^(\d{3})+)/g, ',')//"123,456,789"
'123456789'.replace(/(?!^)(?=(\d{3})+$)/g, ',')//"123,456,789"

分析: ?!$、?!^ ?!取反, 我个人理解其实就是排除的感觉.所以会将这里的 ","干掉。下一篇我们会有更多的例子挑战