JS中的正则

什么是正则?

RegExp: regular expression
按照某种规则去匹配合适条件的字符串


新建正则表达式:
一、字面量形式

var reg = /xyz/

二、使用RegExp构造函数

var reg = new RegExp('xyz')
注意:
1.可以接受第二个参数,全局标志
var reg = new RegExp('xyz','gim')
2.有时需要双重转义字符
var reg = new RegExp('\\wxyx\\w') // \w单词字符

实例属性:

1.修饰符,布尔值,

RegExp.prototype.ignoreCase:返回一个布尔值,表示是否设置了i修饰符。
RegExp.prototype.global:返回一个布尔值,表示是否设置了g修饰符。
RegExp.prototype.multiline:返回一个布尔值,表示是否设置了m修饰符。
注意都是只读的

2.与修饰符无关的属性

(1)RegExp.prototype.lastIndex:返回一个整数,表示下一次开始搜索的位置。该属性可读写,但是只在进行连续搜索时有意义
(2)RegExp.prototype.source:返回正则表达式的字符串形式(不包括反斜杠),该属性只读

实例方法

(1)RegExp.prototype.test()
匹配,有就返回true无就返回false
注意:如果正则表达式带有g修饰符,则每一次test方法都从上一次结束的位置开始向后匹配。

var r = /x/g;
var s = '_x_x';

r.lastIndex // 0
r.test(s) // true

r.lastIndex // 2
r.test(s) // true

r.lastIndex // 4
r.test(s) // false

所以经常会出现同一句代码返回效果却不一样,就是这个lastIndex搞得鬼

(2)RegExp.prototype.exec()
用来返回匹配结果。如果发现匹配,就返回一个数组,成员是匹配成功的子字符串,否则返回null。
注意:如果正则表达式中含有()组匹配,那么返回的结果也会有成功匹配的组成员

var s = '_x_x';
var r = /_(x)/;

r.exec(s) // ["_x", "x"]
image.png

返回的数组还包括以下其他属性:

  • input:整个原字符串。
  • index:整个模式匹配成功的开始位置(从0开始计数)。

如果正则表达式加上g修饰符,则可以使用多次exec方法,下一次搜索的位置从上一次匹配成功结束的位置开始
利用g修饰符允许多次匹配的特点,可以用一个循环完成全部匹配。

var reg = /a/g;
var str = 'abc_abc_abc'

while(true) {
  var match = reg.exec(str);
  if (!match) break;
  console.log('#' + match.index + ':' + match[0]);
}
// #0:a
// #4:a
// #8:a

js字符串与正则表达式相关的方法

  • String.prototype.match():返回一个数组,成员是所有匹配的子字符串。
  • String.prototype.search():按照给定的正则表达式进行搜索,返回一个整数,表示匹配开始的位置。
  • String.prototype.replace():按照给定的正则表达式进行替换,返回替换后的字符串。
  • String.prototype.split():按照给定规则进行字符串分割,返回一个数组,包含分割后的各个成员。

  • match() 方法
    类似于正则对象的exec方法,匹配成功返回一个数组,匹配失败返回null

不同之处在于,match()返回所有成功匹配结果,exec()一次只能返回一个

var s = 'abba';
var r = /a/g;

s.match(r) // ["a", "a"]
r.exec(s) // ["a"]

此外,match()不支持lastIndex属性


  • search()方法

字符串对象的search方法,返回第一个满足条件的匹配结果在整个字符串中的位置。如果没有任何匹配,则返回-1。

'_x_x'.search(/x/)

  • replace()方法
    字符串对象的replace方法可以替换匹配的值。它接受两个参数,第一个是正则表达式,表示搜索模式,第二个是替换的内容。
str.replace(search, replacement)

有g全局标志就全部替换,没有就替换一个

'aaa'.replace('a', 'b') // "baa"
'aaa'.replace(/a/, 'b') // "baa"
'aaa'.replace(/a/g, 'b') // "bbb"
具体实例 模拟实现trim()函数
var str = '  #id div.class  ';
str.replace(/^\s+|\s+$/g, '')
// "#id div.class"

replace方法的第二个参数可以使用美元符号$,用来指代所替换的内容。

$&:匹配的子字符串。
$`:匹配结果前面的文本。
$':匹配结果后面的文本。
$n:匹配成功的第n组内容,n是从1开始      的自然数。
$$:指代美元符号$。
'hello world'.replace(/(\w+)\s(\w+)/, '$2 $1')
// "world hello" 适用于组成员变换位置
'abc'.replace('b', '[$`-$&-$\']')
// "a[a-b-c]c"

