普通人的JavaScript正则表达式

来自菜鸟教程
跳转至:导航、​搜索

正则表达式,也称为正则表达式或正则表达式,是一个难以解决的问题。 如果您还没有 100% c 对编写自己的正则表达式感到满意,请不要感到羞耻,因为它确实需要一些时间来适应。 我希望在本文结束时,您将更接近在 JavaScript 中使用自己的表达式,而无需过多依赖 Stack Overflow 中的 copypasta。

编写正则表达式的第一步是了解如何调用它。 在 JavaScript 中,正则表达式是 标准的内置对象 。 因此,我们可以通过以下几种方式创建一个新的 RegExp 对象:

  • 字面意思,/expression/.match('string to test against')
  • 带字符串参数的 new 关键字 new RegExp('expression')
  • 带文字的 new 关键字 new RegExp(/expression/)

我将使用这些方法的组合来证明它们本质上执行相同的工作。

我们正则表达式的目标

在我的示例中,我将使用包含我的名字、姓氏和域名的字符串。 在现实世界中,这个例子需要更多的思考。 在 处理名称 时有许多微妙之处,我不会在这里讨论。

假设我正在构建一个仪表板并希望显示登录用户的名称。 我无法控制返回给我的数据,所以我必须使用我所拥有的。

我需要将 aaron.arney:alligator.io 转换为 Aaron Arney [Alligator]

正则表达式将许多逻辑融入到单个压缩对象中。 这可能而且将会引起混乱。 一个好的做法是将表达式分解为一种伪代码形式。 这使我们能够看到需要发生什么以及何时发生。

  1. 提取名字
  2. 提取姓氏
  3. 提取域名
  4. 将字符串格式化为所需的模板格式 First Last [Domain]

匹配名字

要将字符串与正则表达式匹配,您所要做的就是传递文字字符串。 表达式末尾的 i 是一个标志。 i 标志特别代表 case insensitive。 这意味着我们的表达式忽略字符串上的大小写。

const unformattedName = 'aaron.arney:alligator.io';

const found = unformattedName.match(/aaron/i);

console.log(found);
// expected output: Array [ "aaron" ]

这很好用,但在我们的例子中,这不是一个好方法,因为用户的名字并不总是“Aaron”。 这是我们探索以编程方式匹配字符串的地方。

让我们暂时专注于匹配名字。 把单词分解成单个字符,你看到了什么?

“Aaron”这个名字由五个字母字符组成。 every 名字是否只有 五个字符? 不,但可以合理地假设名字可以在 1 到 15 个字符之间。 为了表示 az 范围内的字符,我们使用 [a-z]

现在,如果我们更新我们的表达式以使用这个字符类...…

const unformattedName = 'aaron.arney:alligator.io';

const found = unformattedName.match(/[a-z]/i);

console.log(found);
// expected output: Array [ "a" ]

它不是从字符串中提取“aaron”,而是只返回“a”。 这很好,因为正则表达式会尽可能少地匹配。 要重复字符匹配一个数字,直到我们的限制为 15,我们使用大括号。 这告诉我们观察的表达式匹配前面的标记,我们的“az”,匹配 1 到 15 次。

const unformattedName = 'aaron.arney:alligator.io';
const unformattedNameTwo = 'montgomery.bickerdicke:alligator.io';
const unformattedNameThree = 'a.lila:alligator.io';

const exp = new RegExp(/[a-z]{1,15}/, 'i');

const found = unformattedName.match(exp);
const foundTwo = unformattedNameTwo.match(exp);
const foundThree = unformattedNameThree.match(exp);

console.log(found);
// expected output: Array [ "aaron" ]

console.log(foundTwo);
// expected output: Array [ "montgomery" ]

console.log(foundThree);
// expected output: Array [ "a" ]

匹配姓氏

提取姓氏应该像复制和粘贴我们的第一个表达式一样简单。 您会注意到匹配仍然返回相同的值,而不是同时返回名字和姓氏。

逐个字符分解字符串,名称之间用句号分隔。 为了解决这个问题,我们在表达式中添加了句号。

我们必须在这里小心。 . 可以表示表达式中的两件事之一。

  • . - 匹配除换行符以外的任何字符
  • \. - 匹配一个 .

在这种情况下使用任一版本都会产生相同的结果,但情况并非总是如此。 eslint 之类的工具有时会将转义序列 \ 标记为不必要,但我说安全总比抱歉好!

const unformattedName = 'aaron.arney:alligator.io';

const exp = new RegExp(/[a-z]{1,15}\.[a-z]{1,15}/, 'i');

const found = unformattedName.match(exp);

