menu 光风霁月。
正则表达式(上)
346 浏览 | 2020-05-30 | 阅读时间: 约 4 分钟 | 分类: Javascript 原理 | 标签: 正则表达式
请注意,本文编写于 145 天前,最后修改于 128 天前,其中某些信息可能已经过时。

前言

做前端开发避免不了要使用正则表达式来匹配一些特定格式的数据,如手机号一般都是 11 位,email 类似于 XXX@YYY.com,所以这里总结了正则表达式在JS中的一般的使用方法,做开发足够了!

本文参考自 《JavaScript学习指南》 EthonBrown著

字符串原型中的方法

String.prototype 中有一些字符串搜索和匹配的方法来满足基本需要, 字符串原型中的方法均不会修改原字符串 => 字符串在 JavaScript 中是 不可变的

const input = "As I was going to Saint Ives";
input.startsWith("As");             // true
input.endsWith("Ives");             // true
input.startsWith("going", 9);       // true
input.endsWith("going", 14);        // true
input.includes("going");            // true
input.indexOf("going");             // 9
input.indexOf("going", 10);         // -1
input.indexOf("nope");              // -1
input.replace("going", "walking");  // 返回新字符串, 原字符串不变

构造正则表达式

JavaScript 中,通过 RegExp 这个类来表示正则表达式。有两种方式构造正则表达式,优先使用字面量语法。

const re1 = /going/;                // 通过字面量语法
const re2 = new RegExp("going");    // 通过对象构造器

使用正则表达式搜索 、替换

使用正则表达式可以进行搜索, 可以选择从字符串开始或是从正则表达式开始。

// ------------------------------查找-----------------------------------
const input = "As I was going to Saint Ives";
const re = /\w{3,}/ig;    // 包含三个及三个以上的字母单词(不区分大小写)
// 从 input 开始
input.match(re);          // ["was", "going", "Saint", "Ives"]
input.search(re);         // 5
// 从 re 开始
re.test(input);            // true
re.exec(input);            // ["was"]
re.exec(input);            // ["going"]
re.exec(input);            // more and more content until => null
// ------------------------------替换-----------------------------------
const output = input.replace(re, "something meaningful");

分支

当需要包含多个匹配项时, 可以使用分支, 即使用 | 进行分隔。

尽管正则表达式很强大,但是用它来解析 HTML 是不行的,HTML具有等级结构,正则表达式会受限。

const html = 'HTML with <a href="/one">one link</a>, and some JavaScript.' + '<script src="stuff.js"></script>';
const matches = html.match(/area|a|<link|script|source/ig);
// 这里的 ig 表示忽略大小写(i), 和全局搜索(g), 如果不是全局就会返回第一个匹配项

字符集

下面的例子会匹配所有除了空格的字符, [0-9][a-z] 是为了方便书写而制定的字符集, 字符集可以使用 ^ 进行取反。在使用关键字符时记得进行转义, 比如 \- 就是为了将其变为不同字符, 否则正则表达式会尝试把它翻译为查找范围的一部分。

const beer99 = "99 bottles on the wall" +
"take 1 down and pass it around --" +
"98 bottles of beer on the wall.";
const matches = beer99.match(/[\-0-9a-z.]/ig);
const matches = beer99.match(/[^\-0-9a-z.]/ig);

为了再次简化书写, 可使用 具名字符集 来书写正则表达式

具名字符集等价物注释
d[0-9]
D[^0-9]
s[\t \v \n \r]制表符, 空格, 垂直制表符
S[^tvn r]
w[a-zA-Z]
W[^a-zA-Z]破折号, 句号未包含进来
const stuff =
'hight:      9\n' +
'medium:     5\n' +
'low:        2\n';
const levels = stuff.match(/:\s*\d/g);
// 输出结果为: [":      9",":     5",":        2"]

反字符集的用处通常为数据验证与处理(如: 把用户输入的电话号码规范化)

const messyPhone = '(505) 555-1515';
const neatPhone = messyPhone.replace(/\D/g, '');
// 至少包含一个非空格的字符
const field = '  something  ';
const valid = /\S/.test(field);

重复

重复 => 前一个元素应该至少被匹配一次 => 使用 + 来表示。