注意:replace第二个参数还可以是函数,将每个匹配内容替换成函数的返回值

'3 and 5'.replace(/[0-9]+/g, function (match) {
  return 2 * match;
})
// "6 and 10" 将匹配到的3和5都乘了2

function 的参数有:

  • 匹配的字符串
  • 分组内容,如果有3个分组,这部分就有三个参数都传入
  • index 捕捉到的内容在字符串的位置
  • 原字符串
例子
var prices = {
  'p1': '$1.99',
  'p2': '$9.99',
  'p3': '$5.00'
};

var template = '<span id="p1"></span>'
  + '<span id="p2"></span>'
  + '<span id="p3"></span>';

template.replace(
  /(<span id=")(.*?)(">)(<\/span>)/g,
  function(match, $1, $2, $3, $4){
    return $1 + $2 + $3 + prices[$2] + $4;
  }
);
// "<span id="p1">$1.99</span><span id="p2">$9.99</span><span id="p3">$5.00</span>"

分析一下正则表达式
'<span id="p1"></span>'
$1: (<span id=")
$2: (.*?) 就是p1 后面利用$2在哈希中取值prices[$2]
$3:(">)
$4:(</span>)
很经典,四个组匹配$1-$4


split()方法
字符串对象的split方法按照正则规则分割字符串,返回一个由分割后的各个部分组成的数组。

str.split(separator, [limit])
第一个参数是正则表达式,表示分隔规则,第二个参数是返回数组的最大成员数。
// 非正则分隔
'a,  b,c, d'.split(',')
// [ 'a', '  b', 'c', ' d' ]

// 正则分隔,去除多余的空格
'a,  b,c, d'.split(/, */)
// [ 'a', 'b', 'c', 'd' ]

// 指定返回数组的最大成员
'a,  b,c, d'.split(/, */, 2)
[ 'a', 'b' ]

注意

如果正则表达式带有括号,则括号匹配的部分也会作为数组成员返回。

'aaa*a*'.split(/(a*)/)
// [ '', 'aaa', '*', 'a', '*' ]

匹配规则

1.字面量字符和元字符

  • “字面量字符”:也叫原意文本字符
    /dog/ 就是表示d、o、g三个字母连在一起

  • 元字符

'* + ? $ ^ . | \ ( ) { } [ ]' 需要用\转义

(1).字符

点字符(.)匹配除回车(\r)、换行(\n) 、行分隔符(\u2028)和段分隔符(\u2029)以外的所有字符

(2) 位置字符

^ 开始 $ 结束

(3) 或字符 |

2.特殊字符

特殊字符 含义
\cX 表示Ctrl-[X],其中的X是A-Z之中任一个英文字母,用来匹配控制字、
[\b] 匹配退格键(U+0008),不要与\b混淆。
\n 匹配换行键。
\r 匹配回车键。
\t 匹配制表符 tab(U+0009)。
\v 匹配垂直制表符(U+000B)。
\f 匹配换页符(U+000C)。
\0 匹配null字符(U+0000)。
\xhh 匹配一个以两位十六进制数(\x00-\xFF)表示的字符。
\uhhhh 匹配一个以四位十六进制数(\u0000-\uFFFF)表示的 Unicode 字符。

3.字符类
使用元字符[]来构造一类
[abc] 就是把a、b、c、归为一类

脱字符(^)

如果方括号内的第一个字符是[^],
则表示除了字符类之中的字符,其他字符都可以匹配。比如,[^xyz]表示除了x、y、z之外都可以匹配。

/[^abc]/.test('hello world') // true
/[^abc]/.test('bbc') // false

上面代码中,字符串hello world不包含字母a、b、c中的任一个,所以返回true;字符串bbc不包含a、b、c以外的字母,所以返回false。

如果方括号内没有其他字符,即只有[^],就表示匹配一切字符,其中包括换行符。相比之下,点号作为元字符(.)是不包括换行符的。

var s = 'Please yes\nmake my day!';

s.match(/yes.*day/) // null
s.match(/yes[^]*day/) // [ 'yes\nmake my day']

上面代码中,字符串s含有一个换行符,点号不包括换行符,所以第一个正则表达式匹配失败;第二个正则表达式[^]包含一切字符,所以匹配成功。

注意,脱字符只有在字符类的第一个位置才有特殊含义,否则就是字面含义。

字符类去反^ [^abc] 非a或b或c的内容

范围类 - [a-z] a到z任意字符
中间可以连写[a-zA-Z]

4.预定义模式

预定义字符 含义
\d 匹配0-9之间的任一数字,相当于[0-9]。
\D 匹配所有0-9以外的字符,相当于[^0-9]。
\w 匹配任意的字母、数字和下划线,相当于[A-Za-z0-9_]。
\W 除所有字母、数字和下划线以外的字符,相当于[^A-Za-z0-9_]。
\s 匹配空格(包括换行符、制表符、空格符等),相等于[ \t\r\n\v\f]。
\S 匹配非空格的字符,相当于[^ \t\r\n\v\f]。
\b 匹配词的边界。
\B 匹配非词边界,即在词的内部。

5.重复类+量词

符号 含义
{n} 出现n次
{n,m} 出现n到m次
{n,} 至少出现n次
出现零次或1次(<=1)
+ 出现一次或多次(>=1)
* 出现零次或者多次(任意次)

6.贪婪模式和非贪婪模式
默认情况下是贪婪模式,会最大可能匹配

var s = 'aaa';
s.match(/a+/) // ["aaa"]
上面代码中,模式是/a+/,表示匹配1个a或多个a,那么到底会匹配几个a呢?因为默认是贪婪模式,会一直匹配到字符a不出现为止,所以匹配结果是3个a。

如果想将贪婪模式改为非贪婪模式,可以在量词符后面加一个问号。

var s = 'aaa';
s.match(/a+?/) // ["a"]
上面代码中,模式结尾添加了一个问号/a+?/,这时就改为非贪婪模式,一旦条件满足,就不再往下匹配。
非贪婪模式 含义
+? 表示某个模式出现1次或多次,匹配时采用非贪婪模式。
*? 表示某个模式出现0次或多次,匹配时采用非贪婪模式。
?? 表格某个模式出现0次或1次,匹配时采用非贪婪模式。

7.修饰符

修饰符 含义
g修饰符 表示全局匹配(global),加上它以后,正则对象将匹配全部符合条件的结果,主要用于搜索和替换。
i修饰符 以后表示忽略大小写(ignorecase)。
m修饰符 表示多行模式(multiline),会修改^和$的行为

8.组匹配
正则表达式的括号表示分组匹配,括号中的模式可以用来匹配分组的内容。

var m = 'abcabc'.match(/(.)b(.)/);
m// ['abc', 'a', 'c']

注意,使用组匹配时,不宜同时使用g修饰符,否则match方法不会捕获分组的内容。

var m = 'abcabc'.match(/(.)b(.)/g);
m // ['abc', 'abc']

正向引用:正则表达式内部,还可以用\n引用括号匹配的内容

/(.)b(.)\1b\2/.test("abcabc")
// true \1引用第一个组(.)\2引用第二个组(.)

反向引用 $1 $2

'abc'.replace(/(a)b(c)/,'$2b$1')
//"cba" 

非捕获组:
(?:x)称为非捕获组(Non-capturing group),表示不返回该组匹配的内容,即匹配的结果中不计入这个括号。

var m = 'abc'.match(/(?:.)b(.)/);
m // ["abc", "c"] 第一个组设置成了非捕获组了,所以不在返回的数组里

前瞻

先行断言(正向前瞻):reg(?=y)
先行否定断言(负向前瞻):x(?!y)

就好像你站在原地,向前眺望:
正向前瞻型分组 - 你前方是什么东西吗?
负向前瞻型分组 - 你前方不是什么东西吗?

太拗口了,我喜欢称之为肯定表达式与否定表达式。先举个正向前瞻的例子:

var reg = /kid is a (?=doubi)/

reg.test('kid is a doubi')  // true
reg.test('kid is a shabi')  // false
kid is a 后面跟着什么?如果是doubi才能匹配成功。

而负向前瞻则刚好相反:

var reg = /kid is a (?!doubi)/

reg.test('kid is a doubi')  // false
reg.test('kid is a shabi')  // true

如果前瞻型分组也不会捕获值。那么它与非捕获型的区别是什么?看例子:

var reg, str = "kid is a doubi"

reg = /(kid is a (?:doubi))/
reg.test(str)
RegExp.$1  // kid is a doubi

reg = /(kid is a (?=doubi))/
reg.test(str)
RegExp.$1  // kis is a

可见,非捕获型分组匹配到的串,仍会被外层的捕获型分组捕获到,但前瞻型却不会。当你需要参考后面的值,又不想连它一起捕获时,前瞻型分组就派上用场了。

最后,JS不支持后瞻型分组。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,718评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,683评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,207评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,755评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,862评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,050评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,136评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,882评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,330评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,651评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,789评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,477评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,135评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,864评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,099评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,598评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,697评论 2 351

推荐阅读更多精彩内容