JavaScript 正则表达式创建、分组与匹配详解
本文详细解析JavaScript中正则表达式的两种创建方式及其实例共享差异,深入讲解括号分组的三种用法(分组、向后引用、非捕获分组),阐述贪婪与懒惰匹配的回溯机制,并对比零宽断言的实现。最后重点区分String.match()与RegExp.exec()在全局标志g下的返回结果差异及lastIndex属性的处理,提供实用的代码验证建议。
正则表达式对象的创建方式
RegExp对象的创建主要有两种方式。
常规的正则表达式可以使用直接量形式,即用斜杠“/”包裹字符。但在需要动态生成正则表达式的场景中,使用RegExp()构造函数更为灵活和合适。
括号()的三种核心用法
var reg1 = /'\w+'/g;
var reg2 = new RegExp('\'\\w+\'','g');
对比这两种创建方式,使用RegExp构造函数时,第一个参数是正则字符串。由于不是直接量形式,因此不需要用斜杠“/”包裹,但字符串内部的单引号“'”和反斜杠转义符“\”需要进行二次转义,否则会导致语法错误。
无论是使用直接量还是RegExp()构造函数,最终结果都是生成一个新的RegExp对象,并将其赋值给相应的变量。
根据《Javascript权威指南》的描述,在ECMAScript 3规范中,正则直接量每次执行时都会返回同一个RegExp对象实例,这意味着多个变量可能共享同一个正则实例。直到ECMAScript 5规范才明确规定每次使用直接量都会创建不同的实例。
在浏览器兼容性方面,IE浏览器一直遵循ECMAScript 5的规定,每次创建新实例;而其他浏览器的旧版本可能仍遵循ECMAScript 3的规定,共享实例。因此,在实际开发中,使用构造函数创建正则表达式是更安全的做法。如果必须使用直接量且涉及lastIndex属性,务必记得在每次使用后将其重置为0。
括号()在正则表达式中主要有三种用途。
第一种是分组。
分组的作用是将多个单独项组合成一个子表达式,以便统一应用量词如?、+、*、{n,m}等。例如:
var reg = /Java(script)?/;
上述例子中,script被包含在括号内,表示整个script部分是可选的。
第二种是向后引用。
在正则表达式内部,可以使用“\n”(其中n为数字,代表前面第n个括号)来引用之前括号中匹配到的具体文本内容。例如:
分组与向后引用
var reg = /(\d+)[a-z]{3}\1/;
//20man20
//20man23 错
//reg = /\1[a-z]{3}(\d+)/; 错
需要注意的是,“\n”引用的是前面匹配到的具体文本(如“20”),而不是匹配该文本的正则规则(如“\d+”)。此外,JavaScript只支持数字形式的向后引用,不支持命名捕获组如“(?<name>exp)”的语法。如果将“\1”写在定义它的括号之前,将无法匹配任何内容,浏览器通常会报错。
如果只需要进行逻辑分组而不希望生成捕获组或占用引用编号,可以使用非捕获分组“(?: exp)”。这种形式既不匹配文本,也不分配引用编号。例如:
非捕获分组与子模式匹配
var reg = /(\w{3})(?:\d+)([a-z]{2})\2/;
//man7788abab
在上述例子中,“\2”匹配的是“ab”而不是“7788”。这种方式既实现了分组处理,又提高了查询效率。
第三种是子模式匹配(或称替换引用)。
有时我们需要在字符串操作中使用括号匹配到的文本,可以使用“$n”的形式来替换编号为n的匹配文本。这常用于String对象的replace()方法,例如交换等号两边的单词:
var reg = /(\w+)=(\w+)/;
var str = ‘love=hate';
str.replace(reg,'$2=$1');
//"hate=love"
接下来讨论匹配的顺序、贪婪与懒惰模式。
常见的重复匹配字符如?、+、*、{n,m}默认采用贪婪匹配,即尽可能多地匹配字符。与之相对的是懒惰匹配,即在重复字符后加上问号“?”,如??、+?、*?、{n,m}?,表示尽可能少地匹配。例如:
贪婪与懒惰匹配机制
var str = 'goooogle‘;
var reg1 = /o+/; //"goooo"
var reg2 = /o+?/; //"go"
对例子稍作修改后:
var str = 'goooogle‘;
var reg1 = /o+gle/; //"oooogle"
var reg2 = /o+?gle/; //"oooogle"
修改后的例子结果看似相同,但“/o+?gle/”并未直接匹配到“ogle”。这是因为正则表达式始终从左向右进行匹配,不会从右侧获取子串进行匹配。
虽然最终结果相同,但匹配原理不同。在reg1中,“o+”首先匹配所有的“o”,接着匹配“gle”,完成整体匹配。而在reg2中,“o+?”先匹配一个“o”,随后“gle”在字符串的第2位到第4位(即原串的“ooo”部分)匹配失败。引擎回溯至“o+?”,尝试匹配第二个“o”,成功后再在第3位到第4位匹配“gle”,以此类推,直到匹配整个字符串。
总体而言,必须记住:从左往右的匹配顺序优先级高于贪婪或懒惰匹配的选择。
零宽断言用于指定匹配条件而不消耗字符。
关于零宽断言的总体解释可参考博文《正则表达式30分钟入门教程》,值得注意的是,JS里只支持零宽先行断言。即零宽正预测先行断言"(?=exp)"和零宽负预测先行断言“(?!exp)”。 所谓“零宽”,就是它并不在匹配的结果字符中占据空间。例如“\w","\s"就会占据一个或几个空间,依匹配的字符长度决定。而像”^","$"这种对应的首末位置,不占据空间,零宽就是属于这一类。
所谓“正/负预测”,是指断言中要求满足或不满足的条件。“正”表示必须满足exp,“负”表示必须不满足exp。
所谓“先行”,是指被检查的字符串位于断言之前,零宽断言位于之后。即判断字符串的后一部分是否满足断言条件。
所谓“断言”,就是判断的条件本身。
以下是两种零宽断言的例子:
零宽先行断言详解
var str = 'java coffeescript';
var reg1 = /\b\w+(?=script\b)/; //coffee
var reg2 = /\b\w+(?!script\b)/; //java
reg1为零宽正预测先行断言,“(?=script\b)”表示单词必须以“script”结尾,这是一种条件判断,不占用任何空间。
同样,reg2为零宽负预测先行断言,“(?!script\b)”表示单词不以“script”结尾。
另外,由于JavaScript不支持零宽后顾断言,无法直接判断字符串前面部分是否满足条件。但在JS中可以通过多次正则表达式实现:先匹配目标串,然后截取从开头到index的字符子串,再判断子串末尾是否符合所需的断言条件。具体用法可自行尝试。
match()与exec()方法的异同。
match和exec是正则表达式匹配字符串的常用方法,两者功能相似,但存在一些细微区别。
1、使用方式不同。
match是字符串包装对象的方法,用法为:String.match(RegExp); exec是正则表达式对象的方法,用法为:RegExp.exec(String);
2、返回的结果不同。
当RegExp未设置全局标志“g”时:
两者的返回结果相同。如果没有匹配值,返回null;如果有匹配值,返回一个数组(记为array)。array[0]为匹配的字符串,array[1]、array[2]……对应正则表达式中圆括号匹配的子字符串$1、$2……。同时,数组带有两个属性:array.index表示匹配字符串的初始位置,array.input表示正在检索的原始字符串。
当RegExp设置了全局标志“g”时:
match在有匹配值时返回一个数组array,数组的每一项依次表示所有匹配到的字符串,此时不再包含圆括号匹配的子字符串。此外,此时的数组没有index属性和input属性。
exec则与没有全局标志“g”时的表现无异。此时返回的是数组array,array[0]为当前匹配的字符串,array[1]、array[2]……为当前匹配下的圆括号匹配子串。此时需要注意RegExp对象的lastIndex属性,它表示原字符串中当前匹配字符串末尾的下一个位置。当没有进一步匹配结果时,lastIndex属性置为0。因此,可以利用lastIndex属性的循环来找出所有匹配字符串。例如:
match与exec方法的差异对比
var str = 'I love1 my job22';
var reg = /\b[a-z]+(\d+)\b/g;
array = str.match(reg);
//array = ["love1", "job22"]
//array.index = undefind
//array.input = undefined
------------------------------------
array = reg.exec(str);
//array = ["love1", "1"]
//array.index = 2
//array.input = "I love1 my job22"
//reg.lastIndex = 7
//run again
reg.exec(str);
//array = ["job22", "22"]
//array.index = 11
//array.input = "I love1 my job22"
//reg.lastIndex = 16
//run again
reg.exec(str);
//reg.lastIndex = 0
最后,考虑到ECMAScript 3和ECMAScript 5的版本差异,每次匹配完成后记得手动将RegExp对象的lastIndex属性置0,以满足老版本非IE浏览器的要求。
js正则表达式,pattern,注意事项
简而言之,请使用“/\w+@+\w+(\.+\w+){1,}/.test(str)”来验证,而不要使用“"\w+@+\w+(\.+\w+){1,}".test(str)”来验证; 使用后者,直接输入“add@dfddf”即可通过验证(这通常是不期望的行为)。
什么时候该用JS构造器:与工厂模式、字面量的选择对比
2026-11-05 16:18:22
推荐几款轻量级JS照片墙开源库及对比
2026-11-05 15:56:19
JavaScript递归写法有哪些常见陷阱及避免方法
2026-11-05 14:53:38
JS数组浅拷贝与深拷贝怎么选?场景与方法总结
2026-11-05 14:41:02
p5.js 中 setup() 和 draw() 函数的执行机制详解
2026-11-05 12:32:38