重复元字符是一种修饰器, 它不会且不能单独出现。

const match = beer99.match(/[0-9][0-9][0-9] ...more/);
const match = beer99.match(/[0-9]+/);
重复修饰符描述示例
{n}精确 n 次/\d{5}/ 匹配 5 位数字
{n,}至少 n 次/\d{5,}/ 匹配 >= 5 位上的数字
{n,m}最少 n 次, 最多 m 次/\d{2,5}/ 匹配 2 到 5 位数字
?0 或 1 次, 等价于{0, 1}/[a-z]\d?/i匹配跟随 0 或 1 个数字的字符
*0 或 多次/[a-z]\d*/i匹配跟随 0 或 多个数字的字母
+1 或 多次/[a-z]\d+/i匹配至少跟随 1 个数字的字母

在正则表达式中, 句点是一个特殊字符, 表示匹配任何内容 (除了新的一行) , 因此真正的通配符是 [\s\S]

分组

分组允许创建子表达式, 之前的匹配都是基于单个字符, 而分组可以作为一个独立单元

捕获组 使用 () 非捕获组 使用 (?:) 非捕获组有一些性能优势。

const html = 
"<link rel='stylesheet' href='http://insecure.com/stuff.css'></link>\n" + 
"<link rel='stylesheet' href='htttps://secure.com/other.css'></link>\n" +
"<link rel='stylesheet' href='//anything.com/flexible.css'></link>"

const matches = "html.match(/(?:https?)?\/\/[a-z][a-z0-9-]+[a-z0-9]+/ig)"

在实际的使用中, 没有必要一次性构建出完美的 正则表达式 而是采用少量、多次的方法, 比如要找出 URL, 没必要花费很多的精力去一次性构建, 可以第一次先找出 类似于 URL 的字符串, 然后再做进一步处理。

懒惰匹配、贪婪匹配

据说能理解这里的人都是专业开发者, 菜鸟与大佬的分水岭。

const input = "Regex pros know the difference between\n" + 
      "<i>greed</i> and <i>lazy</i> matching.";
input.replace(/<i>(.*)<\/i>/ig, '<strong>$1</strong>');

令人失望的结果:

Regex pros know the difference between <strong>greedy</i> and <i> lazy</strong> matching

最外部的 i 标签被替换, 这是因为正则表达式的默认工作方式为贪婪模式, 也就是在找到匹配的字符串后, 它能确定不会再出现同样的字符串, 否则会一直寻找下去。

为了改变结果, 可以使用 ? 来改变正则表达式的工作方式。

input.replace(/<i>(.*?)<\/i>/ig, '<strong>$1</strong>');

所有的重复元字符: *+?{n}{n,}{n,m}都可以在其后跟随一个问号将它变成懒惰模式。

反向引用

正则表达式最不常用的特性之一, 除了引号匹配

const promo = "Opening for XAAX is the dynamic GOOG!" +
            "At the box office now!";
const bands = promo.match(/(?:[A-Z])(?:[A-Z])\2\1/g);  -error
const bands = promo.match(/([A-Z])([A-Z])\2\1/g);       -right

在上面的例子中, 后面的 \2 \1 代表的是前面的分组匹配到的所有内容, 12 是前面分组的顺序。JavaScript学习指南上这本书上的例子是错误的, 因为 ?:代表的是不捕获。

引号匹配

const html = '<img alt="A `simple` example.">' +
            '<img alt="Dont't abuse it">';
const matches1 = html.match(/<img alt=(['"]).*\1/g);
const matches2 = html.match(/<img alt=(['"]).*?\1/g);
// 结果1 匹配到1个 (贪婪模式)
["<img alt=\"A `simple` example.\"><img alt=\"Dont`t abuse it\""]
// 结果2 匹配到两个 (懒惰模式)
["<img alt=\"A `simple` example.\"","<img alt=\"Dont`t abuse it\""]

上面的字符串 html 中使用了重音符, 因为单引号、双引号都用光了。

重点看后面的 \1 是匹配了前面的单引号或双引号, 因为在 html 里单 双引号均可。

而且看 * 后面是 ? , 采用了前面讲述的懒惰模式。

知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议

发表评论

email
web

全部评论 (暂无评论)

info 还没有任何评论,你来说两句呐!