console.log(found);
// expected output: Array [ "aaron.arney" ]

由于我们更喜欢将字符串拆分为两个项目,并且不包括表达式返回的句号,我们现在可以使用 capturing groups。 这些由括号 () 表示,并环绕您想要返回的表达式部分。 如果我们将它们包裹在名字和姓氏表达式周围,我们将得到新的结果。

使用捕获组的语法很简单:(expression)。 由于我只想返回我的名字和姓氏并且 而不是 句号,因此将表达式括在括号中。

const unformattedName = 'aaron.arney:alligator.io';

const exp = new RegExp(/([a-z]{1,15})\.([a-z]{1,15})/, 'i');

const found = unformattedName.match(exp);

console.log(found);
// expected output: Array [ "aaron.arney", "aaron", "arney" ]

匹配域名

为了提取“alligator.io”,我们将使用到目前为止我们已经使用过的字符类。 当然,稍作修改。

验证域名和 TLD 是一项艰巨的任务。 我们将假设我们解析的域总是 > 3 && < 25 字符。 TLD 始终为 > 1 && < 10。 如果我们插入这些,我们将得到一些新的输出:

const unformattedName = 'aaron.arney:alligator.io';

const exp = new RegExp(/([a-z]{1,15})\.([a-z]{1,15}):([a-z]{3,25}\.[a-z]{2,10})/, 'i');

const found = unformattedName.match(exp);

console.log(found);
// expected output: Array [ "aaron.arney:alligator.io", "aaron", "arney", "alligator.io" ]

捷径

我向你展示了关于表达的“漫长的道路”。 现在,我将向您展示如何使用不太冗长的表达式来捕获相同的文本。 通过使用 + 量词,我们可以告诉我们的表达式尽可能多地重复前面的标记。 它将一直持续到它遇到死胡同,在我们的例子中是句号。 这个表达式还引入了 g 标志,它代表 global。 它告诉表达式我们想要尽可能多地重复我们的搜索,而不是最少的次数。

// With the global flag
'aaron.arney:alligator.io'.match(/[a-z]+/ig);
// expected output: Array(4) [ "aaron", "arney", "alligator", "io" ]

// Without the global flag
'aaron.arney:alligator.io'.match(/[a-z]+/i);
// expected output: Array(4) [ "aaron" ]

格式化输出

要格式化字符串,我们将在 String 对象上使用 替换方法replace 方法有两个参数:

  • RegExp | String - 正则表达式对象或文字
  • RegExp | function - 正则表达式或函数
const unformattedName = 'aaron.arney:alligator.io';

// The "long" way
const exp = new RegExp(/([a-z]{1,15})\.([a-z]{1,15}):([a-z]{3,25}\.[a-z]{2,10})/, 'i');

unformattedName.replace(exp, '$1 $2 [$3]');
// expected output:  "aaron arney [alligator.io]"

// A slightly shorter way
unformattedName.replace(/([a-z]+)\.([a-z]+):([a-z]+\.[a-z]{2,10})/ig, '$1 $2 [$3]');
// expected output: "aaron arney [alligator.io]"

在上面的代码片段中,$1$2$3 是由 replace 方法解释的特殊模式。

  • $1 - The first result from the match array =>A reference to the first parenthesized group
  • $2 - The second result from the match array=> A reference to the second parenthesized group
  • $n - 以此类推

为了使单词大写,我们可以使用另一个正则表达式。 我们将传递一个函数,而不是像上面那样格式化输出。 该函数将提供的参数大写并返回它。

在这里,我将介绍几个新部分,anchorsalternation 和一个新的字符类 [^]

  • [^abc] - 不是 abc
  • \b - 字边界
  • ab|cd - 逻辑“或”,匹配 abcd
// Capitalize the words
"aaron arney [alligator.io]".replace(/(^\b[a-z])|([^\.]\b[a-z])/g, (char) => char.toUpperCase());
// expected output: "Aaron Arney [Alligator.io]"

把这个表达式分成两部分…

  • (^\b[a-z]) - 捕获字符串的第一个字符。 ^ 表示匹配字符串的开头。
  • |([^\.]\b[a-z]) - 或者,匹配 以句点 . 开头的新词,因为这是 TLD。

继续你的探索

这只是对正则表达式强大功能的一小部分了解。 我完成的示例是可以改进的,但是如何改进呢?

  • 表达方式是否过于冗长? 是不是太简化了?
  • 它是否涵盖边缘情况?
  • 你能用本机方法的一些巧妙的字符串操作来替换它吗?

在这里,您可以获取所学知识并尝试回答这些问题。 探索以下资源,以帮助您的旅程和实验!