JavaScript-RegExp及String的正则表达式处理方法

概述

最近开始正式使用JavaScript开发桌面应用(Electron),首先是被JS的各类版本搞得眼花缭乱,幸好Electron龟缩在NodeJS/Chrome V8框架之内,可以避开许多不必要的兼容问题。

在使用到字符串匹配相关时,正则表达式显然是逃不掉,具体的正则表达式语法其实是统一的,与语言无关,但是一些具体细节,差别还是蛮大的。

笔者在使用JavaScript的字符串匹配正则的方法时,单纯通过代码索引去查看函数说明,往往一头雾水,只能求助于Google,发现 The Modern JavaScript Tutorial 网站还不错,只是相关书籍需要付费下载,故而选择在线版本查询,对于正则部分的说明,笔者认为解释比较切合实际,故而选择将对应页面翻译、记录。(官方貌似有在译的中文版本,貌似尚不完整,且不去管它)

String对象方法

str.search(reg)

该方法将返回首个匹配值的索引位置,如不存在匹配项则返回-1:

let str = "A drop of ink may make a million think";
alert( str.search( /a/i ) ); // 0 (the first position)

注意事项:search 方法仅返回首个匹配项所在位置。
search 方法无法获得第二或更多的匹配项位置,该方法无此类语法支持。
我们可以使用其他方法达到此目的。

str.match(reg) (不使用g标识)

str.match的行为变化取决于g标识的使用。首先了解一下没有g标识的情况。
str.match(reg)只会查找首个匹配项。
返回结果是一个包含匹配值和如下属性的数组:

  • index-str内首个匹配值的索引位置
  • input-the subject string(不知道怎么翻译,可能就是输入字符串的意思?)

示例:

let str = "Fame is the thirst of youth";

let result = str.match( /fame/i );

alert( result[0] );    // Fame (the match)
alert( result.index ); // 0 (at the zero position)
alert( result.input ); // "Fame is the thirst of youth" (the string)

返回值数组可能包含不止一个元素。
如果正则表达式中包含圆括号限定(…),那么返回值数组中将会包含该圆括号内限定所得值。

示例:

let str = "JavaScript is a programming language";

let result = str.match( /JAVA(SCRIPT)/i );

alert( result[0] ); // JavaScript (the whole match)
alert( result[1] ); // script (the part of the match that corresponds to the parentheses)
alert( result.index ); // 0
alert( result.input ); // JavaScript is a programming language

因为 i-标识代表不区分大小写,所以 result[0] 为 JavaScript。而匹配圆括号内SCRIPT部分的值则作为result[1]的值存在。
更多关于圆括号的内容将在 Capturing groups 章节描述,圆括号的使用对于搜索和替换是极其有用的。

str.match(reg) (使用g标识)

当使用g标识时,str.match将会返回所有匹配值的数组。此时,返回值不会附带额外的属性值,并且使用圆括号也不会产生任何新内容。

示例:

let str = "HO-Ho-ho!";

let result = str.match( /ho/ig );

alert( result ); // HO, Ho, ho (all matches, case-insensitive)

圆括号不会带来任何改变,示例如下:

let str = "HO-Ho-ho!";

let result = str.match( /h(o)/ig );

alert( result ); // HO, Ho, ho

所以,使用g标识,result只会是所有匹配值的数组集合,没有其他任何额外属性。
如果想要获取注入匹配位置、使用圆括号等其他信息,应当使用 RegExp#exec 方法,后续将会描述其使用。

注意:如果没有匹配值,str.match将会返回null。
请注意,当没有匹配值时,result将会是一个null值,而非一个空数组。
请牢记该提醒以避免踩坑:

let str = "Hey-hey-hey!";

alert( str.match(/ho/gi).length ); // error! there's no length of null

str.split(regexp|substr,limit)

可以使用正则表达式子字符串作为分隔字符串的定位符。
使用子字符串的例子:

alert('12-34-56'.split('-')) // [12, 34, 56]

使用正则表达式的例子:

alert('12-34-56'.split(/-/)) // [12, 34, 56]

str.replace(str|reg, str|func)

该方法在用于字符串查询、替换等功能上,犹如瑞士军刀一般有效。
一个极简的查询、替换子字符串的示例如下:

// replace a dash by a colon //使用冒号替换连接号
alert('12-34-56'.replace("-", ":")) // 12:34-56

上述示例说明,当replace方法的第一个参数时一个字符串时,只会返回第一个匹配值。
上述示例中,要找到所有的连接号,需要使用正则表达式 /-/g 替代”-“字符串:

