JS学习笔记——从入门到熟练掌握正则表达式(全网最细的讲解含元字符、修饰符、正则方法、特殊用法)

不管是在平时工作学习中,还是面试中,经常会碰到使用正则表达式的场景,平时对这方面没有下功夫,都是用的时候现查先用,过后就忘了,所以想写一篇博客来记录学习一下正则表达式。

1、什么是正则

正则表达式(英语:Regular Expression,在代码中常简写为regexregexpRE)使用单个字符串来描述、匹配一系列符合某个句法规则的字符串搜索模式

搜索模式可用于文本搜索文本替换

2、正则的定义

在创建正则表达式的时候有两种方法:

2.1、RegExp构造函数
var pattern = new RegExp("[bc]at","i")

即:接收两个参数:一个是要匹配字符串模式,另一个是可选标志字符串(修饰符)

在脚本运行过程中,用构造函数创建的正则表达式会被编译。如果正则表达式将会改变,或者它将会从用户输入等来源中动态地产生,就需要使用构造函数来创建正则表达式。

2.2、字面量

由包含在斜杠/之间的模式组成:

var pattern = /[bc]at/i

即 :/正则表达式主体/修饰符(可选)

脚本加载后,正则表达式字面量就会被编译。当正则表达式保持不变时,使用字面量创建可获得更好的性能

2.3、区别:

RegExp构造函数字面量创建正则表达式方法的不同之处在于,正则表达式字面量始终会共享同一个RegExp实例,而使用构造函数创建的每一个新RegExp实例都是新实例

3、元字符

正则表达式的基本组成元素可以分为:字符元字符

  • 字符:就是基础的计算机字符编码,通常正则表达式里面使用的就是数字英文字母
  • 元字符:也被称为特殊字符,是一些用来表示特殊语义的字符。如^表示|表示等。利用这些元字符,才能构造出强大的表达式模式(pattern)。

对于最简单的正则表达式,由简单的数字字母组成即可,没有特殊的语义(无须元字符),纯粹就是一一对应的关系。如想在apple这个单词里找到a这个字符,就直接用/a/这个正则就可以。

但是如果想要匹配特殊字符的话,就得使用元字符,所以下面介绍下元字符:

3.1、常用元字符
字符描述
.匹配除换行符以外的任意字符
\转义字符:将下一个字符标记为一个特殊字符、或一个原义字符、或一个 向后引用、或一个八进制转义符
^匹配行的开始(以谁开始
$匹配行的结束(以谁结束
\b匹配单词开始结束
\d匹配数字等价于[0-9]
\s匹配任意的空白符
\w匹配字母数字下划线等价于 [A-Za-z0-9_]
3.2、常用反义元字符
字符描述
[^x]匹配除了x以外的任意字符
[^xy]匹配除了xy字母以外的任意字符
[^a-z]匹配除了a-z以外的任意字符
\B匹配不是单词开头结束的位置
\D匹配任意不是数字的字符
\S匹配任意不是空白符的字符
\W匹配任意不是字母数字下划线的字符([^0-9a-zA-Z_]
3.3、常用范围限定元字符
字符描述
[标记一个中括号表达式的开始(要匹配 [,请使用 \[
{标记限定符表达式的开始(要匹配 {,请使用\{
|指明两项之间的一个选择(要匹配 |,请使用\|
()标记一个子表达式开始结束位置(要匹配(),请使用 \(\)
-指定区间范围/[0-9]/匹配所有数字/[a-z]/匹配所有英文小写字母
[xyz]匹配x或y或z中的任意所有字符
(例如[aeiou]匹配字符串 “google runoob taobao” 中所有e o u a字母)
3.4、常用重复限定元字符
字符描述
?重复0次1次n?匹配0个1个n 的字符串)
+重复1次n次n+匹配1个n个n 的字符串)
*重复0次n次n*匹配0个n个n 的字符串)
{n}重复n
{n,}重复n次或更多次( 至少n次
{n,m}重复n到m次({min, max}介于min次max次之间)
3.5、常用前后选择元字符
字符描述
?=exp1(?=exp2)表示查找 exp2 前面的 exp1
?<=(?<=exp2)exp1表示查找 exp2 后面的 exp1
?!exp1(?!exp2)表示查找后面不是 exp2 的 exp1
?<!(?<!exp2)exp1表示查找前面不是 exp2 的 exp1
3.6、元字符查询表

下表包含了元字符的完整列表以及它们在正则表达式上下文中的行为:

字符描述
\将下一个字符标记为一个特殊字符、或一个原义字符、或一个向后引用、或一个进制转义符
在非特殊字符之前的反斜杠表示下一个字符是特殊字符,不能按照字面理解(例如,前面没有 “” 的 “b” 通常匹配小写字母 “b”,即字符会被作为字面理解,如果前面加了 “\”,它将不再匹配任何字符,而是表示一个字符边界)
在特殊字符之前的反斜杠表示下一个字符不是特殊字符,应该按照字面理解。比如 ‘\\’ 匹配 “\” ,而 “\(” 则匹配 “(”
^匹配输入字符串的开始位置或者输入的开始(例如,/^A/ 并不会匹配 “an A” 中的 ‘A’,但是会匹配 “An E” 中的 ‘A’)
如果设置了 RegExp 对象的 Multiline 属性,即如果多行标志被设置为 true,那么也匹配换行符后紧跟的位置,即匹配’\n` 或 ‘\r’ 之后的位置
当 ‘^’ 作为第一个字符出现在一个字符集合模式时,它将会表示反向字符集
$匹配输入字符串的结束位置(例如,/t$/ 并不会匹配 “eater” 中的 ‘t’,但是会匹配 “eat” 中的 ‘t’)
如果设置了RegExp 对象的 Multiline 属性,即如果多行标志被设置为 true,那么也匹配换行符前的位置,即匹配 ‘\n’ 或 ‘\r’ 之前的位置
*匹配前面的子表达式零次或多次,* 等价于{0,}(例如,zo* 能匹配 “z” 以及 “zoo”)
+匹配前面的子表达式一次或多次,+ 等价于 {1,}(例如,‘zo+’ 能匹配 “zo” 以及 “zoo”,但不能匹配 “z”)
?匹配前面的子表达式零次或一次,? 等价于 {0,1}(例如,“do(es)?” 可以匹配 “do” 或 “does” )
如果紧跟在任何量词 *、 +、? 或 {} 的后面,将会使量词变为非贪婪(匹配尽量少的字符),和缺省使用的贪婪模式(匹配尽可能多的字符)正好相反。例如,对 “123abc” 使用 /\d+/ 将会匹配 “123”,而使用 /\d+?/ 则只会匹配到 “1”。
{n}n 是一个非负整数。匹配前一个字符正好出现 n 次(例如,‘o{2}’ 不能匹配 “Bob” 中的 ‘o’,但是能匹配 “food” 中的两个 o)
{n,}n 是一个非负整数。匹配前一个字符至少出现 n 次(例如,‘o{2,}’ 不能匹配 “Bob” 中的 ‘o’,但能匹配 “foooood” 中的所有 o。‘o{1,}’ 等价于 ‘o+’。‘o{0,}’ 则等价于 ‘o*’)
{n,m}m 和 n 均为非负整数,其中n <= m。匹配前一个字符出现最少 n 次且最多 m 次(例如,“o{1,3}” 将匹配 “fooooood” 中的前三个 o,即使原始的字符串中有更多的o。‘o{0,1}’ 等价于 ‘o?’。请注意在逗号和两个数之间不能有空格)
?当该字符紧跟在任何一个其他限制符 (*, +, ?, {n}, {n,}, {n,m}) 后面时,匹配模式是非贪婪的。非贪婪模式尽可能少的匹配所搜索的字符串,而默认的贪婪模式则尽可能多的匹配所搜索的字符串(例如,对于字符串 “oooo”,‘o+?’ 将匹配单个 “o”,而 ‘o+’ 将匹配所有 ‘o’)
.匹配除换行符(\n、\r)之外的任何单个字符。要匹配包括 ‘\n’ 在内的任何字符,请使用像"(.|\n)"的模式
例如,/.n/ 将会匹配 “nay, an apple is on the tree” 中的 ‘an’ 和 ‘on’,但是不会匹配 ‘nay’。
(pattern)匹配 pattern 并获取(即记住)这一匹配,其中括号被称为捕获括号。在JScript 中则使用 $0…$9 属性。要匹配圆括号字符,请使用 ‘\(’ 或 ‘\)
(?:pattern)匹配 pattern 但不获取(即不记住)匹配结果,也就是说这是一个非获取匹配。这种括号叫作非捕获括号,不进行存储供以后使用。这在使用 “或” 字符 (|) 来组合一个模式的各个部分是很有用。
例如, 'industr(?:y|ies)就是一个比 ‘industry|industries’ 更简略的表达式
例如, /(?:foo){1,2}/。如果表达式是 /foo{1,2}/,{1,2} 将只应用于 ‘foo’ 的最后一个字符 ‘o’。如果使用非捕获括号,则 {1,2} 会应用于整个 ‘foo’ 单词。
(?=pattern)正向肯定预查(look ahead positive assert),在任何匹配pattern的字符串开始处匹配查找字符串。这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。例如,"Windows(?=95|98|NT|2000)“能匹配"Windows2000"中的"Windows”,但不能匹配"Windows3.1"中的"Windows"。预查不消耗字符,也就是说,在一个匹配发生后,在最后一次匹配之后立即开始下一次匹配的搜索,而不是从包含预查的字符之后开始
x(?=y)正向先行断言。匹配’x’,仅仅当’x’后面跟着’y’
例如,/Jack(?=Sprat)/会匹配到’Jack’仅当它后面跟着’Sprat’。/Jack(?=Sprat|Frost)/匹配‘Jack’仅当它后面跟着’Sprat’或者是‘Frost’。但是‘Sprat’和‘Frost’都不是匹配结果的一部分
(?!pattern)正向否定预查(negative assert),在任何不匹配pattern的字符串开始处匹配查找字符串。这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。例如"Windows(?!95|98|NT|2000)“能匹配"Windows3.1"中的"Windows”,但不能匹配"Windows2000"中的"Windows"。预查不消耗字符,也就是说,在一个匹配发生后,在最后一次匹配之后立即开始下一次匹配的搜索,而不是从包含预查的字符之后开始
x(?!y)正向否定查找负向先行断言。匹配’x’,仅仅当’x’后面不跟着’y’
例如,/\d+(?!.)/ 匹配一个数字,仅仅当这个数字后面没有跟小数点的时候。正则表达式/\d+(?!.)/.exec(“3.141”)匹配‘141’而不是‘3.141’
(?<=pattern)反向(look behind)肯定预查,与正向肯定预查类似,只是方向相反。例如,"(?<=95|98|NT|2000)Windows“能匹配"2000Windows"中的"Windows”,但不能匹配"3.1Windows"中的"Windows"
(?<=y)x正向后行断言。匹配’x’,仅当’x’前面是’y’
例如,/(?<=Jack)Sprat/会匹配到’ Sprat ‘仅仅当它前面是’ Jack '。/(?<=Jack|Tom)Sprat/匹配‘ Sprat ’仅仅当它前面是’Jack’或者是‘Tom’。但是‘Jack’和‘Tom’都不是匹配结果的一部分。
(?<!pattern)反向否定预查,与正向否定预查类似,只是方向相反。例如"(?<!95
(?<!y)x负向后行断言反向否定查找。匹配‘x’,仅仅当’x’前面不是’y’
例如, /(?<!-)\d+/ 匹配一个数字。仅仅当这个数字前面没有负号的时候,/(?<!-)\d+/.exec(‘3’) 匹配到 “3”./(?<!-)\d+/.exec(’-3’) 因为这个数字前有负号,所以没有匹配到。
x|y匹配 x 或 y。例如,'z
[xyz]字符集合。匹配所包含的任意一个字符。例如, ‘[abc]’ 可以匹配 “plain” 中的 ‘a’
[^xyz]负值字符集合。匹配未包含的任意字符。例如, ‘[^abc]’ 可以匹配 “plain” 中的’p’、‘l’、‘i’、‘n’
[a-z]字符范围。匹配指定范围内的任意字符。例如,’[a-z]’ 可以匹配 ‘a’ 到 ‘z’ 范围内的任意小写字母字符
[^a-z]负值字符范围。匹配任何不在指定范围内的任意字符。例如,’[^a-z]’ 可以匹配任何不在 ‘a’ 到 ‘z’ 范围内的任意字符
\b匹配一个单词边界,也就是指单词和空格间的位置。例如, ‘er\b’ 可以匹配"never" 中的 ‘er’,但不能匹配 “verb” 中的 ‘er’
使用"moon"举例:/\bm/匹配“moon”中的‘m’;/oo\b/并不匹配"moon"中的’oo’,因为’oo’被一个“字”字符’n’紧跟着;/oon\b/匹配"moon"中的’oon’,因为’oon’是这个字符串的结束部分。这样他没有被一个“字”字符紧跟着;/\w\b\w/将不能匹配任何字符串,因为在一个单词中间的字符永远也不可能同时满足没有“字”字符跟随和有“字”字符跟随两种情况。
\B匹配非单词边界。‘er\B’ 能匹配 “verb” 中的 ‘er’,但不能匹配 “never” 中的 ‘er’
\cx匹配由 x 指明的控制字符。例如, \cM 匹配一个 Control-M 或回车符。x 的值必须为 A-Z 或 a-z 之一。否则,将 c 视为一个原义的 ‘c’ 字符
\d匹配一个数字字符。等价于 [0-9]
\D匹配一个非数字字符。等价于 [^0-9]
\f匹配一个换页符。等价于 \x0c 和 \cL
\n匹配一个换行符。等价于 \x0a 和 \cJ
\r匹配一个回车符。等价于 \x0d 和 \cM
\s匹配任何空白字符,包括空格、制表符、换页符等等。等价于 [ \f\n\r\t\v]
\S匹配任何非空白字符。等价于 [^ \f\n\r\t\v]
\t匹配一个制表符。等价于 \x09 和 \cI
\v匹配一个垂直制表符。等价于 \x0b 和 \cK
\w匹配字母、数字、下划线。等价于’[A-Za-z0-9_]’
\W匹配非字母、数字、下划线。等价于 ‘[^A-Za-z0-9_]’
\xn匹配 n,其中 n 为十六进制转义值。十六进制转义值必须为确定的两个数字长。例如,’\x41’ 匹配 “A”。’\x041’ 则等价于 ‘\x04’ & “1”。正则表达式中可以使用 ASCII 编码
\num匹配 num,其中 num 是一个正整数。对所获取的匹配的引用。例如,’(.)\1’ 匹配两个连续的相同字符
\n标识一个八进制转义值或一个向后引用。如果 \n 之前至少 n 个获取的子表达式,则 n 为向后引用。否则,如果 n 为八进制数字 (0-7),则 n 为一个八进制转义值
\nm标识一个八进制转义值或一个向后引用。如果 \nm 之前至少有 nm 个获得子表达式,则 nm 为向后引用。如果 \nm 之前至少有 n 个获取,则 n 为一个后跟文字 m 的向后引用。如果前面的条件都不满足,若 n 和 m 均为八进制数字 (0-7),则 \nm 将匹配八进制转义值 nm
\nml如果 n 为八进制数字 (0-3),且 m 和 l 均为八进制数字 (0-7),则匹配八进制转义值 nml
\un匹配 n,其中 n 是一个用四个十六进制数字表示的 Unicode 字符。例如, \u00A9 匹配版权符号 (?)

注意事项:

(1) 转义字符\

\代表转义字符,顾名思义,就是让其后续的字符失去其本来的含义

  • 特殊字符之前的反斜杠\表示下一个字符不是特殊字符,应该按照字面理解
  • 非特殊字符之前的反斜杠\表示下一个字符特殊字符,不能按照字面理解

举个例子:

我想匹配*这个符号,由于*这个符号本身是个特殊字符,所以我要利用转义元字符\来让它失去其本来的含义:/\*/

如果本来这个字符不是特殊字符,使用转义符号就会让它拥有特殊的含义。我们常常需要匹配一些特殊字符,比如空格\s,制表符\t,回车\r,换行\n,换页\f,回退符[\b]等,而这些就需要我们使用转义字符来匹配。

(2)正向和负向

  • 负向正向断言的区别,就在于该位置之后的字符能否匹配括号中的表达式

(3)()、[ ]、{ }

  • ()是,主要应用在限制多选结构范围/分组/捕获文本/环视/特殊模式处理
  • () 是为了提取匹配的字符串。表达式中有几个()就有几个相应的匹配字符串
  • [ ]是单个匹配,定义匹配的字符范围,用于字符集/排除字符集/命名字符集
  • {}一般用来表示匹配的长度。比如 \s{3} 表示匹配三个空格,\s{1,3}表示匹配一到三个空格

()[ ]有本质的区别:

()内的内容表示的是一个子表达式,本身不匹配任何东西,也不限制匹配任何东西,只是把括号内的内容作为同一个表达式来处理。例如(ab){1,3},就表示ab一起连续出现最少1次,最多3次。如果没有括号的话,ab{1,3},就表示a,后面紧跟的b出现最少1次,最多3次。另外,括号在匹配模式中也很重要。

[ ]表示匹配的字符在[ ]中,并且只能出现一次,并且特殊字符写在[ ]会被普通字符来匹配。例如[(a)],会匹配(、a、)、这三个字符。

案例:

了解完上面元字符的概念,我们就举一个简单的案例来讲解一下:

例:
var str = "abcd test@runoob.com 1234";
var patt1 = /\b[\w.%+-]+@[\w.-]+\.[a-zA-Z]{2,6}\b/g;
console.log(str.match(patt1));

//['test@runoob.com']

在这里插入图片描述

4、元字符(运算符)优先级

相同优先级从左到右进行运算,不同优先级的运算先高后低。下表从最高到最低说明了各种正则表达式运算符的优先级顺序:

运算符描述
\转义符
(), (?:), (?=), [ ]圆括号和方括号
*, +, ?, {n}, {n,}, {n,m}限定符
^, $, \任何元字符、任何字符定位点和序列(即:位置和顺序)
|替换,“或"操作字符具有高于替换运算符的优先级,使得"m|food"匹配"m"或"food”。若要匹配"mood"或"food",请使用括号创建子表达式,从而产生"(m

5、修饰符(标记)

标记也称为修饰符,正则表达式的标记用于指定额外的匹配策略

标记不写在正则表达式里,标记位于表达式之外,格式如下:

/pattern/flags
5.1、i修饰符

i(ignore):表示不区分大小写,将匹配设置为不区分大小写,搜索时不区分大小写: A 和 a 没有区别。

var str="Google runoob taobao RUNoob"; 
var n1=str.match(/runoob/g);   // 区分大小写
var n2=str.match(/runoob/gi);  // 不区分大小写

console.log(n1) //['runoob']
console.log(n2) //['runoob', 'RUNoob']
5.2、g修饰符

g(global):表示全局匹配,查找所有的匹配项。

var str="Google runoob taobao runoob"; 
var n1=str.match(/runoob/);   // 查找第一次匹配项
var n2=str.match(/runoob/g);  // 查找所有匹配项

console.log(n1) //['runoob', index: 7, input: 'Google runoob taobao runoob', groups: undefined]
console.log(n2) //['runoob', 'runoob']
5.3、m修饰符

m (multi line):表示多行匹配。使边界字符 ^ 和 $ 匹配每一行的开头和结尾,记住是多行,而不是整个字符串的开头和结尾。

g 只匹配第一行m之后实现匹配多行

以下实例字符串中使用 \n 来换行:
var str="runoobgoogle\ntaobao\nrunoobweibo";
var n1=str.match(/^runoob/g);   // 匹配一个
var n2=str.match(/^runoob/gm);  // 多行匹配

console.log(n1) //['runoob']
console.log(n2) //['runoob', 'runoob']
5.4、s修饰符

s:会使特殊字符圆点 .中包含换行符 \n默认情况下的圆点.是 匹配换行符 \n之外的任何字符;但是我们加上 s 修饰符之后, .中包含换行符 \n

var str="google\nrunoob\ntaobao";
var n1=str.match(/google./);   // 没有使用 s,无法匹配\n
var n2=str.match(/runoob./s);  // 使用 s,匹配\n

console.log(n1)//null
console.log(n2)//['runoob\n', index: 7, input: 'google\nrunoob\ntaobao', groups: undefined]

除了上述4中常用的修饰符,在ES6中又增加了y 修饰符u 修饰符,有兴趣的可以点击此处查看

6、正则的常用方法

6.1、正则的方法
6.1.1、test()
  • 作用:在字符串中查找符合正则的内容,若查找到返回true,反之返回false

  • 用法:正则.test(字符串)

  • 参数:字符串

  • 返回值:truefalse

  • test()受到正则全局标识g的影响。每个正则表达式都有一个 lastIndex 属性,用于记录上一次匹配结束的位置,表示开始搜索下一个匹配项的字符位置。如果加g,就会受到这个影响

regexp.test(str)
var regexp = /^1[3-9]\d{9}$/; // 手机号正则
console.log( regexp.test('13588888888') ); // true
console.log( regexp.test('12588888888') ); // false 第2位不满足

(加上全局修饰符g

var re = /^[1-9]\d{4,10}$/gi;
var str1 = "123456";
console.log(re.test(str1));  //true
console.log(re.lastindex)    //6
str2 = "1234567";
console.log(re.test(str2));  //false
6.1.2、exec()

execmatch有很多相似和区别,注意类比学习
探究js正则匹配方法:match和exec

  • 作用: 以一个数组返回正则匹配的结果 。

  • 用法:正则.exec(字符串)

  • 参数:字符串

  • 返回值:存放匹配结果的数组。该数组的内容依赖于是否具有全局标志 g

  • 数组第0项表示匹配到的字符串;数组的index表示匹配项在字符串中的下标位置;数组的input表示应用正则表达式的字符串length表示匹配到的结果个数groups表示当前的正则表达式是否使用分组。未匹配到则返回null

  • exec()受到正则全局标识g的影响,没有全局标识g时,仅返回第一次匹配的结果。每个正则表达式都有一个 lastIndex 属性,用于记录上一次匹配结束的位置,表示开始搜索下一个匹配项的字符位置。如果加g,就会受到这个影响

regexp.exec(str)

(不带全局修饰符g

var reg = /apple/g;
var aaa = 'peach lemon apple banana pear orange';
var match = reg.exec(aaa);

console.log(match)
[
	0: "apple"
	index: 12
	input: "peach lemon apple banana pear orange"
	length: 1
	groups: undefined
]
var a = 'reactjs,angularjs,vuejs,backbonejs';

var reg = /.js/;

var match = reg.exec(a);
console.log(match)
[
	0: "tjs"
	groups: undefined
	index: 4
	input: "reactjs,angularjs,vuejs,backbonejs"
	length: 1
]

可以看出:没有全局修饰符g的情况下,exec仅匹配单次匹配的内容

上面结果返回的是Array的实例,包含几个额外的属性:

第0项:匹配到字符串

第1项:index表示首次匹配上的子串的起始下标

第2项:input,表示源字符串

第3项:length,表示匹配到的结果个数,由于这里不使用全局匹配,只找到首次匹配项就结束了,所以匹配结果只有1个,length也就是1

第4项:groups:undefined,这表示当前的正则表达式没使用分组

(加上全局修饰符g

let a = 'reactjs,angularjs,vuejs,backbonejs';

let reg = /.js/g;

let match = reg.exec(a);

console.log(match);
console.log(match.index);
console.log(reg.lastIndex);

match = reg.exec(a);

console.log(match);
console.log(match.index);
console.log(reg.lastIndex);

在这里插入图片描述
可以看出:有全局修饰符g的情况下,exe会从上次匹配结束的下一位开始匹配,返回本次匹配的内容,直至无可以匹配的内容,返回null

(使用分组,不使用g

var a = "aaa1 bbb2 ccc3";
var reg = /\b(\w+)(\d{1})\b/;

var match = reg.exec(a);

console.log(match);
console.log(match.index);
console.log(reg.lastIndex);

var match = reg.exec(a);

console.log(match);
console.log(match.index);
console.log(reg.lastIndex);

在这里插入图片描述
该表达式中使用了小括号(),在此处的作用为分组。所以exec()的结果是带有分组特征的。返回的数组包含多个元素,第一个元素是以贪婪模式找到的最长的匹配"aaa1",之后的元素依次为该匹配中的第一、第二、第三 …个分组,这里只有2个分组,所以也就是匹配到2个分组结果,也就是”aaa""1"

(使用分组,同时使用全局修饰符g

var a = "aaa1 bbb2 ccc3";
var reg = /\b(\w+)(\d{1})\b/g;
var match = reg.exec(a);

console.log(match);
console.log(match.index);
console.log(reg.lastIndex);

var match = reg.exec(a);

console.log(match);
console.log(match.index);
console.log(reg.lastIndex);

var match = reg.exec(a);

console.log(match);
console.log(match.index);
console.log(reg.lastIndex);

var match = reg.exec(a);

console.log(match);
console.log(match.index);
console.log(reg.lastIndex);

在这里插入图片描述可以看出,当正则中使用全局匹配g,exec每次匹配的结果与没有g的匹配结果一样,然后下一次的匹配结果会从lastIndex向后继续匹配。

6.2、字符串的方法
6.2.1、search()
  • 作用:在字符串搜索符合正则的内容,搜索到就返回出现的位置(从0开始,如果匹配的不只是一个字母,那只会返回第一个字母的位置), 如果搜索失败就返回 -1
  • 用法:字符串.search(正则)
  • 返回值:匹配位置的下标
  • 参数:正则表达式
str.search(regexp)
var str = "hey JudE";
var re = /[A-Z]/g;
var re2 = /[.]/g;

console.log(str.search(re)); 4 //which is the index of the first capital letter "J"
console.log(str.search(re2)); -1// cannot find '.' dot punctuation
 var part = /Box/ig;
 var str = "this is box,is a Box";
 
 console.log(str.search(part)); //8 说明从下标为8开始就匹配到这个字符了Box(不区分大小写)
6.2.2、match()

execmatch有很多相似和区别,注意类比学习
探究js正则匹配方法:match和exec

  • 作用:在字符串内检索指定的值或找到一个或多个正则表达式的匹配,返回存放匹配结果的数组。如果没找到匹配结果返回 null

  • 用法:字符串.match(正则)

  • 参数:正则表达式

  • 返回值:存放匹配结果的数组。该数组的内容依赖于是否具有全局标志 g

  • 数组第0项表示匹配到的字符串;数组的index表示匹配项在字符串中的下标位置;数组的input表示应用正则表达式的字符串length表示匹配到的结果个数groups表示当前的正则表达式是否使用分组。未匹配到则返回null

  • match()受到正则全局标识g的影响,没有全局标识g时,仅返回第一次匹配的结果;有全局标识g时,match会返回所有匹配上的内容

str.match(regexp)
var part = /Box/i;
var str = "this is box,is a Box";

console.log(str.match(part));
[
	0: "box"
	groups: undefined
	index: 8
	input: "this is box,is a Box",
	length: 1
]

第0项:匹配到字符串

第1项:index表示首次匹配上的子串的起始下标

第2项:input,表示源字符串

第3项:length,表示匹配到的结果个数,由于这里不使用全局匹配,只找到首次匹配项就结束了,所以匹配结果只有1个,length也就是1

第4项:groups:undefined,这表示当前的正则表达式没使用分组

(带全局变量g

var part = /Box/ig;
var str = "this is box,is a Box";

console.log(str.match(part)); 
[
	'box',
	'Box',
	length: 2
]

可以看到,这次的返回值仍然是个数组,只不过没有indexinput这2项。length属性同样为匹配到的结果个数,这里显然是2个。

(使用分组,且不使用g

var str = 'Today is the 286th day of 2018, the 108th Thanksgiving Day.';
var results = str.match(/\d+(th)/); //匹配str中首个以数字开头,并且以th结尾的子串
console.log(results);

在这里插入图片描述
由于该正则表达式为:/\d (th) /,该表达式中使用了小括号(),在此处的作用为分组。所以match()的结果是带有分组特征的。返回的数组包含多个元素,第一个元素是以贪婪模式找到的最长的匹配,之后的元素依次为该匹配中的第一、第二、第三 …个分组,这里只有1个分组,所以也就只匹配到1个分组结果,也就是”th"。

假如正则表达式改成:/\d+(t)(h)/,那么匹配到的项就有3个,分别是 : ‘286th’ 、 ‘t’ 、‘h’。我相信大家看到这里,对于分组的意义,以及如何匹配分组就已经了解了。

需要注意的是,这种结果是前提是:1、使用分组 2、不做全局匹配

(使用分组,同时使用全局匹配g

var str = 'Today is the 286th day of 2018, the 108th Thanksgiving Day.';
var results = str.match(/\d+(th)/g); //匹配str中所有的以数字开头,并且以th结尾的子串
console.log(results);

在这里插入图片描述
可以看出,当正则中使用全局匹配g,即使有分组的存在,在匹配结果中也只有匹配到的最长的,那些分组的子匹配都不见了。

具体表现为:这次匹配到的结果是 "286th""108th" ,前一个例子中使用分组是出线的那个单独的分组子匹配 ‘th’,这一项不见了。我们把这个现象理解为,只要使用了全局匹配模式,那么match()将只返回“贪婪”的匹配结果,这里的“贪婪”指的就是只招那个最长的能匹配上的字符串,至于分组项,就忽略了。

6.2.3、replace()
  • 作用:在字符串内查找检索指定的正则表达式,并用replacement的字符串替换的操作

  • 用法:字符串.replace(regreplacement)

  • 参数:正则表达式要替换的值

  • 返回值:替换后的字符串

  • replace()受到正则全局标识g的影响,没有全局标识g时,仅替换第一个匹配字串。如果有全局标识g,则替换全部匹配的字符串。

(1)简单替换:匹配到第一个符合条件的字符进行替换

var str = "JavAScript"
str.replace(/a/i,"B")
'JBvAScript'

(2)全局替换:匹配到字符串中的所有匹配字符进行全部替换

var str = "JavAScript"
str.replace(/a/ig,"B")

'JBvBScript'

(3)使用特殊字符替换replacement,replacement中$字符有特殊的意义

具体说明如下表格:
在这里插入图片描述
注意:$1、$2、···、$99这种是参数一的正则是分组的前提下,这样第二个参数$1、$2、···、$99对应的分组匹配结果

$&

var Str='讨论一下正则表达式中的replace的用法';
Str.replace(/正则表达式/,'《$&》');

"讨论一下《正则表达式》中的replace的用法"

$`:匹配字符串左边的所有字符

var sStr='讨论一下正则表达式中的replace的用法';
sStr.replace(/正则表达式/,'《$`》');

"讨论一下《讨论一下》中的replace的用法"

$’:匹配字符串右边的所有字符
注意,既然 $’ 有单引号,那么外面的引号必须双引号

var sStr='讨论一下正则表达式中的replace的用法';
sStr.replace(/正则表达式/,"《$'》");

"讨论一下《中的replace的用法》中的replace的用法"

$1, $2, $3, …, $n:依次匹配分组的子表达式

var sStr='讨论一下正则表达式中的replace的用法';
sStr.replace(/(正则)(.+?)(式)/,"《$1》$2<$3>");

"讨论一下《正则》表达<式>中的replace的用法"

例:

var str = "javascript"
str.replace(/(java)(script)/,"$2$1")

'scriptjava'

(4)使用函数替换replacement

当replace() 方法的第二个参数 replacement是函数不是字符串时,每次匹配都调用该函数,将这个函数的返回的字符串将作为替换文本使用,即return返回值就是要求的替换后的内容

当第二个参数是函数时:

  1. 当正则没有分组的时候,传进去的第一个实参是正则捕获到的内容,第二个参数是捕获到的内容在原字符串中的索引位置,第三个参数是原字符串(输入字符串)
  • arguments[0]是正则捕获查找到的内容
  • arguments[1]是正则查找到的内容在str这个字符串中的索引位置
  • arguments[2]str字符串本身(叫输入字符串)

例题:
有字符串XaZZcUdFe,找到字符串中的小写字母,给它在后边加上小括号来注明它在str字符串中的位置。比如str中的第一个a,它出现在str字符串的第一个索引位置中,则a变成a(1)

var str="XaZZcUdFe";

var reg=/[a-z]/g;//注意:全文替换必须加g

str=str.replace(reg,function(){

	return arguments[0]+"("+arguments[1]+")";

})
console.log(str);// 弹出   Xa(1)ZZc(4)Ud(6)Fe(8)

//arguments.length的值是3,在reg没有分组的情况下length属性肯定是3.

//其中arguments[0]是正则捕获查找到的内容;
//arguments[1]是正则查找到的内容在str这个字符串中的索引位置;
//arguments[2]是str字符串本身(叫输入字符串)

//这个匿名函数被自动执行四次,每一次arguments里的值分别是:

//第一次:arguments[0]是a,arguments[1]是1,arguments[2]是原字符str本身

//第二次:arguments[0]是c,arguments[1]是4,arguments[2]是原字符str本身

//第三次:arguments[0]是,arguments[1]是6,arguments[2]是原字符str本身

//第四次:arguments[0]是e,arguments[1]是8,arguments[2]是原字符str本身

在这里插入图片描述

var str = 'abc2020abc2021';
str = str.replace(/abc/g, function () {
 	console.log(arguments)
 	// 返回的数组和执行exec返回的结果一致
 	return 'JavScript';
});

['abc', 0, 'abc2020abc2021']
['abc', 7, 'abc2020abc2021']
'JavScript2020JavScript2021'

例题:将’-‘连接的字符串转换成驼峰模式,利用replace函数替换

写法一:
var str = "get-element-by-id"
var reg = /-\w/g
str.replace(reg,function($){
    console.log('$:',$)
    return $.slice(1).toUpperCase()
})

$: -e
$: -b
$: -i
'getElementById'


写法二:
var str = "the-first-name"
var reg = /-(\w)/g
str.replace(reg,($,$1)=>{
  console.log("$:",$,"$1:",$1)
  return $1.toUpperCase()
})

$: -f $1: f
$: -n $1: n
'theFirstName'

可以看出,每次执行匿名函数arguments值和通过exec捕获到的内容很类似

例题:将数字替换成对应的汉字

var str = '20171001';
var arr = ["零","壹","贰","叁","肆","伍","陆","柒","捌","玖"];
str = str.replace(/\d/g,function () {
 	var num = arguments[0]; // 把捕获的内容,作为数组的下标
 	return arr[num];
});

console.log(str); //贰零壹柒壹零零壹

可以看出,每次执行匿名函数arguments值和通过exec捕获到的内容很类似

  1. 当正则有分组的时候,第一个参数是总正则找到的内容,后面依次是各个子正则查找到的内容
  • arguments[0] - arguments[n]总正则子正则捕获查找到的内容
  • arguments最后两项是正则查找到的内容在str这个字符串中的索引位置str字符串本身

例题:
找出下面字符串中两个连着出现的数字,用它们的和将它们替换( 比如第一次找到4和5,用9替换,第二次找到的是8和9,用17替换,第三次找到的是7和2,用9替换)

var str="456a89b72cs";

var reg=/(\d)(\d)/g;

str=str.replace(reg,function(){

	return Number(arguments[1])+Number(arguments[2]);

});

console.log(str);//  弹出   96a17b9cs

//上面replace里的匿名函数会被自动执行三次(因为匹配到了三次);

//每次执行,arguments.length都是5;arments[0]是总正则查找到的字符串,arguments[1]是第一个分组查找到的内容,arguments[2]是第二个分组查找到的内容,arguments[3]是总正则查找到的内容在str这个字符串中的索引位置,arguments[4]是str这个字符串本般

//第一次这五个参数的值分别是:arguments[0]是"45",arguments[1]是"4",arguments[2]是"5",arguments[3]是0,arguments[4]是"456a89b72cs";

//第二次这五个参数的值分别是:arguments[0]是"89",arguments[1]是"8",arguments[2]是"9",arguments[3]是4,arguments[4]是"456a89b72cs";

//第三次这五个参数的值分别是:arguments[0]是"45",arguments[1]是"4",arguments[2]是"5",arguments[3]是0,arguments[4]是"456a89b72cs"

在这里插入图片描述

例题:

var s = 'abbaaaaa kmmkk edduu qqaaq heegg wrrwwwfe';

var pattern=/([a-z])([a-z])\2\1{2,}/g; 

s.replace(pattern,function($4,$0,$1,$2,$3){
    console.log('$4:',$4,'$0:',$0,'$1:',$1,'$2:',$2,'$3:',$3)
});

(这道题的replace的第二个参数是函数式写法,同时我们发现其第一个参数正则只有两个小括号(),即意味着只有两个分组,所以我们不用被其$4,$0,$1,$2,$3这种写法所干扰,依然按照正常的arguments进行拆分理解)

$4,$0,$1,$2,$3写法
在这里插入图片描述
arguments写法
在这里插入图片描述

7、正则特殊用法

7.1、反向引用\1

我们可能会在某些正则中看到\1\2···这种写法,往往这种写法前面会配合(),我们知道,用()括号括起来的匹配会是可获取的,而在JaveScript中总共可以放9个对应用\1....\9获取重用

\num表示重复第num个括号里的内容匹配,例如:

(\w)(\d)\1

第一个小括号就是(\w),因此(\w)就会重复一次,所以这个正则就相当于:

(\w)(\d)(\w)

例题:匹配xxyy模式:

var reg = /(\w)\1(\w)\2/g;

例题:aaabbbbcccc变成abc

var str = 'aaaaaaaaabbbbbbbbcccccc'

var reg = /(\w)\1*/g

console.log(str.replace(reg,'$1'))
7.2、?:

我们有时会经常见到正则中(pattern)(?:pattern)pattern代表任意字符串,

  • 前者 (pattern)捕获(获取)分组,即匹配同时获取
  • 后者 (?:pattern)非(捕获)获取匹配,即匹配但不获取
  • 区别:在于正则表达式匹配输入字符串之后所获得的匹配的数组当中没有(?:pattern)匹配的部分

比如下面例子:

(?:pattern)

var m = "abcabc".match(/(?:a)(b)(c)/)
console.log(m)

结果 ["abc", "b", "c", index: 0, input: 'abcabc']
m[0]/(?:a)(b)(c)/匹配到的整个字符串,这里包括了a,这里匹配了a,但是数组不获取a
m[1] 是捕获组1,即(b)匹配到的子字符串
m[2] 是捕获组2,即(c)匹配到的子字符串

(pattern)

var m = "abcabc".match(/(a)(b)(c)/)
console.log(m)

结果['abc', 'a', 'b', 'c', index: 0, input: 'abcabc']
m[0]/(a)(b)(c)/匹配到的整个字符串,这里包括了a,这里匹配了a,同时数组获取a
m[1] 是捕获组1,即(a)匹配到的子字符串,这里匹配了a,同时数组获取a
m[2] 是捕获组2,即(b)匹配到的子字符串
m[3] 是捕获组3,即(c)匹配到的子字符串

所以通过上述例子,我们可以理解(?:pattern),就是匹配pattern不数组获取匹配结果,不进行存储供以后使用。

这在使用或字符“(|)”来组合一个模式的各个部分是很有用。例如“industr(?:y|ies)”就是一个比“industry|industries”更简略的表达式。

7.3、断言(零宽断言)

在正则中,我们经常看到正向断言负向断言,或者先行断言后行断言,其实不管是什么断言,都是根据下面这4种符号来进行判别:

?=?!?<=?!=

  • (?=pattern) 零宽正向先行断言
  • (?!pattern) 零宽负向先行断言
  • (?<=pattern) 零宽正向后行断言
  • (?<!pattern) 零宽负向后行断言

零宽断言:

用于查找在某些内容(但并不包括这些内容)之前之后的东西,也就是说它们像\b^$那样用于指定一个位置,这个位置应该满足一定的条件(即断言),因此它们也被称为零宽断言

零宽这两个字,正如字面意思**,零宽是一种零宽度的匹配**,它匹配到的内容不会保存到匹配结果中去,最终匹配结果只是一个位置而已

(重复一遍,只匹配位置这个概念非常重要)

(?=pattern)
非获取匹配,正向先行断言,在任何匹配pattern的字符串开始处匹配查找字符串,该匹配不需要获取供以后使用。例如,“Windows(?=95|98|NT|2000)”能匹配“Windows2000”中的“Windows”,但不能匹配“Windows3.1”中的“Windows”。预查不消耗字符,也就是说,在一个匹配发生后,在最后一次匹配之后立即开始下一次匹配的搜索,而不是从包含预查的字符之后开始。

(?!pattern)
非获取匹配,负向先行断言,在任何不匹配pattern的字符串开始处匹配查找字符串,该匹配不需要获取供以后使用。例如“Windows(?!95|98|NT|2000)”能匹配“Windows3.1”中的“Windows”,但不能匹配“Windows2000”中的“Windows”。

(?<=pattern)
非获取匹配,正向后行断言,与正向肯定预查类似,只是方向相反。例如,“(?<=95|98|NT|2000)Windows”能匹配“2000Windows”中的“Windows”,但不能匹配“3.1Windows”中的“Windows”。

(?<!pattern)
非获取匹配,负向后行断言,与正向否定预查类似,只是方向相反。例如“(?<!95|98|NT|2000)Windows”能匹配“3.1Windows”中的“Windows”,但不能匹配“2000Windows”中的“Windows”。这个地方不正确,有问题 

注:

  • 先行:可以理解为要匹配的正则字符?=或者?!,这两个符号在后面
  • 后行:可以理解为要匹配的正则字符?<=或者?<!,这两个符号在前面
  • 正向:括号里面的字符匹配的字符的位置
  • 负向:括号里面的字符不是匹配的字符的位置

例1:正向零宽断言

var str="<div>antzone";
var reg=/^(?=<)<[^>]+>\w+/;

console.log(str.match(reg));

在这里插入图片描述
匹配过程如下:

  • 首先由正则表达式中的"^"获取控制权,首先由位置0开始进行匹配,它匹配开始位置0,匹配成功
  • 然后控制权转交给"(?=<)",由于"“是零宽的,所以”(?=<)“也是从位置0处开始匹配,它要求所在的位置右侧必须是字符”<",位置0的右侧恰好是字符"<",匹配成功
  • 然后控制权转交个"<",由于"(?=<)"也是零宽的,所以它也是从位置0处开始匹配,于是匹配成功
  • 后面的匹配过程按照正则常规匹配

例2:负向零宽断言

var str="abZW863ab88";
var reg=/ab(?![A-Z])/g;

console.log(str.match(reg));

在这里插入图片描述
匹配过程如下:

  • 首先由正则表达式的字符"a"获取控制权,从位置0处开始匹配,匹配字符"a"成功
  • 然后控制权转交给"b",从位置1处开始匹配,配字符"b"成功
  • 然后控制权转交给"(?![A-Z])",它从位置2处开始匹配,它要求所在位置的右边不能够是任意一个大写字母,而位置的右边是大写字母"Z",匹配失败
  • 然后控制权又重新交给字符"a",并从位置3处开始尝试,匹配失败
  • 然后控制权再次交给字符"a",从位置4处开始尝试匹配,依然失败,如此往复尝试,直到从位置7处开始尝试匹配成功
  • 然后将控制权转交给"b",然后从位置8处开始尝试匹配,匹配成功
  • 然后再将控制权转交给"(?![A-Z])",它从位置9处开始尝试匹配,它规定它所在的位置右边不能够是大写字母,匹配成功,但是它并不会真正匹配ab后面的字符,所以最终匹配结果是"ab"

例3:数字千分位处理,用将 123456789000 变成 123.456.789.000(经典面试题)

(方法不唯一)

var str = '123456789000'
var reg = /(?<=\d)(?=(\d{3})+$)/g

str.replace(reg,'.') // '123.456.789.000'

var str = '123456789000'
var reg = /(?=(\B)(\d{3})+$)/g

str.replace(reg,'.')  // '123.456.789.000'

以方法一来讲:

  • 保证千分位,就要从最后开始匹配,从后向前数三个数字的位置,并且每个位置的左边都需要有数字,所以我们很容易得到(?=\d{3}$)
    在这里插入图片描述
  • 我们找到了一个正确的位置,但还没有打到要求,我们还想在6734中间插入分割点,我们发现这些位置是从末尾向前369位,即都是3的倍数,所以我们可以用+元字符,得到(?=(\d{3})+$)+作用于前面括号括起来的\d{3},表示至少需要有一个\d{3}
    在这里插入图片描述
    (注意这里在使用+时,不必须将\d{3}用括号给包裹起来,这样+才表示3n倍的数字,如果没有用括号包裹,控制台就会报错)
    在这里插入图片描述
  • 我们看到,现在基本可以满足了,但是开头的数字也被匹配了,我们需要将它排除,只要保证每一个的位置左边至少有一个数字就可以了,可以加上(?<=\b),表示当前位置的左边需要是一个数字,这样正则就成了(?<=\d)(?=(\d{3})+$)
    在这里插入图片描述
  • 不过看起来有点麻烦,我们可以用\B来代替(?<=\b)\B表示非单词边界

(上面是方法一的具体思路,方法二的思路我就不细讲了)

7.4、()、[ ]、{}

()是为了提取匹配的字符串。表达式中有几个()就有几个相应的匹配字符串
[]是定义匹配的字符范围。比如 [a-zA-Z0-9] 表示相应位置的字符要匹配英文字符和数字
{}一般用来表示匹配的长度

举个例子:

  • (\s*)表示连续空格的字符串;[\s*]表示空格或者*号\s{3}表示匹配三个空格\s{1,3}表示匹配一到三个空格
  • (0-9)匹配 '0-9′本身;[0-9]*匹配数字(注意后面有 *,可以为空);[0-9]+ 匹配数字(注意后面有 +,不可以为空);{1-9} 写法错误。

():
圆括号(),主要应用在限制多选结构的范围/分组/捕获文本``/环视/特殊模式处理

示例:
1、(abc|bcd|cde) :表示这一段是abc、bcd、cde三者之一均可,顺序也必须一致;
2、(abc)? :表示这一组要么一起出现,要么不出现,出现则按此组内的顺序出现;
3、(?:abc):表示找到这样abc这样一组,但不记录,不保存到$变量中,否则可以通过$x取第几个括号所匹配到的项,比如:(aaa)(bbb)(ccc)(?:ddd)(eee),可以用$1获取(aaa)匹配到的内容,而$3则获取到了(ccc)匹配到的内容,而$4则获取的是由(eee)匹配到的内容,因为前一对括号没有保存变量;
4、a(?=bbb) :顺序环视 表示a后面必须紧跟3个连续的b;
5、(?i:xxxx) :不区分大小写 (?s:.*) 跨行匹配.可以匹配回车符;

[ ]:
方括号[]单个匹配,主要用于字符集/排除字符集/命名字符集

示例:
1、[0-3] :表示找到这一个位置上的字符只能是0到3这四个数字,与(abc|bcd|cde)的作用比较类似,但圆括号可以匹配多个连续的字符,而一对方括号只能匹配单个字符
2、[^0-3] : 表示找到这一个位置上的字符只能是除了0到3之外的所有字符

()和[]有本质的区别

()内的内容表示的是一个子表达式,()本身不匹配任何东西,也不限制匹配任何东西,只是把括号内的内容作为同一个表达式来处理,例如(ab){1,3},就表示ab一起连续出现最少1次,最多3次。如果没有括号的话,ab{1,3},就表示a,后面紧跟的b出现最少1次,最多3次。另外,括号在匹配模式中也很重要。

[]表示匹配的字符在[]中,并且只能出现一次,并且特殊字符写在[]会被当成普通字符来匹配。例如[(a)],会匹配(a)、这三个字符。

所以() [] 无论是作用还是表示的含义,都有天壤之别,没什么联系

7.5、贪婪、非贪婪

贪婪非贪婪模式影响的是被量词修饰的子表达式的匹配行为

  • 贪婪模式整个表达式匹配成功的前提下,尽可能的匹配量词
  • 非贪婪模式整个表达式匹配成功的前提下,尽可能的匹配量词

一般我们使用的量词有{m,n}{m,}?*+,这些常常用于贪婪模式,在这些量词后面加上?,即{m,n}?{m,}???*?+?,就常常用于非贪婪模式

(这里有一篇关于贪婪模式和非贪婪模式的文章,虽然是十几年前,但是讲得很深刻)正则基础之——贪婪与非贪婪模式

贪婪模式:

"aaaaa".match(/a+/);
//["aaaaa", index: 0, input: "aaaaa"]

非贪婪模式:

"aaaaa".match(/a+?/);
//["a", index: 0, input: "aaaaa"]

但是非贪婪匹配有时候和我们期待的并不一样,比如:

"aaab".match(/a+b/);
["aaab", index: 0, input: "aaab"]

"aaab".match(/a+?b/);
["aaab", index: 0, input: "aaab"]

在我们期待的情况下非贪婪匹配应该是匹配"ab"才对但是结果却和贪婪匹配时一样的。

在《权威指南》中有这么一句话:正则表达式的模式匹配总是会寻找字符串中第一个可能匹配的位置

个人对这句话的理解是:上例中正则表达式会先找到第一个字符a,因为a后面连接的字符有可能形成匹配,这是正则表达式就认定这个位置的字符了,然后开始往后进行匹配,如果像第一个例子中那样非贪婪匹配,匹配到第一个a就结束了,但是第二个例子中还要匹配b所以不得不接着往下匹配直到匹配到b为止结束。


本博客参考

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值