JavaScript 基础入门10
正则表达式
正则表达式
(regular expression)是一个描述字符模式的对象。在JavaScript
中,RegExp
表示正则对象。在开发的工作当中,我们经常需要使用正则表达式强大的模式匹配、文本
检索、以及替换功能。
简单的说,正则表达式
是一种强大的字符串匹配工具。
为什么使用正则表达式?
我们学习正则表达式的原因非常简单,目的就是能够帮助我们快速的匹配字符串。
我们可以通过下面的代码来简单的体会一下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>使用正则表达式的理由</title>
</head>
<body>
</body>
<script>
// 例如我们现在有这样一个需求,将一个字符串当中的数字拿出
var str = "12 ,90,abc,admin-root30369,.....";
// 创建一个空的数组
var arr = [];
var tmp = '';
// 开启循环处理数据
for(var i=0;i<str.length;i++){
// 判断字符串中的内容是否为数字
if(str.charAt(i) >= '0' && str.charAt(i) <= '9'){
tmp += str.charAt(i);
}else{
if(tmp){
arr.push(tmp);
tmp = '';
}
}
}
// 打印
console.log(arr);
</script>
</html>
在上面你的代码中,我们是通过循环处理的形式取出了所有的数字。下面我们再来看下,当我们使用字符串的时候,处理方式是如何的。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>通过正则取出字符串当中的数字</title>
</head>
<body>
</body>
<script>
var str = "12 ,90,abc,admin-root30369,.....";
var n = /\d+/g;
console.log(str.match(n));
</script>
</html>
在上面的两种写法里,都实现了要从字符串中拿取数字的需求,但是你相信大家也应该明白了,我们为什么要学习正则表达式。
正则表达式的应用场景
ok,通过上面的例子,相信大家已经清楚的知道,正则表达式的强大之处,但是你还应该清楚的是,当你学习完正则表达式之后,能够应用在哪些的应用场景当中。
下面是一些常见的应用场景:
- 验证用户数据
- 采集程序/爬虫
- 符号匹配
- 验证网页信息
- ....
例如我们通过正则表达式,可以验证用户输入的账号、密码、邮箱等等的信息是否符合规则,也可以将正则应用在爬虫领域来获取我们想要的数据。在比如我们在聊天的过程中使用的聊天表情,
其实也是可以通过正则来实现匹配的。
如何创建一个正则表达式
一般情况下,我们定义一个正则表达式,可以采用下面的三种形式:
<script>
// 定义一个正则表达式的三种方式:
var re1 = /abc/; // 定义正则最简单的方式,通过直接量的形式直接创建. 两个斜杠当中就是正则的内容.
var re2 = new RegExp(/abc/); // 定义正则的第二种形式,也可以变化成下面的写法
var re2_two = new RegExp("abc"); // 等同于上面的写法
var re3 = RegExp("hello"); // 使用的是转换函数创建的正则
</script>
在上面的代码中,我们通过三种形式分别创建了正则表达式,无论是从哪个方面来说,都推荐使用第一种写法。
基础语法
正则表达式的学习往往没有经验的同学不知道该如何下手,下面我们将通过一个个小案例的形式来带着大家完成正则表达式的学习。
首先,我们假设要匹配出字符串中的a,我们可以采取如下的代码:
<script>
// 创建一个用于测试的字符串
var str = "abcdefga";
// 通过RegExp来创建一个正则用于测试,需要匹配字符串a
// 参数1 'a' 表示要匹配字符串a 参数2 i 表示不区分大小写
var re = new RegExp('a','i');
// 通过test方法来进行测试
console.log(re.test(str)); // true
</script>
当然在上面的代码中,我们可以将构造函数创建正则的写法替换成最简单的直接量的写法,例如:
var re = /a/i;
两条斜线之间写的是正则表达式的内容,斜线后面写的是一些辅助的参数。
通常情况下,正则表达式可以和字符串对象当中的一些方法进行配合:
例如,当我们使用字符串中的search
方法的时候,使用方式大致如下:
<script>
var str = 'abcfefg';
console.log(str.search('b'));// 返回1 ,返回的是字符串b所在的位置
// 我们也可以将search的参数换为正则表达式
console.log(str.search(/b/)); // 同样返回1
</script>
具有特殊意义的转义字符
正则表达式当中,存在一些转义字符,在解析之后的意义和字符本身的意义并不相同。例如\d
代表数字。
<script>
var str = "abs idji 12dkkasd sdkjaks";
var re = /\d/;
console.log(str.search(re)); // 9
</script>
常用的转义字符:
- . 匹配任意字符 除了\n
- \d 匹配数字
- \w 匹配数字字母下划线
- \s 匹配所有的空白字符
- \D 匹配非数字
- \W 除了字母数字下划线
- \S 匹配所有可见字符
量词
在正则表达式当中,存在一些量词,用来表示数量的意思。
我们还是回到刚才的案例,假如我们希望能够匹配全局的数字。可以采用match方法和正则相互配合。
例如:
<script>
var str = "abs idji 12dkkasd sdkjaks";
var re = /\d/;
console.log(str.match(re)); // 1 结果只找到了第一个数字
</script>
通过上面的正则,我们只找到了第一个数字,但是我们希望找到所有的数字,我们就需要在正则表达式的后面加上一个修饰字符g
。
例如:
var re = /\d/g;
当我们将正则修改为上面的代码后,我们发现,正则表达式的确帮助我们找到了我们要找的数字。返回结果为1和2.
但是如果我们需要将数字成组的找出,那么我们就需要通过+
这个量词来设置匹配次数。
例如:
var re = /\d+/g;
此时,通过上面的正则,我们的返回值就变成了12
。
上面的代码中,我们用的+
,表示匹配一次或者多次。
除了使用上面说的+
以外,还可以采用{n,m}
这种写法来表示次数。其中n表示最少次数,m表示匹配最多次数。
上面的这种写法还存在下面的几种变形:
- {n,} 最少出现n次,最多不限
- { , m} 最少不限,最多m次
- {n} 正好n次
常用的量词简写:
-
- 一次到多次
-
- 0次到多次
- ? 0次到1次
小练习:
我们来实现一下qq号的匹配:
var str = "我的qq号是21102944842,你的是30369090吗";
var re = /[1-9]\d{4,11}/g; // qq号第一位不能是0
console.log(str.match(re));
通过replace方法结合正则实现内容替换
例如:
var str = "abcdefg";
var info = str.replace('a','p');
console.log(info);
在上面的代码中,我们通过replace 方法将字符串中的a替换成p。
虽然上面的代码实现了我们的需求,但是却存在一定的缺陷,例如字符串中假如存在多个a的时候,那么通过上述的代码,将只能替换第一个a。
这个时候我们可以通过正则来解决问题。
var str = "abcadefg";
var info = str.replace(/a/gi,'p');
console.log(info);
字符类
我们可以通过[]来实现字符类。
我们以/a[bc]d/
为例,将会匹配到abd,acd
例如:
var str = "abd and acd and ad and abcd";
var re = /a[bc]d/g;
console.log(str.match(re)); // abd acd
上面的代码最终匹配的结果是 abd 和 acd。
也就是说,此时,[]表示的意义是或者
。
[ab] 表示的是a 或者 b,如果是[a,b]表示的是a或者,或者b。
我们对于字符类较为常用的写法有[a-z][A-Z][0-9]
三种,也存在组合使用的写法[a-zA-Z0-9]
。
字符类的另外一种用法是[^a]
,表示不包括方括号内的内容。
源码匹配:
我们下面来完成一个小练习,下面是在网站上找到的一段小说网站的源码:
<div id="content"> 沧桑之声带着威严,回荡整个法兵系,大殿外所有听到的学子,无不心神一震,尤其是那些之前幸灾乐祸之人,更是睁大了眼睛嘴巴都合不拢,有些不敢相信。
<br />
<br /> 一时之间,大殿外一片静寂,所有的目光都落在了王宝乐身上,看着他昂首挺胸的走来,那一身红色的特招学袍,这一刻似乎格外的显眼。
<br />
<br /> 众人下意识的退后,让开了一条道路,望着远去的王宝乐,许久之后阵阵吸气声以及哗然声,骤然爆发。
<br />
<br /> “竟然没事!”
<br />
<br /> “这怎么可能,他不是作弊了么,居然说没有违规?”
<br />
<br /> 在这哗然中,柳道斌也站在人群里,此刻同样被震动,不由得脑海里浮现出王宝乐在那考核里的一幕幕英武以及学堂内他出人意料取出大喇叭的一幕。
<br />
<br /> “高深莫测啊!”半晌之后,柳道斌深吸口气,顿时就觉得王宝乐这里能成为特招,绝非侥幸,实在是他在王宝乐身上,感受到了一股与众不同的特质。
<br />
<br /> 不仅是柳道斌这里有所震动,在这大殿外那之前带着王宝乐来此地的院纪部学长,相互看了看,也都看到了彼此目中的不可思议。
<br />
<br /> 在这众人的哗然中,当王宝乐回到了洞府后,关于他被道院澄清没有违规的事情,已然通过灵网,传遍整个下院岛,所有关注此事之人,无不吃惊疑惑。
<br />
<br /> 一时之间,王宝乐的名字再次于灵网上霸屏,而此刻的王宝乐,正坐在洞府的露台上,得意的看着灵网,与之前的自黑心态不同,此刻的他看着自己的人气节节攀升,很是欣慰。
<br />
<br /> “想要成为联邦高官,民意是极为重要的,看来现在我已经具备了一定的基础了。”王宝乐眉飞色舞,只觉得自己距离梦想又近了一步。
<br />
<br /> “不过我也不能掉以轻心啊。”王宝乐脑海浮现出之前大殿内的黑衣中年,对方那狠辣的言辞,致自己于死地的举动,让王宝乐内心一凛。
<br />
<br /> “此人必定是下院岛的高官,如果不知道他的具体身份,我会很被动的……”王宝乐想到这里,赶紧去灵网上寻找线索,直至黄昏降临时,他终于找到了此人的身份资料,可呼吸却急促起来。
<br />
<br /> “这……副掌院!天啊……”王宝乐内心咯噔一声,揉了揉眼睛确定自己没看错后,他顿时紧张起来,实在是对方的身份太高,下院岛的副掌院,这在王宝乐看去,已经是相当的高度了。
<br />
<br /> “我没得罪他啊,难道我们家祖上有我不知道的秘密……曾经得罪了他?”王宝乐胡思乱想,很是头痛,可半晌之后,想起自己研究的那些高官自传,他目中露出坚定。
<br />
<br /> “几乎所有高官,他们这一生都会遇到一个又一个政敌,可以说他们的道路,就是与政敌的一次次斗争中越走越高!”
<br />
<br /> “这副掌院,看来就是我王宝乐此生,第一个政敌啊!”将对方定位成自己的政敌后,王宝乐顿时就不紧张了,反倒是斗志盎然,开始琢磨自己特招学子的优势。
<br />
<br /> 他之前那些天虽没怎么出门,可通过灵网早就知道了特招学子的一些特权,其中有一条就是可以去所在系的藏宝阁,免费借取一样法器,为期五年。
<br />
</div>
在上面的源码中,存在着很多的标签和字符,我们尝试着通过正则将这些内容去除。
首先,我们需要了解一个符号的特殊用处,就是.
,在正则当中,这个.
可以代指任何东西,英文、数字、符号等等。
我们不妨来想一下标签的内容,大体如下:
var re = /<.+>/g;
我们可以尝试着将上面的正则应用到demo当中来看一下效果:
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title></title>
</head>
<body>
源码:
<textarea rows="30" cols="50" id="txt1">
</textarea>
<button id="btn">点击过滤</button>
<textarea rows="30" cols="50" id="txt2">
</textarea>
</body>
<script>
// 获取元素
var oTxt1 = document.getElementById('txt1');
var btn = document.getElementById('btn');
var oTxt2 = document.getElementById('txt2');
btn.onclick = function(){
var re = /<.+>/g;
oTxt2.value = oTxt1.value.replace(re,'');
}
</script>
</html>
当我们测试上面代码的时候,发现可以成功的将网页的内容去掉标签。但是如果需要过滤的内容为类似下面的格式:
<><>hello,world<><>
当要过滤的内容如下,我们发现过滤的结果为空,为什么会出现这种情况呢,原因是因为触发了贪婪模式
。
说的通俗一点,就是正则会尽可能的按照最大的长度匹配。
那么此时正则实际上把我们复制的内容从最开始的< 一直到最后的> 都当成了一块,并且替换为了空。
那么对于此种情况怎么解决呢?
我们从实际的角度去想:
目前的正则是: /<.+>/g
在一个正常的html标签中不可能再次出现<>,如 <<>> 。
所以我们可以把正则表达式改成 /<[^<>]+>/g。
将我们案例当中的代码改为新的正则表达式,发现已经可以成功的避免贪婪模式。
贪婪模式
贪婪模式与非贪婪模式影响的是被量词修饰的子表达式的匹配行为,贪婪模式在整个表达式匹配成功的前提下会尽可能多的去匹配,而非贪婪模式则是在整个匹配模式成功的前提下,尽可能少的去匹配。
练习 邮箱验证
我们常见的邮箱一般是:admin@qq.com
这种格式,我们可以将邮箱拆分成几个部分来看,@
前面的部分,后面的部分以及点和最后的后缀。
admin 数字字母下划线
@
qq 英文或者数字
. 需要转义的一个点
com 英文,通常是2-3位
根据上面的分析,我们的正则表达式大体为/\w+@[a-z0-9]+\.[a-z]{2,3}/
.
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title></title>
</head>
<body>
<input type="text" id="test">
<button id="btn">点击</button>
</body>
<script>
// 获取元素
var test = document.getElementById('test');
var btn = document.getElementById('btn');
btn.onclick = function(){
var re = /\w+@[a-z0-9]+\.[a-z]{2,3}/;
if(re.test(test.value)) {
console.log("邮箱正确");
}else {
console.log('邮箱错误');
}
}
</script>
</html>
我们在这个时候已经基本可以判断邮箱了,但是依然会出现问题,在邮箱的开头和结尾输入违规的内容时,会发现依然显示对了,这种情况的原因是因为在正则判断中,只要在字符串中存在符合的内容,正则就会判断是对的,哪怕除了符合正则的要求以外还存在不符合的内容。
那么我们怎么才能让正则在判断的时候,判断的是一个整体而不是局部呢?
需要用的我们正则当中的定界符 ^ $
在我们的正则开始位置加^ ,在正则结束的位置加上$,这样,正则在进行判断的时候,就会把输入的全部内容当作一个整体进行一个判断。
Tip:需要注意的是,^在字符类里代表了否定,但是放在字符类外,则表示定界符.
var re = /^\w+@[a-z0-9]+\.[a-z]{2,3}$/;
上面的正则就可以正确的匹配邮箱了。
中文匹配
当我们在一些特殊的网站建设当中,往往会碰到一些需要对中文进行匹配的情况,那么我们该如何对中文进行匹配呢?
首先,我们要先了解一下下面的这个东西:
[\u2e80-\u9fff] 这个代表了可以在计算机中打出的第一个汉字到最后一个汉字。
我们也可以在网上查看一下究竟是什么字?
你会发现,第一个字代表了一字 ,第二个字代表了 龥字 。
那么我们怎么通过这两个编码进行匹配中文呢?
<script>
var str = "hello,world,这是测试";
var re = /[\u2e80-\u9fff]+/g;
var info = str.match(re);
console.log(info); // 这是测试
</script>
模式单元
在正则当中,原子是最基本的组成部分,我们输入的任何一个字符都是一个原子。原子是正则当中最小的单位。
在正则当中,()
被称之为模式单元。通过模式单元可以实现非常多的功能。
1、改变优先级
例如下面的代码:
console.log("jack".match(/jack|rose/)); // jack
而一旦我们使用了模式单元:
console.log('jack'.match(/jac(k|r)ose/))
那么上面的代码将不会匹配到内容,正确的会被匹配到的内容为jackose
或者jacrose
。
2、将多个原子视为一个原子
例如:
console.log("dds".match(/dds/));// 返回dds
但是如果我们需要匹配多个dds,代码就可以改成下面这样:
console.log("ddsddsdds".match(/(dds){1,3}/));// 返回ddsddsdds
3、将内容存储到内存中
通过模式单元我们可以将匹配到的内容存储到内存中。
console.log('aaa'.match(/aaa/)); // aaa
// 我们如果改为下面这种写法
console.log('aaa'.match(/(a)(a)(a)/)); // 0:aaa 1:a 2:a 3:a 内容被存储到了内存之中
当内容被存储到内存中之后,我们可以通过索引值的形式进行访问。
我们可以通过?:
的形式来让元素不存在内存中。
console.log('aaa'.match(/(?:a)(a)(a)/));// 0:aaa 1:a 2:a