// replace all dashes by a colon
alert( '12-34-56'.replace( /-/g, ":" ) )  // 12:34:56

第二个参数即为用来替换的字符串。我们可以在其中使用一些特殊的字符:

SymbolInsertsComment
$$“$”使用 “$” 替换匹配值
$&the whole match使用所得匹配值替换 “$&”在新值中所占位置
$`a part of the string before the match使用所得匹配值之前的一部分(即当前匹配到前一个匹配之间的部分)替换 “$`”在新值中所占位置
$’a part of the string after the match使用所得匹配值之后的所有字符串(当前匹配到字符串结尾部分)替换 “$’”在新值中所占位置
$nif n is a 1-2 digit number, then it means the contents of n-th parentheses counting from left to right将圆括号内匹配值按顺序编号(即使用 n n 形 式 ) , 并 将 对 应 值 替 换 n在新值中所占位置

比如,使用 $& 方式将所有 “John” 替换成 “Mr.John”:

let str = "John Doe, John Smith and John Bull.";

// for each John - replace it with Mr. and then John
alert(str.replace(/John/g, 'Mr.$&'));
// "Mr.John Doe, Mr.John Smith and Mr.John Bull.";

圆括号通常与 1, 1 , 2一同使用:

let str = "John Smith";

alert(str.replace(/(John) (Smith)/, '$2, $1')) // Smith, John

在某些需要”智能”替换的场景下,第二个参数可以是一个函数方法。
该方法在将在每个匹配中生效,函数返回值作为替换值插入新字符串:

let i = 0;

// replace each "ho" by the result of the function
alert("HO-Ho-ho".replace(/ho/gi, function() {
  return ++i;
})); // 1-2-3

上述示例中,函数每次只会返回下一个数字,但是通常情况下返回值都是基于匹配值的。
带参的函数形式为 func(str, p1, p2, …, pn, offset, s):
1、str - 匹配值
2、p1, p2, …, pn - 圆括号中内容(如果有的话)
3、offset - 匹配值位置
4、s - 源字符串

没有圆括号匹配的情况,函数只会有3个参数: func(str, offset, s),示例如下:

// show and replace all matches
function replacer(str, offset, s) {
  alert(`Found ${str} at position ${offset} in string ${s}`);
  return str.toLowerCase();
}

let result = "HO-Ho-ho".replace(/ho/gi, replacer);
alert( 'Result: ' + result ); // Result: ho-ho-ho

// shows each match:
// Found HO at position 0 in string HO-Ho-ho
// Found Ho at position 3 in string HO-Ho-ho
// Found ho at position 6 in string HO-Ho-ho

下述示例中包含了两个圆括号,所以replacer有5个参数:

function replacer(str, name, surname, offset, s) {
  // name is the first parentheses, surname is the second one
  return surname + ", " + name;
}

let str = "John Smith";

alert(str.replace(/(John) (Smith)/, replacer)) // Smith, John

使用函数方法功作为替换对象,可以访问外部变量并实现任何想要实现的功能。

regexp

regexp.test(str)

正则表达式对象的test方法,查找任何匹配的值,根据是否找到对应匹配返回true或false。
所以,regexp.test(str)等价于 str.search(reg) != -1:

let str = "I love JavaScript";

// these two tests do the same
alert( /love/i.test(str) ); // true
alert( str.search(/love/i) != -1 ); // true

不匹配的示例:

let str = "Bla-bla-bla";

alert( /love/i.test(str) ); // false
alert( str.search(/love/i) != -1 ); // false

regexp.exec(str)

我们已经接触过如下查询方法:

  • search - 找到匹配的位置索引
  • match - 没有g标识的情况下,返回首个匹配值(圆括号存在的情况下,result会返回圆括号内匹配值数值)
  • match - 有g标识的情况下,返回所有匹配值(即便有圆括号,result中也不会包含对应匹配数据)

regexp.exec 方法不是很好用,它可以用来匹配所有圆括号及位置。
它的行为取决于是否使用了g标识。

  • 如果没有g标识,那么 regexp.exec(str) 返回首个匹配值,与str.match(reg)行为一致
  • 如果存在g标识,那么 regexp.exec(str) 将会返回首个匹配值,同时会使用 regexp.lastIndex属性保存该值之后的位置。下一次调用时, 将会从 regexp.lastIndex开始查找,并返回下一个匹配值。如果 regexp.exec没有匹配值了,将会返回null, 同时regexp.lastIndex将会被设为0。

由此可见,与str.match相同的是,当不使用g标识时,该方法没有特殊之处。
但是带了g标识之后,就能得到所有的匹配位置以及圆括号组信息。

下述示例描述了regexp.exec如何逐个调用匹配的行为:

let str = "A lot about JavaScript at https://javascript.info";

let regexp = /JAVA(SCRIPT)/ig;

// Look for the first match
let matchOne = regexp.exec(str);
alert( matchOne[0] ); // JavaScript
alert( matchOne[1] ); // script
alert( matchOne.index ); // 12 (the position of the match)
alert( matchOne.input ); // the same as str

alert( regexp.lastIndex ); // 22 (the position after the match)

// Look for the second match
let matchTwo = regexp.exec(str); // continue searching from regexp.lastIndex
alert( matchTwo[0] ); // javascript
alert( matchTwo[1] ); // script
alert( matchTwo.index ); // 34 (the position of the match)
alert( matchTwo.input ); // the same as str

alert( regexp.lastIndex ); // 44 (the position after the match)

// Look for the third match
let matchThree = regexp.exec(str); // continue searching from regexp.lastIndex
alert( matchThree ); // null (no match)

alert( regexp.lastIndex ); // 0 (reset)

可以看出,每个regexp.exec都返回了一个完整格式:带圆括号匹配值的数组,索引、输入字符串属性。
regexp.exec主要用来循环获得所有匹配:

let str = 'A lot about JavaScript at https://javascript.info';

let regexp = /javascript/ig;

let result;

while (result = regexp.exec(str)) {
  alert( `Found ${result[0]} at ${result.index}` );
}

该循环会一直运行到 regexp.exec返回null为止,即不再有更多的匹配内容。

我们可以通过手动设置lastIndex来强制regexp.exec从某个指定位置开始查询:

let str = 'A lot about JavaScript at https://javascript.info';

let regexp = /javascript/ig;
regexp.lastIndex = 30;

alert( regexp.exec(str).index ); // 34, the search starts from the 30th

“y”标识

y标识的意思是,必须在指定位置(regexp.lastIndex)找到匹配项。

换言之,通常情况下,正则式 /javascript/ 会在字符串中查找所有的匹配。但是一旦加上y标识之后,将只会在regexp.lastIndex(默认为0)值所示位置查找匹配。

示例如下:

let str = "I love JavaScript!";

let reg = /javascript/iy;

alert( reg.lastIndex ); // 0 (default)
alert( str.match(reg) ); // null, not found at position 0

reg.lastIndex = 7;
alert( str.match(reg) ); // JavaScript (right, that word starts at position 7)

// for any other reg.lastIndex the result is null

上述 /javascript/iy 表达式只会在reg.lastIndex=7的地方查找。

y标识使用的意义,原文描述如下:

So, what’s the point? Where do we apply that?

The reason is performance.

The y flag works great for parsers – programs that need to “read” the
text and build in-memory syntax structure or perform actions from it.
For that we move along the text and apply regular expressions to see
what we have next: a string? A number? Something else?

The y flag allows to apply a regular expression (or many of them
one-by-one) exactly at the given position and when we understand
what’s there, we can move on – step by step examining the text.

Without the flag the regexp engine always searches till the end of the
text, that takes time, especially if the text is large. So our parser
would be very slow. The y flag is exactly the right thing here.

归纳起来的意思就是:

  1. 使用y标识可以更精准的定位匹配
  2. 使用y标识可以在某些确定情况下提供更好的性能

总结

根据真实的使用场景,可以做如下总结归纳:

只查找首个匹配项

  • 查找首个项的位置 - str.search(reg)
  • 查找全部匹配信息 - str.match(reg)
  • 检查是否匹配 - regexp.test(str)
  • 在指定位置查找匹配 - 设置regexp.lastIndex为指定值,使用regexp.exec(str)

查找所有匹配项

  • 获取所有匹配项数组 - str.match(reg),reg中带g标识
  • 获取所以匹配项,并包含单个项的所有信息 - regexp.exec(str),在循环中使用g标识

查找并替换

  • 使用字符串或函数方法替换字符串 - str.replace(reg, str|func)

拆分字符串

  • str.split(str|reg)

g/y标识

  • g标识用于获取所有匹配项(全局搜索)
  • y标识用于在文本指定位置搜索匹配项

参考链接

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Luppiter.W

你的鼓励是我创作最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值