<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ 正则表达式 - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ freeCodeCamp 是一个免费学习编程的开发者社区，涵盖 Python、HTML、CSS、React、Vue、BootStrap、JSON 教程等，还有活跃的技术论坛和丰富的社区活动，在你学习编程和找工作时为你提供建议和帮助。 ]]>
        </description>
        <link>https://www.freecodecamp.org/chinese/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ 正则表达式 - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/chinese/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Tue, 09 Jun 2026 04:20:12 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/chinese/news/tag/regular-expressions/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ 适合初学者的简单正则表达式技巧 ]]>
                </title>
                <description>
                    <![CDATA[ 一直想学习正则表达式但被它们的复杂性劝退了？在本文中，我将向你展示五个易于学习的正则技巧，你可以立即在你最喜欢的文本编辑器中开始使用它们。 文本编辑器设置 虽然现在几乎所有文本编辑器都支持正则表达式，但我将在本教程中使用 Visual Studio Code。你可以使用任何你喜欢的编辑器。另外，请注意，你通常需要在搜索输入附近的某处打开 RegEx。这是在 VS Code 中执行此操作的方法： You need to enable RegEx by checking this option1) . — 匹配任何字符 让我们从简单的开始。点符号 . 匹配任何字符： b.t 上面的 RegEx 匹配 "bot”、"bat” 和任何其他以 b 开头并以 t 结尾的三个字符的单词。但是如果你想搜索点符号，你需要用 \  对其进行转义，所以这个 RegEx 只会匹配精确的文本 "b.t"： b\.t 2) .* — 匹配任何字符 这里 . 表示“任何字符”，* 表示“此符号之前的任何内容重复任意次数”。它们一起（.* ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/simple-regex-tricks-for-beginners/</link>
                <guid isPermaLink="false">62fdc03b60480505ded7a71f</guid>
                
                    <category>
                        <![CDATA[ 正则表达式 ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Chengjun.L ]]>
                </dc:creator>
                <pubDate>Mon, 19 Aug 2024 04:19:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2024/08/pexels-dmitry-demidov-515774-3852577.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>原文：</strong> <a href="https://www.freecodecamp.org/news/simple-regex-tricks-for-beginners-3acb3fa257cb/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">Simple RegEx tricks for beginners</a>
      </p><p>一直想学习正则表达式但被它们的复杂性劝退了？在本文中，我将向你展示五个易于学习的正则技巧，你可以立即在你最喜欢的文本编辑器中开始使用它们。</p><h3 id="-">文本编辑器设置</h3><p>虽然现在几乎所有文本编辑器都支持正则表达式，但我将在本教程中使用 Visual Studio Code。你可以使用任何你喜欢的编辑器。另外，请注意，你通常需要在搜索输入附近的某处打开 RegEx。这是在 VS Code 中执行此操作的方法：</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://cdn-media-1.freecodecamp.org/images/GjEq59Gj7Io7MWY4OuayOCmqZo0f5ezXyvOS" class="kg-image" alt="GjEq59Gj7Io7MWY4OuayOCmqZo0f5ezXyvOS" width="600" height="400" loading="lazy"><figcaption>You need to enable RegEx by checking this option</figcaption></figure><h3 id="1-"><strong>1) <code>. </code>— </strong>匹配任何字符</h3><p>让我们从简单的开始。点符号 <code>.</code> 匹配任何字符：</p><pre><code>b.t</code></pre><figure class="kg-card kg-image-card"><img src="https://cdn-media-1.freecodecamp.org/images/tMEDSKS2mHfOfqYTQP-elCZI5OnGUObC484v" class="kg-image" alt="tMEDSKS2mHfOfqYTQP-elCZI5OnGUObC484v" width="600" height="400" loading="lazy"></figure><p>上面的 RegEx 匹配 <code>"bot”</code>、<code>"bat”</code> 和任何其他以 <code>b</code> 开头并以 <code>t</code> 结尾的三个字符的单词。但是如果你想搜索点符号，你需要用 <code>\</code> 对其进行转义，所以这个 RegEx 只会匹配精确的文本 <code>"b.t"</code>：</p><pre><code>b\.t</code></pre><figure class="kg-card kg-image-card"><img src="https://cdn-media-1.freecodecamp.org/images/anNgoajLGzpFhPWYRnVGlowi0bL4Z4xni59R" class="kg-image" alt="anNgoajLGzpFhPWYRnVGlowi0bL4Z4xni59R" width="600" height="400" loading="lazy"></figure><h3 id="2-"><strong>2) .* — </strong>匹配任何字符</h3><p>这里 <code>.</code> 表示“任何字符”，<code>*</code> 表示“此符号之前的任何内容重复任意次数”。它们一起（<code>.*</code>）表示“任何符号任意次数”。例如，你可以使用它来查找以某些文本开头或结尾的匹配项。假设我们有一个带有以下签名的 JavaScript 方法：</p><pre><code>loadScript(scriptName: string, pathToFile: string)</code></pre><p>我们想要找到这个方法的所有调用，其中 <code>pathToFile</code> 指向文件夹 <code>“lua”</code> 中的任何文件。你可以为此使用以下正则表达式：</p><pre><code>loadScript.*lua</code></pre><p>这意味着，匹配所有以 <code>“loadScript”</code> 开头的文本，然后是最后一次出现的 <code>“lua”</code>。</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://cdn-media-1.freecodecamp.org/images/wCWC964KLZKxEHdW0fZlr4Z-X-vdcbYX-ogk" class="kg-image" alt="wCWC964KLZKxEHdW0fZlr4Z-X-vdcbYX-ogk" width="600" height="400" loading="lazy"><figcaption><code style="box-sizing: inherit; margin: 0px; padding: 0px 5px 2px; border: 0px; font-style: inherit; font-variant: inherit; font-weight: 400 !important; font-stretch: inherit; line-height: 1em; font-family: &quot;Roboto Mono&quot;, monospace; font-size: 0.8em; vertical-align: baseline; background: var(--gray15);">loadScript.*lua: matches everything starting with "loadScript" and ending in "lua"</code></figcaption></figure><h3 id="3-"><strong>3) ? — </strong>非贪婪匹配</h3><p><code>.*</code> 后面的 <code>?</code> 和其他一些 RegEx 序列之后的符号表示“尽可能少地匹配”。如果你查看上一张图片，你会看到文本 <code>“lua”</code> 在每次匹配中出现两次，并且匹配到第二个 <code>“lua”</code> 的所有内容。如果你想将所有内容匹配到第一次出现的 <code>"lua"</code>，你将使用以下正则表达式：</p><pre><code>loadScript.*?lua</code></pre><p>这意味着，匹配以 <code>"loadScript"</code> 开头的所有内容，然后是第一次出现的 <code>"lua"</code>。</p><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/news/content/images/2022/08/1660810429783.jpg" class="kg-image" alt="1660810429783" width="600" height="400" loading="lazy"></figure><h3 id="4-"><strong>4) ( ) $ — </strong>捕获组和反向引用</h3><p>好的，现在我们可以匹配一些文本。但是如果我们想改变我们找到的部分文本呢？为此，我们经常不得不使用捕获组。</p><p>假设我们更改了 <code>loadScript</code> 方法，现在它突然需要在两个参数之间插入另一个参数。让我们将这个新参数命名为 <code>id</code>，因此新函数签名应该是 <code>loadScript(scriptName, id, pathToFile)</code>。我们不能在这里使用文本编辑器的普通替换功能，但正则表达式正是我们所需要的。</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://cdn-media-1.freecodecamp.org/images/hRdlYnNzYuX64kcVoXrvtH2RfwaY3FzNZedD" class="kg-image" alt="hRdlYnNzYuX64kcVoXrvtH2RfwaY3FzNZedD" width="600" height="400" loading="lazy"><figcaption>loadScript\(.*?,.*?\)</figcaption></figure><p>上面你可以看到运行以下正则表达式的结果：</p><pre><code>loadScript\(.*?,.*?\)</code></pre><p>这意味着：匹配以 <em><em><code>"loadScript("</code></em></em> 开头的所有内容，然后是第一个 <em><em><code>,</code></em></em> 之前的任何内容，然后是第一个 <em><em><code>)</code></em></em> 之前的任何内容。</p><p>唯一对你来说可能看起来很奇怪的是 <code>\</code> 符号。它们用于转义括号。</p><p>我们需要对符号 <code>(</code> 和 <code>)</code> 进行转义，因为它们是 RegEx 用来捕获部分匹配文本的特殊字符。但是我们需要匹配实际的括号字符。</p><p>在前面的 RegEx 中，我们用 .*? 定义了方法调用的两个参数。符号。让我们通过在它们周围添加 ( 和 ) 符号来使我们的每个参数成为一个单独的捕获组：</p><pre><code>loadScript\((.*?),(.*?)\)</code></pre><p>如果你运行这个正则表达式，你会发现没有任何改变。这是因为它匹配相同的文本。但是现在我们可以将第一个参数称为 <code>$1</code>，将第二个参数称为 <code>$2</code>。这可被称为反向引用，它将帮助我们做我们想做的事情：在调用中间添加另一个参数。</p><p>搜索输入：</p><pre><code>loadScript\((.*?),(.*?)\)</code></pre><p>这意味着与之前的正则表达式相同，但将参数分别映射到捕获组 1 和 2。</p><p>替换输入：</p><pre><code>loadScript($1,id,$2)</code></pre><p>这意味着将每个匹配的文本替换为文本 <code><em><em>“loadScript(“</em></em></code>，后跟捕获组 1、<code><em><em>“id”</em></em></code>、捕获组 2 和 <code><em><em>)</em></em></code>。请注意，你不需要在替换输入中转义括号。</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://cdn-media-1.freecodecamp.org/images/w27UNrc7N2hkWAO1DmU6p0gulIYiwU-oYjpT" class="kg-image" alt="w27UNrc7N2hkWAO1DmU6p0gulIYiwU-oYjpT" width="600" height="400" loading="lazy"><figcaption>替换结果</figcaption></figure><h3 id="5-"><strong>5) [ ] — </strong>字符类</h3><p>你可以通过在这些字符周围放置 <code>[</code> 和 <code>]</code> 符号来列出要在特定位置匹配的字符。例如，class <code>[0-9]</code> 匹配从 0 到 9 的所有数字。你也可以显式列出所有数字：<code>[0123456789]</code> ——含义相同。你也可以将破折​​号与字母一起使用，<code>[a-z]</code> 将匹配任何小写拉丁字符，<code>[A-Z]</code> 将匹配任何大写拉丁字符，而 <code>[a-zA-Z]</code> 将匹配两者。</p><p>你也可以在字符类之后使用 <code>*</code>，就像在 <code>.</code> 之后一样，在这种情况下，这意味着：匹配该类中出现的任意数量的字符。</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://cdn-media-1.freecodecamp.org/images/2aTqw0lDyht0cE1gqoF3O5eYcemyzhIBhSzU" class="kg-image" alt="2aTqw0lDyht0cE1gqoF3O5eYcemyzhIBhSzU" width="600" height="400" loading="lazy"><figcaption>expect.*to.equal\([0–9]*\)：仅匹配我们期望被测变量等于数字的那些行</figcaption></figure><h3 id="--1"><strong>总结</strong></h3><p>你应该知道有几种正则表达式风格。我在这里讨论的是 JavaScript RegEx 引擎。大多数现代引擎都是相似的，但可能存在一些差异。通常，这些差异包括转义字符和反向引用标记。</p><p>我建议你立即打开文本编辑器并开始使用其中的一些技巧。你将看到自己现在可以比以前更快地完成许多重构任务。如果你对这些技巧感到满意，<a href="https://www.regular-expressions.info/">你就可以开始更多地研究正则表达式</a>。</p><p>感谢你阅读这篇文章。如果你觉得它有帮助，请将它分享给更多人。</p> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ JavaScript 中的正则表达式（RegEx）——初学者手册 ]]>
                </title>
                <description>
                    <![CDATA[ 正则表达式，也被称为 regex，是用于模式匹配和文本处理的强大工具。无论是验证用户输入、从字符串中提取数据，还是进行高级的文本处理任务，理解正则表达式对开发人员来说都是必不可少的。 这份全面的指南将带领你了解 JavaScript 中正则表达式的基础知识，包括如何创建和使用正则表达式、正则表达式中的特殊字符、标志以及一些实际的示例。 预备知识 虽然本教程旨在适合初学者，但具备基本的 JavaScript 基础知识将会有所帮助。熟悉 JavaScript 中的变量、数据类型、函数和字符串处理将有助于理解本教程涵盖的概念。 目录  1.  什么是正则表达式      – 如何编写一个正则表达式的模式  2.  如何在JavaScript中使用正则表达式      – JavaScript中的正则表达式模式      – 通过标志进行高级搜索  3.  正则表达式中的锚点      ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/regex-in-javascript/</link>
                <guid isPermaLink="false">6602b1876f02f80413b539fe</guid>
                
                    <category>
                        <![CDATA[ 正则表达式 ]]>
                    </category>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Tsukistar ]]>
                </dc:creator>
                <pubDate>Tue, 26 Mar 2024 13:13:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2024/03/Regular-Expressions-in-JavaScript-Cover-2.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>原文：</strong> <a href="https://www.freecodecamp.org/news/regex-in-javascript/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">Regular Expressions (RegEx) in JavaScript – A Handbook for Beginners</a>
      </p><!--kg-card-begin: markdown--><p>正则表达式，也被称为 regex，是用于模式匹配和文本处理的强大工具。无论是验证用户输入、从字符串中提取数据，还是进行高级的文本处理任务，理解正则表达式对开发人员来说都是必不可少的。</p>
<p>这份全面的指南将带领你了解 JavaScript 中正则表达式的基础知识，包括如何创建和使用正则表达式、正则表达式中的特殊字符、标志以及一些实际的示例。</p>
<h3 id="">预备知识</h3>
<p>虽然本教程旨在适合初学者，但具备基本的 JavaScript 基础知识将会有所帮助。熟悉 JavaScript 中的变量、数据类型、函数和字符串处理将有助于理解本教程涵盖的概念。</p>
<h2 id="">目录</h2>
<ol>
<li><a href="#what-are-regex">什么是正则表达式</a><br>
– <a href="#how-to-write-regular-expression-patterns">如何编写一个正则表达式的模式</a></li>
<li><a href="#how-to-use-regular-expressions-in-javascript">如何在JavaScript中使用正则表达式</a><br>
– JavaScript中的正则表达式模式<br>
– <a href="#advanced-searching-with-flags">通过标志进行高级搜索</a></li>
<li><a href="#anchors-in-regex">正则表达式中的锚点</a><br>
– <a href="#multiline-mode-of-anchors-and-">锚点的多行模式</a><br>
– <a href="#word-boundaries-b-">单词边界（<code>\b</code>）</a></li>
<li><a href="#quantifiers-in-regex">正则表达式中的量词</a><br>
– <a href="#greedy-quantifiers-">贪婪量词</a><br>
– <a href="#non-greedy-quantifiers">非贪婪量词（懒惰模式）</a></li>
<li><a href="#sets-and-ranges-in-regex">正则表达式中的集合与区间</a><br>
– <a href="#sets-">集合</a><br>
– <a href="#ranges-">区间</a><br>
– <a href="#negating-excluding-ranges-">否定/排除区间</a><br>
– <a href="#predefined-character-classes-">预定义的字符类</a></li>
<li><a href="#special-characters-and-escaping-in-regex">正则表达式中的特殊字符与转义</a><br>
– <a href="#metacharacters-">元字符</a><br>
– <a href="#escape-special-characters-">转义特殊字符</a></li>
<li><a href="#groupings-in-regex">正则表达式中的分组</a><br>
– <a href="#capturing-groups-">捕获组</a><br>
– <a href="#non-capturing-groups-">非捕获组</a><br>
– <a href="#backreferences-">后向引用</a><br>
– <a href="#regex-alternation">正则表达式选择符号</a></li>
<li><a href="#lookahead-and-lookbehind-in-regex">正则表达式中的前瞻断言和后顾断言</a><br>
– <a href="#lookahead-">前瞻断言（?=）</a><br>
– <a href="#negative-lookaheads-">否定前瞻断言（?!）</a><br>
– <a href="#lookbehind-">后顾断言（?&lt;=）</a><br>
– <a href="#negative-lookbehind-">否定后顾断言（?&lt;!）</a></li>
<li><a href="#practical-examples-and-use-cases-of-regex">正则表达式的实际应用示例</a><br>
– <a href="#password-strength-checking-">密码强度检查</a><br>
– <a href="#email-validation-function-">电子邮件地址校验</a><br>
– <a href="#phone-number-formatting-function-">电话号码格式化函数</a></li>
<li><a href="#tips-and-best-practices-for-using-regular-expressions">使用正则表达式的技巧和最佳实践方式</a></li>
<li><a href="#conclusion">总结</a></li>
</ol>
<h2 id="what-are-regex">什么是正则表达式</h2>
<p>正则表达式是一种定义搜索模式的字符序列，通常缩写为“regex”。这种模式提供了一种强大的方式来搜索、替换和操作文本，它被用于在字符串中查找匹配项，帮助你识别特定的文本或字符模型。</p>
<p>在JavaScript中，你可以使用字面量或<code>RegExp</code>构造函数创建正则表达式：</p>
<ul>
<li><strong>使用正则表达式字面量</strong>：模式由斜杠（"/"）包围。</li>
</ul>
<pre><code class="language-javascript">const re = /pattern/;

// 示例
const re = /ab+c/;
</code></pre>
<ul>
<li><strong>使用构造函数：</strong><code>RegExp</code> 构造函数。这种方式允许对正则表达式进行运行时编译，并且在模式可能更改时非常有用。</li>
</ul>
<pre><code class="language-javascript">const re = new RegExp("pattern");

// 示例
const re = new RegExp("ab+c");
</code></pre>
<p>两种方法会产生相同的结果——选择哪一种取决于你的偏好。</p>
<h3 id="how-to-write-regular-expression-patterns">如何编写一个正则表达式的模式</h3>
<p>一个正则表达式模式是由简单的字符或者是简单和特殊字符的组合所构成的。</p>
<ol>
<li><strong>简单模式</strong>：它们匹配精确的字符序列。例如，模式 <code>/abc/</code> 匹配字符串中的序列"abc"。</li>
<li><strong>特殊字符</strong>：它们通过重复或匹配特定类型的字符等功能增强了模式匹配，从而实现了更灵活、更强大的模式匹配。例如，<code>*</code> 匹配前一项出现0次或多次。<code>/ab*c/</code> 匹配 "ac"、"abc"、"abbc" 等等诸如这类形式的字符串。</li>
</ol>
<h2 id="how-to-use-regular-expressions-in-javascript">如何在JavaScript中使用正则表达式</h2>
<p>你可以使用JavaScript中适用于 <code>RegExp</code> 和 <code>String</code> 对象的多种方法来使用正则表达式，例如<code>test()</code>和 <code>exec()</code>方法，以及具有如下语法的方法：</p>
<pre><code class="language-javascript">regex.methodname(string)

// 示例
string.test(string)
</code></pre>
<p>以及类似于 <code>match()</code> 和 <code>replace()</code> 方法，以及具有这种语法的方法：</p>
<pre><code class="language-javascript">string.methodname(regex)

// 示例
string.replace(regex, replacement)
</code></pre>
<p>在这里， <code>string</code> 是字符串， <code>regex</code> 是一种正则表达式模式。</p>
<p>让我们来探讨一下这些方法在实践中是如何使用的。</p>
<p><strong><code>test()</code> 方法</strong>：检查特定字符串是否与指定模式或正则表达式匹配。如果字符串中找到了该模式，则返回 <code>true</code>；否则返回 <code>false</code>。</p>
<pre><code class="language-javascript">let pattern = /hello/;
let str = "hello world";

let result = pattern.test(str);
console.log(result); // 输出为：true
</code></pre>
<p><strong><code>exec()</code> 方法</strong>：根据正则表达式模式的内容搜索字符串中的匹配项。它返回一个数组，其中包含匹配文本、匹配项在字符串中的索引以及输入字符串本身的详细信息。例如：</p>
<pre><code class="language-javascript">let pattern = /world/;
let str = "hello world";

let result = pattern.exec(str);
console.log(result); // 输出为：["world", index: 6, input: "hello world"]
</code></pre>
<p><strong><code>match()</code>方法</strong>: 根据正则表达式模式的内容，在字符串中搜索它的出现次数。它返回匹配的第一个元素。如果具有全局标志（<code>g</code>），则返回一个包含所有找到的匹配项的数组，如果没有找到匹配项，则返回 <code>null</code>。</p>
<pre><code class="language-javascript">let str = "The quick brown fox jumps over the lazy dog.";
let matches = str.match(/the/gi);

console.log(matches); // 输出为： ["The", "the"]
</code></pre>
<p><code>/the/gi</code> 在字符串中搜索所有出现的单词 "the"，不区分大小写。</p>
<p><strong><code>matchAll()</code>方法</strong>: 返回一个用于匹配字符串中正则表达式的所有结果的迭代器。迭代器的每个元素都是一个数组，包含有关匹配的详细信息，包括捕获的分组。</p>
<pre><code class="language-javascript">let str = "Hello world! This is a test string.";
let regex = /[a-zA-Z]+/g;

let matches = str.matchAll(regex);

for (let match of matches) {
    console.log(match);
}
</code></pre>
<p>当你需要获取字符串中所有匹配项的详细信息时，此方法非常有用。</p>
<p><strong><code>search()</code>方法</strong>: 在字符串中搜索指定的模式。它返回字符串中模式的第一个出现的索引，如果未找到模式，则返回 <code>-1</code>。</p>
<pre><code class="language-javascript">let str = "The quick brown fox jumps over the lazy dog";
let pattern = /brown/;

let result = str.search(pattern);
console.log(result); // 输出为： 10
</code></pre>
<p><strong><code>replace()</code>方法</strong>：用另一个子字符串或值替换字符串中指定模式的第一个实例。要替换所有实例，可以在正则表达式中使用全局标志（<code>g</code>）。</p>
<pre><code class="language-javascript">let str = "Hello, World!";
let newStr = str.replace(/o/g, "0");

console.log(newStr); // 输出为： "Hell0, W0rld!"
</code></pre>
<p><strong><code>replaceAll()</code>方法</strong>：替换指定子字符串或模式的所有实例为一个替代字符串。它与 <code>replace()</code> 的不同之处在于，它默认替换所有出现实例，无需使用全局标志（<code>g</code>）。</p>
<pre><code class="language-javascript">let str = "apple,banana,apple,grape";
let newStr = str.replaceAll("apple", "orange");
console.log(newStr); // 输出为： "orange,banana,orange,grape"
</code></pre>
<p>这种方法简化了在字符串中替换所有子字符串实例的过程。</p>
<p><strong><code>split()</code>方法</strong>: 虽然 <code>split()</code> 并不是专属于正则表达式的方法，但它可以接受一个正则表达式模式作为其参数，根据指定的模式或分隔符将字符串拆分为子字符串数组。例如：</p>
<pre><code class="language-javascript">let str = "apple,banana,grape";
let arr = str.split(/,/);
console.log(arr); // 输出为：["apple", "banana", "grape"]
</code></pre>
<p>这些方法根据你的需求提供不同的功能。例如，如果你只需要知道字符串中是否找到了模式，则 <code>test()</code> 或 <code>search()</code> 方法是有效的。如果你需要更多关于匹配的信息，则 <code>exec()</code> 或 <code>match()</code> 方法是合适的。</p>
<h2 id="advanced-searching-with-flags">通过标志进行高级搜索</h2>
<p>在JavaScript中，正则表达式支持模式标志，这些是可选参数，修改了模式匹配的行为。</p>
<p>让我们深入了解两个常见的标志：忽略标志（<code>i</code>）和全局标志（<code>g</code>）。</p>
<h3 id="i">忽略标志（<code>i</code>）</h3>
<p>忽略标志（<code>i</code>）可以使正则表达式在搜索匹配项时忽略大小写敏感性。例如：</p>
<pre><code class="language-javascript">let re = /hello/i;
let testString = "Hello, World!";
let result = re.test(testString);

console.log(result); // 输出为：true
</code></pre>
<p>在这种情况下，正则表达式 <code>/hello/i</code> 会匹配字符串 <code>"Hello"</code>（即使大小写不同），因为我们使用了忽略标志。</p>
<h3 id="g">全局标志（<code>g</code>）</h3>
<p>全局标志（<code>g</code>）允许正则表达式在字符串中找到所有匹配项，而不是在找到第一个匹配项后停止。例如：</p>
<pre><code class="language-javascript">let re = /hi/g;
let testString = "hi there, hi again!";
let result = testString.match(re);

console.log(result); // 输出为：["hi", "hi"]
</code></pre>
<p>在这个例子中，正则表达式 <code>/hi/g</code> 找到了字符串 <code>"hi there, hi again!"</code> 中的两个 <code>"hi"</code> 的实例。</p>
<h3 id="">组合标志</h3>
<p>你可以将标志进行组合以实现特定的匹配行为。例如，同时使用忽略标志（<code>i</code>）和全局标志（<code>g</code>）允许进行不区分大小写的匹配，直到找到模式的所有匹配项。</p>
<pre><code class="language-javascript">let re = /hi/gi;
let testString = "Hi there, HI again!";
let result = testString.match(re);

console.log(result); // 输出为：["Hi", "HI"]
</code></pre>
<p>在这个例子中，正则表达式 <code>/hi/gi</code> 匹配字符串 <code>"Hi there, HI again!"</code> 中的 <code>"Hi"</code> 和 <code>"HI"</code>。</p>
<h3 id="u"><code>u</code> 标志</h3>
<p>虽然不常用，但 <code>u</code> 标志可以正确处理 Unicode 字符，特别是代理项对。代理项对用于表示 UTF-16 编码中基本多文种平面（BMP）之外的字符。</p>
<p><strong>示例：</strong> 让我们考虑一个包含表情符号字符的字符串，并尝试分别使用包含<code>u</code>标志和不包含<code>u</code>标志的正则表达式来匹配它们。</p>
<pre><code class="language-javascript">// 不使用u标志
let result1 = 'Smile Please 😊'.match(/[😒😊🙄]/);
console.log(result1); // 输出为：["�"]

// 使用u标志
let result2 = 'Smile Please 😊'.match(/[😒😊🙄]/u);
console.log(result2); // 输出为：["😊"]
</code></pre>
<p>没有使用 <code>u</code> 标志时，正则表达式无法正确匹配表情符号，因为它们在 UTF-16 编码中表示为代理项对。但是，使用 <code>u</code> 标志时，它可以正确匹配表情符号 <code>'😊'</code>。</p>
<h2 id="anchors-in-regex">正则表达式中的锚点</h2>
<p>锚点是正则表达式中的特殊字符，它们不表示实际字符，而是用于检查字符在字符串中是否处于特定的位置。本文讲解两个主要的锚点：<code>^</code> 和 <code>$</code>。</p>
<p><strong>锚点 <code>^</code></strong>：锚点 <code>^</code> 匹配文本的开头。一般情况下，它检查字符串是否以特定字符或模式开头。</p>
<pre><code class="language-javascript">let str = 'Mountain';
console.log(/^S/.test(str)); // 输出为：false
</code></pre>
<p><strong>锚点 <code>$</code></strong>：锚点 <code>$</code> 匹配文本的结尾。它检查字符串是否以特定字符或模式结尾。</p>
<pre><code class="language-javascript">let str = 'Ocean';
console.log(/n$/.test(str)); // 输出为：true
</code></pre>
<p>你经常会一起使用 <code>^</code> 和 <code>$</code> 来检查字符串是否完全匹配某个模式。</p>
<pre><code class="language-javascript">let isValid = /^\d\d:\d\d$/.test('10:01');
console.log(isValid); // 输出为：true
</code></pre>
<p>这个示例检查输入字符串是否匹配时间格式，例如 "10:01"。</p>
<p>在上面的代码中，<code>^\d\d:\d\d$</code> 确保字符串包含两个数字，后跟一个冒号，然后是两个数字。</p>
<h3 id="multiline-mode-of-anchors-and-">锚点的多行模式（`^` 和 `$`）</h3>
<p>默认情况下，正则表达式中的 <code>^</code> 和 <code>$</code> 锚点以单行模式操作，意味着它们匹配整个字符串的开头和结尾。但在某些情况下，你可能希望匹配多行字符串中各行的开头和结尾，这就是多行模式的作用，可以通过 <code>m</code> 标志来指示。</p>
<p>由于单行模式是默认的，因此它只匹配字符串开头的第一个数字 "1"。</p>
<pre><code class="language-javascript">let str = `1st line
2nd line
3rd line`;

let re = /^\d/g; // "^\d" 匹配字符串开头的数字
let matches = str.match(re);

console.log(matches); // 输出为：["1"]
</code></pre>
<ul>
<li><strong>多行模式（m）</strong>：<code>/^\d/gm</code> 是启用了 <code>m</code> 标志的正则表达式模式。通过利用 <code>m</code> 标志，你可以确保 <code>^</code> 和 <code>$</code> 匹配多行字符串中各行的开头和结尾，而不仅仅是整个字符串本身。</li>
</ul>
<p>因此，它从第一行匹配到 "1"，从第二行匹配到 "2"，从第三行匹配到 "3"：</p>
<pre><code class="language-javascript">let str = `1st line
2nd line
3rd line`;

let re = /^\d/gm;
let matches = str.match(re);

console.log(matches); // 输出为：["1", "2", "3"]
</code></pre>
<p>这在处理包含多行或换行符的文本时特别有用。</p>
<h3 id="word-boundaries-b-">单词边界（`\b`）</h3>
<p><code>\b</code> 是正则表达式中的一个特殊字符，称为锚点，就像 <code>^</code> 和 <code>$</code> 一样。它用于匹配字符串中的位置，其中一个单词字符（如字母、数字或下划线）之前或之后不是另一个单词字符。例如：</p>
<ul>
<li><code>\bword\b</code> 匹配字符串中的单词 "word"，但不匹配子串如 "wording" 或 "swordfish"。</li>
</ul>
<pre><code class="language-javascript">let pattern = /\bword\b/;
let pattern2 = /word/;
console.log(pattern.test("This is a word.")); // 输出为：true
console.log(pattern.test("This is wording.")); // 输出为：false (没有匹配"wording")
console.log(pattern2.test("This is wording")); // 输出为：True
</code></pre>
<p><code>/word/</code> 在字符串中的任何位置匹配子串 "word"。它在 "This is wording." 中匹配 "word"，因为它不包括任何单词边界断言。</p>
<p>其他示例包括：</p>
<ul>
<li><code>\b\d+\b</code> 匹配字符串中的整数，但不包括数字字符相邻的非数字字符。</li>
<li><code>^\bword\b$</code> 匹配仅由单词 “word” 组成的字符串。</li>
</ul>
<h2 id="quantifiers-in-regex">正则表达式中的量词</h2>
<p>在正则表达式中，量词允许你指定你想要在字符串中匹配的字符或字符类的数量。它们是定义你要查找的字符或组的实例数量的符号或字符。</p>
<h3 id="n">精确数量量词 <code>{n}</code></h3>
<p>最简单的量词是 <code>{n}</code>，它指定了你想匹配的字符或字符类的精确的数量。（译者注：该量词的一般使用形式为<code>x{n}</code>，其中x为任意字符或字符类，n为正整数，该量词的含义为“与‘只重复出现n次的x’对应的部分匹配”。）假设我们有一个字符串 "Year: 2022"，我们想从中提取年份：</p>
<pre><code class="language-javascript">let str = 'Year: 2022';
let re = /\d{4}/; // 匹配一个四位数字；基本上是等同于\d\d\d\d的更简洁、更好的写法。

let result = str.match(re);

console.log(result); // 输出为：["2022"]
</code></pre>
<p>（译者注：在上述例子中，该量词对应的模式只会与四位数字匹配。对于小于四位数字的字符串，例如203，该模式不会匹配；对于大于四位数字的字符串，例如20356，该模式会匹配最前面四位数字“2035”。<a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Regular_Expressions/Quantifiers#%E7%B1%BB%E5%9E%8B">参考文档链接</a>）</p>
<h3 id="nm">区间量词 <code>{n,m}</code></h3>
<p>区间量词 <code>{n,m}</code> 匹配一个字符或字符类从 n 到 m 次，包括 n 和 m。例如：</p>
<pre><code class="language-javascript">let str = "The meeting is scheduled for 10:30 AM and ends at 2 PM";
let re = /\d{2,4}/g; // 匹配有2到4位数字的数

let result = str.match(re);
console.log(result); // 输出为：[ '10', '30' ]
</code></pre>
<p>/\d{2,4}/g 匹配连续有2到4位数字的数，即 '10'、'30'。<br>
（译者注：区间量词中，n的取值为0或一个正整数，m&gt;n且m为一个正整数。与精确数量量词<code>{n}</code>相似，例如对于形式为<code>a{2,4}</code>的量词，它不会匹配"candy"中的'a'，而对于"caaaaady"，它只会匹配其中的前四个'a'）</p>
<h3 id="n"><code>{n,}</code> 和简写形式</h3>
<p><code>{n,}</code> 量词匹配一个字符或字符类至少 n 次。此外，还有常见量词的简写表示法。例如：</p>
<pre><code class="language-javascript">let str = 'The price of the item is $2500';
let re = /\d{2,}/g; // 匹配至少有2位数字的数。

let result = str.match(re);
console.log(result); // 输出为：["2500"]
</code></pre>
<h3 id="">简写形式：<code>+</code>, <code>?</code>, <code>*</code></h3>
<p>量词 <code>+</code>、<code>?</code> 和 <code>*</code> 是常见用例的简写表示法。让我们使用简写 <code>+</code> 来匹配电话号码中的一个或多个数字：</p>
<pre><code class="language-javascript">let phone = "+1-(103)-777-0101";
let result = phone.match(/\d+/g); // 匹配一个或多个数字。

console.log(result); // 输出为：["1", "103", "777", "0101"]
</code></pre>
<p>/\d+/g 匹配电话号码中一个或多个连续数字。</p>
<h3 id="">量词：零或一次（<code>?</code>）</h3>
<p>正则表达式中的量词 <code>?</code> 表示前一个字符或组的零次或一次出现。它等同于 {0,1}。例如：</p>
<pre><code class="language-javascript">let str = 'The sky is blue in color, but the ocean is blue in colour';
let result = str.match(/colou?r/g); // 匹配"color"和"colour"

console.log(result); // 输出为：["color", "colour"]
</code></pre>
<p>在这个例子中，正则表达式 <code>/colou?r/g</code> 匹配给定字符串中的 "color" 和 "colour"，允许字母 "u" 出现零次或一次。</p>
<h3 id="">量词：零次或更多（<code>*</code>）</h3>
<p>在正则表达式中，量词 <code>*</code> 表示前一个字符或组的零次或更多次出现。它等同于 {0,}。例如：</p>
<pre><code class="language-javascript">let str = 'Computer science is fascinating, but computational engineering is equally interesting';
let re = /comput\w*/g; // 匹配"computer"和"computational"

let results = str.match(re);

console.log(results); // 输出为：["computer", "computational"]
</code></pre>
<h3 id="greedy-quantifiers-">贪婪量词</h3>
<p>在正则表达式中，量词决定了特定元素在匹配中可以出现的次数。</p>
<p>默认情况下，量词以所谓的“贪婪”模式运行。这意味着它们会尝试匹配尽可能多的前一个元素。例如：</p>
<pre><code class="language-javascript">let regexp = /".+"/g;
let str = 'The "Boy" and his "Friends" were here';
console.log( str.match(regexp) ); // "Boy" and his "Friends"
</code></pre>
<p>它找到一个包含两者（"Boy" and his "Friends"）的匹配，而不是找到两个单独的匹配（"Boy" 和 "Friends"）。</p>
<h4 id="">理解贪婪搜索</h4>
<p>为了理解为什么初始尝试失败，让我们深入了解正则表达式引擎是如何进行搜索的。</p>
<ol>
<li>引擎从字符串的开始处开始，并找到开头的引号。</li>
<li>它继续匹配跟在开头引号后面的字符。由于模式是 <code>".+"</code>，其中 <code>.</code> 匹配任何字符，<code>+</code> 使其匹配一次或多次，引擎会继续匹配字符，直到达到字符串的末尾。</li>
<li>然后，引擎回溯以找到结束引号 <code>"</code> 以完成匹配。它首先假设由 <code>".+"</code> 匹配的最大可能字符数量，并逐渐减少字符数量，直到找到有效的匹配。</li>
<li>最终，引擎找到了一个包含整个子字符串 "Boy" and his "Friends" 的匹配。</li>
</ol>
<p>这种贪婪地匹配尽可能多的字符的行为是正则表达式中量词的默认模式，不总是产生期望的结果。你可以在这个例子中看到这一点，它导致单一匹配，而不是对带引号的字符串进行多个独立的匹配。</p>
<h3 id="non-greedy-quantifiers">非贪婪量词（懒惰模式）</h3>
<p>为了解决贪婪模式的限制，正则表达式也支持量词的懒惰模式。在懒惰模式中，量词后的字符重复的次数是满足模式所必需的最小次数。</p>
<p>我们可以通过在量词后添加一个问号 ? 来启用懒惰模式。例如，*? 或 +? 表示懒惰重复。</p>
<pre><code class="language-javascript">let regexp = /".+?"/g;
let str = 'The "Boy" and his "Friends" were here';
console.log( str.match(regexp) ); // "Boy" "Friends"
</code></pre>
<p>在这个例子中，懒惰量词 <code>".+?"</code> 确保每个带引号的字符串都被单独匹配，通过最小化开头和结束引号之间匹配的字符数量。</p>
<p>让我们逐步跟踪搜索过程，以理解懒惰量词是如何工作的：</p>
<ul>
<li>引擎从字符串的开始处开始，并找到开头的引号。</li>
<li>与其贪婪地匹配直到字符串的末尾的所有字符，懒惰量词 <code>".+?"</code> 只匹配满足模式所需的字符。它一遇到结束引号 <code>"</code> 就停止。</li>
<li>引擎为文本中的每个带引号的字符串重复此过程，导致 "Boy" 和 "Friends" 分别被单独匹配。</li>
</ul>
<h2 id="sets-and-ranges-in-regex">正则表达式的集合与区间</h2>
<p>在正则表达式中，你可以使用集合和区间来匹配特定的字符或在给定模式内的一系列字符。</p>
<h3 id="sets-">集合</h3>
<p>一个集合使用方括号 <code>[...]</code> 来定义。它允许你匹配集合中的任何字符。例如，<code>[aeiou]</code> 匹配元音字母 'a', 'e', 'i', 'o', 或 'u' 中的任何一个。</p>
<p><strong>示例：</strong> 假设我们有一个字符串 <code>'The quick brown fox jumps over the lazy dog.'</code>。为了匹配这个字符串中的所有元音字母，我们可以使用正则表达式 <code>/[aeiou]/g</code>。</p>
<pre><code class="language-javascript">let str = 'The quick brown fox jumps over the lazy dog.';
let re = /[aeiou]/g;
let results = str.match(re);

console.log(results); // 输出为：['e', 'u', 'i', 'o', 'o', 'u', 'o', 'e', 'e', 'a', 'o']
</code></pre>
<p>这个正则表达式匹配字符串中所有元音字母的实例。</p>
<pre><code class="language-javascript">let str = 'The cat chased the rats in the backyard';;
let re = /[cr]at/g;
let results = str.match(re);

console.log(results); // 输出为：['cats', 'rats']
</code></pre>
<p>在这里，正则表达式 [cr]at 匹配以 'c' 或 'r' 开头，并跟着 'at' 的单词。</p>
<h3 id="ranges-">区间</h3>
<p>区间允许你在集合内指定一系列字符。例如，<code>[a-z]</code> 匹配从 'a' 到 'z' 的任何小写字母，而 <code>[0-9]</code> 匹配从 '0' 到 '9' 的任何数字。示例：</p>
<pre><code class="language-javascript">let str = 'Hello World!';
let re = /[a-z]/g;
let results = str.match(re);

console.log(results); // 输出为：['e', 'l', 'l', 'o', 'o', 'r', 'l', 'd']
</code></pre>
<p>在这里，正则表达式 <code>[a-z]</code> 匹配字符串中的所有小写字母。</p>
<h3 id="">否定/排除区间</h3>
<p>要从集合中排除某些字符，你可以在方括号内使用 <code>^</code> 符号。例如：</p>
<pre><code class="language-javascript">let str = 'The price is $19.99';
let re = /[^0-9]/g;
let results = str.match(re);

console.log(results); // 输出为：['T', 'h', 'e', ' ', 'p', 'r', 'i', 'c', 'e', ' ', 'i', 's', ' ', '$', '.'] 
</code></pre>
<p>在这里，<code>[^0-9]</code> 匹配字符串中不是数字的任何字符。同样地，<code>[^a-z]</code> 将匹配任何不是小写字母的字符：</p>
<pre><code class="language-javascript">let str = 'The price is $19.99';
let results2 = str.match(/[^a-z]/g);

console.log(results2); // 输出为：['T', ' ', ' ', ' ', '$', '1', '9', '.', '9', '9']
</code></pre>
<h3 id="predefined-character-classes-">预定义的字符类</h3>
<p>某些字符类具有预定义的简写符号，用于常见字符区间的匹配。</p>
<p><strong><code>\d</code>类</strong>：<code>\d</code>匹配任何数字字符，等价于区间 <code>[0-9]</code>。例如：</p>
<pre><code class="language-javascript">let phone = '+1-(103)-777-0101';
let re = /\d/g;
let numbers = phone.match(re);
let phoneNo = numbers.join('');
console.log(phoneNo); // 输出为：11037770101
</code></pre>
<p>我们使用 <code>match()</code> 和 <code>join()</code> 方法来格式化电话号码。这种方法简化了数据的处理和清理过程，使其适用于各种文本处理应用程序。</p>
<p>类似地，<code>\s</code> 匹配单个空白字符，包括空格、制表符和换行符，而 <code>\w</code> 匹配任何单词字符（字母数字字符或下划线），等价于区间 <code>[a-zA-Z0-9_]</code>。</p>
<p>结合这些类可以实现更灵活、更精确的模式匹配，从而实现各种文本处理任务。示例：</p>
<pre><code class="language-javascript">let str = 'O2 is oxygen';
let re = /\w\d/g;
console.log(str.match(re)); // 输出为：["O2"]
</code></pre>
<p>这些预定义的字符类为常用的字符区间提供了便捷途径。</p>
<p><strong>反向类</strong>，用大写字母表示（例如，<code>\D</code>），匹配任何不包含在相应小写类中的字符。这提供了一种方便的方式来匹配特定集合之外的字符，例如非数字字符、非空白字符或非单词字符。示例：</p>
<pre><code class="language-javascript">let phone = '+1-(103)-777-0101';
let re = /\D/g;
console.log(phone.replace(re,'')); // 输出为：11037770101
</code></pre>
<h2 id="special-characters-and-escaping-in-regex">正则表达式中的特殊字符与转义</h2>
<h3 id="">元字符</h3>
<p>元字符是在正则表达式中具有特殊含义的字符，用于构建用于匹配文本的模式。</p>
<p>锚点 (<code>^</code> 和 <code>$</code>)、交替 (<code>|</code>)、量词 (<code>+</code>, <code>?</code>, <code>{}</code>) 和预定义的字符类 (<code>\d</code>, <code>\w</code>, <code>\s</code>) 都被认为是元字符，每个都在模式定义中有不同的用途。我们还有一些其他的元字符，现在我们来详细介绍它们。</p>
<p><strong>点号 (<code>.</code>)</strong> 是一个具有特殊含义的元字符。它用于匹配除换行符 (<code>\n</code>) 外的任何单个字符。它起到通配符的作用，允许在确切字符未知或无关紧要时进行灵活的模式匹配。</p>
<p>如果你需要点号匹配换行符，你可以在 JavaScript 中使用 <code>/s</code> 标志，该标志启用了 "单行" 模式，使点号匹配任何字符，包括换行符。例如：</p>
<pre><code class="language-javascript">const regex = /a.b/; 

console.log(regex.test('acb')); // true
console.log(regex.test('aXb')); // true
console.log(regex.test('a\nb')); // false（未匹配到换行符）
console.log(regex.test('a\nb', 's')); // true（使用 's' 标志，匹配到换行符）
console.log(regex.test('ab')); // false（'a' 和 'b' 之间缺少字符）
</code></pre>
<p><code>/a.b/</code> 匹配以 'a' 开始，后跟任何单个字符（除换行符外），并以 'b' 结束的任何字符串。</p>
<p>点号 (<code>.</code>) 可以与其他正则表达式元素结合，形成更复杂的模式。例如，<code>/.at/</code> 匹配任何以 'at' 结尾的三个字符序列，如 'cat'、'bat' 或 'hat'。</p>
<h3 id="escape-special-characters-">转义特殊字符</h3>
<p>在正则表达式模式中，当你想要搜索或匹配这些字符，而不触发它们的特殊正则表达式含义，转义特殊字符是至关重要的。</p>
<p>要在正则表达式模式中字面匹配一个特殊字符，你需要通过在其前面加上反斜杠（\）来转义它。这告诉正则表达式引擎将特殊字符视为普通字符。例如：</p>
<pre><code class="language-javascript">let str = 'This ^ symbol is called Caret ';
let re = /[\^]/g;
let results = str.match(re);

console.log(results); // 输出为：['^']
</code></pre>
<p>如果没有<code>\</code>，<code>^</code> 将被解释为字面插入符号。</p>
<p>有趣的事实是，我们用来转义元字符的 <code>/</code> 本身也是一个元字符，可以用另一个反斜杠来转义成 <code>//</code>。</p>
<h2 id="groupings-in-regex">正则表达式中的分组</h2>
<h3 id="capturing-groups-">捕获组</h3>
<p>在JavaScript正则表达式中，捕获组用于提取匹配字符串的特定部分。试想你有一个类似于"resource/id"的路径，例如 "posts/123"。为了匹配这个路径，你可以使用正则表达式 <code>/\w+\/\d+/</code>。</p>
<ul>
<li><code>\w+</code> 匹配一个或多个单词字符。</li>
<li><code>\/</code> 匹配斜杠 <code>/</code>。</li>
<li><code>\d+</code> 匹配一个或多个数字。</li>
</ul>
<p>假设你有一个路径像 "posts/123"，你想捕获 <code>id</code> 部分（123）。我们可以使用捕获组来实现这一点。</p>
<p>要创建一个捕获组，你将想要捕获的正则表达式模式的部分放在括号中。例如，<code>(\d+)</code> 捕获一个或多个数字。</p>
<p>这是它的运行步骤：</p>
<pre><code class="language-javascript">const path = 'posts/123';
const pattern = /\w+\/(\d+)/;

const match = path.match(pattern);
console.log(match);
</code></pre>
<p>输出为：</p>
<pre><code class="language-bash">[ 'posts/123', '123', index: 0, input: 'posts/123', groups: undefined ]
</code></pre>
<p>在这里，<code>'123'</code> 被捕获组 <code>(\d+)</code> 捕获。</p>
<p><strong>使用多个捕获组</strong>：你可以在正则表达式模式中使用多个捕获组。例如，为了从路径 "posts/123" 中同时捕获资源（如 "posts"）和 id（如 "123"），你可以使用 <code>/(\w+)\/(\d+)/</code>。</p>
<pre><code class="language-javascript">const path = 'posts/123';
const pattern = /(\w+)\/(\d+)/;

const match = path.match(pattern);
console.log(match);
</code></pre>
<p>输出为：</p>
<pre><code class="language-bash">['posts/123', 'posts', '123', index: 0, input: 'posts/123', groups: undefined]
</code></pre>
<p>在这里，<code>'posts'</code> 和 <code>'123'</code> 分别由两个捕获组 <code>(\w+)</code> 和 <code>(\d+)</code> 捕获。</p>
<p><strong>命名捕获组</strong>允许你为捕获组指定名称，这样在后续的代码中引用它们会更容易。</p>
<p>命名捕获组的语法是 <code>(?&lt;name&gt;rule)</code>，其中：</p>
<ul>
<li><code>()</code> 表示一个捕获组。</li>
<li><code>?&lt;name&gt;</code> 指定捕获组的名称。</li>
<li><code>rule</code> 是模式中的一个规则。</li>
</ul>
<p>例如，假设我们想要使用命名捕获组从路径 "posts/123" 中捕获资源（如 "posts"）和 id（如 "123"）。</p>
<pre><code class="language-javascript">const path = 'posts/123';
const pattern = /(?&lt;resource&gt;\w+)\/(?&lt;id&gt;\d+)/;

const match = path.match(pattern);
console.log(match);
</code></pre>
<p>输出为：</p>
<pre><code class="language-javascript">[
  'posts/123',
  'posts',
  '123',
  index: 0,
  input: 'posts/123',
  groups: [Object: null prototype] { resource: 'posts', id: '10' }
]
</code></pre>
<p>在这里，<code>resource</code> 和 <code>id</code> 是分配给捕获组的名称。我们可以使用 <code>match.groups</code> 来访问它们。</p>
<p><strong>另一个例子</strong>: 假设我们有一个类似于"posts/2022/02/18"的路径，我们想要使用命名捕获组来捕获资源（如 "posts"）、年份（如 "2022"）、月份（如 "02"）和日期（如 "18"）。</p>
<p>该例子对应的正则表达式的模式为：</p>
<pre><code class="language-javascript">const path = 'posts/2024/02/22';
const pattern =
  /(?&lt;resource&gt;\w+)\/(?&lt;year&gt;\d{4})\/(?&lt;month&gt;\d{2})\/(?&lt;day&gt;\d{2})/;

const match = path.match(pattern);
console.log(match.groups);
</code></pre>
<p>输出为：</p>
<pre><code class="language-bash">{resource: 'posts', year: '2024', month: '02', day: '22'}
</code></pre>
<p>在这里，路径的每个部分都使用命名捕获组进行捕获，这样就可以轻松地通过它们各自的名称访问它们。</p>
<h3 id="">非捕获组</h3>
<p>在正则表达式中，非捕获组用于将模式的部分组合在一起，以便应用量词或交替，而不捕获匹配的子字符串。</p>
<p>要创建一个非捕获组，你需要在括号的开始处添加 <code>?:</code>。因此，<code>/(?:\d)+/</code> 是前一个示例的非捕获版本。<code>?:</code> 告诉正则表达式引擎不要捕获匹配的子字符串。</p>
<p>让我们通过一个例子来看捕获组和非捕获组之间的区别：</p>
<pre><code class="language-javascript">// capturing group
const regexWithCapture = /(\d{2})\/(\d{2})\/(\d{4})/;
const matchWithCapture = regexWithCapture.exec('02/26/2024');

console.log(matchWithCapture); // ["02/26/2024", "02", "26", "2024"]
</code></pre>
<pre><code class="language-javascript">// non-capturing group
const regexWithoutCapture = /(?:\d{2})\/(?:\d{2})\/(?:\d{4})/;
const matchWithoutCapture = regexWithoutCapture.exec('02/26/2024');

console.log(matchWithoutCapture); // ["02/26/2024"]
</code></pre>
<p>总结来说，非捕获组 <code>(?:pattern)</code> 在匹配模式方面的行为与常规捕获组 <code>()</code> 相同，但它们不会将匹配的文本存储在内存中以供以后检索。这使得它们在你不需要提取匹配文本的特定部分时非常有用。</p>
<h3 id="">后向引用</h3>
<p>后向引用允许你在正则表达式中引用先前捕获的组，将它们视为存储匹配模式的变量。</p>
<p>在JavaScript中，后向引用的语法是 <code>\N</code>，其中 <code>N</code> 是表示捕获组编号的整数。</p>
<p>例如，考虑一个包含重复单词 "Lion" 的字符串，我们希望删除重复的单词以得到 <code>'Lion is the King'</code>：</p>
<pre><code class="language-javascript">const s = 'Lion Lion is the King';
</code></pre>
<ul>
<li>首先，我们使用 <code>\w+\s+</code> 匹配一个单词。</li>
<li>然后，我们创建一个捕获组来捕获这个单词，使用 <code>(\w+)\s+</code>。</li>
<li>接下来，我们使用反向引用 (<code>\1</code>) 来引用第一个捕获组。</li>
<li>最后，我们使用 <code>String.replace()</code> 将整个匹配替换为第一个捕获组。</li>
</ul>
<pre><code class="language-javascript">const pattern = /(\w+)\s+\1/;
const result = s.replace(pattern, '$1');
console.log(result); // 输出为：'Lion is the King'
</code></pre>
<h3 id="regex-alternation">正则表达式选择符号</h3>
<p>正则表达式的选择符号是一种允许你在单个正则表达式中匹配不同的模式的功能。它的工作方式类似于逻辑运算符<code>OR</code>。正则表达式使用竖线符号 <code>|</code> 表示选择符号，你可以使用它来匹配 A 或 B。</p>
<pre><code>A | B // 这意味着你可以匹配模式A或模式B
</code></pre>
<p>现在，让我们探讨一些正则表达式选择符号的实际应用：</p>
<p><strong>匹配格式为hh:mm的时间字符串</strong>：假设我们想要匹配格式为 hh:mm 的时间字符串，其中 hh 表示小时，mm 表示分钟。一个基本的正则表达式来匹配这种格式就是 <code>/\d{2}:\d{2}/</code>。</p>
<p>然而，这个基本模式匹配了无效的时间，比如 "99:99"。为了确保我们匹配有效的时间（小时区间从00到23，分钟区间从00到59），我们需要使用选择符号来完善我们的正则表达式。</p>
<p>为了匹配有效的小时（00到23），我们可以使用以下模式：</p>
<ul>
<li><code>[01]\d</code> 匹配00到19的数字。</li>
<li><code>2[0-3]</code> 匹配20到23的数字。</li>
</ul>
<p>因此，小时的模式变为 <code>[01]\d|2[0-3]</code>。</p>
<p>我们可以使用模式 <code>[0-5]\d</code> 来匹配有效的分钟数(00 to 59)。</p>
<p>现在，我们可以使用选择符号将小时和分钟模式结合起来，得到最终的正则表达式模式：</p>
<p><code>/([01]\d|2[0-3]):[0-5]\d/g</code></p>
<p>在这个模式中:</p>
<ul>
<li><code>([01]\d|2[0-3])</code> 匹配有效的小时数。</li>
<li><code>:</code> 匹配冒号。</li>
<li><code>[0-5]\d</code> 匹配有效的分钟数。</li>
</ul>
<p>该正则表达式模式确保我们只匹配 <code>hh:mm</code> 格式的有效时间字符串。例如：</p>
<pre><code class="language-javascript">const timeString = '07:23 33:71 21:17 25:81';
const pattern = /([01]\d|2[0-3]):[0-5]\d/g;
const matches = timeString.match(pattern);

console.log(matches);
</code></pre>
<p><strong>期望输出</strong>:</p>
<pre><code>['07:23', '21:17']
</code></pre>
<h2 id="lookahead-and-lookbehind-in-regex">正则表达式中的前瞻断言和后顾断言</h2>
<h3 id="lookahead-">前瞻断言</h3>
<p>正则表达式中的前瞻允许仅当某个模式（X）后面紧跟着另一个特定模式（Y）时进行匹配。语法是 <code>X(?=Y)</code>，其中：</p>
<ul>
<li><strong>X</strong> 是你要匹配的模式。</li>
<li><strong>(?=Y)</strong> 是前瞻断言，指示 <code>X</code> 应该紧跟着 <code>Y</code>。</li>
</ul>
<p><strong>例如：</strong> 假设我们有一个描述各种距离的字符串，我们想要识别字符串中包含的单位为 "miles" 而不是 "kilometers" 的数字。我们可以在正则表达式模式中使用前瞻断言：</p>
<pre><code class="language-javascript">const dist = "He ran 5 miles, but not 10 kilometers.";

const regex = /\d+(?=\s*miles)/g;

console.log(dist.match(regex)); // 输出为：["5"]
</code></pre>
<p><strong>多重前瞻断言</strong>: 在正则表达式中可以使用语法 <code>X(?=Y)(?=Z)</code> 来使用多个前瞻断言，这能够让我们对匹配施加多个条件。</p>
<p><strong>例如:</strong> 假设我们想要匹配同时包含 "foo" 和 "bar" 的字符串，但它们可以以任意的顺序排列：</p>
<pre><code class="language-javascript">const regex = /(?=.*foo)(?=.*bar)/;

console.log(regex.test("foobar")); // true
console.log(regex.test("barfoo")); // true
console.log(regex.test("foo"));    // false
console.log(regex.test("bar"));    // false
</code></pre>
<h3 id="negative-lookaheads-">否定前瞻断言</h3>
<p>为了否定一个前瞻断言，可以使用否定前瞻断言，其语法为 <code>(?!Y)</code>。在这种情况下，正则表达式引擎只有在 X 后面不跟着 Y 的情况下才会匹配 X。</p>
<p><strong>例如</strong>： 假设我们想要匹配数字，但不希望它们后面跟着 "miles"：</p>
<pre><code class="language-javascript">const text = "He ran 5 miles, but not 10 kilometers.";

const regex = /\d+(?!\s*miles)/g;

console.log(text.match(regex)); // 输出为：["10"]
</code></pre>
<p><code>(?!\s*miles)</code> 是一个否定前瞻断言，它确保数字后面不是零个或多个空格加上单词 "miles"。</p>
<h3 id="lookbehind-">后顾断言</h3>
<p>后顾断言提供了一种根据其前面的内容来匹配模式的方式，如果某个特定元素在其前面，则匹配该元素。</p>
<p><strong>例如</strong>：假设我们有一个包含价格的字符串，并且我们想要匹配在货币符号 "$" 前面的数字，但不匹配在 "€" 前面的数字。我们可以在正则表达式模式中使用后顾断言。</p>
<pre><code class="language-javascript">const priceString = "The price is $100, but €200.";

const regex = /(?&lt;=\$)\d+/g;

console.log(priceString.match(regex)); // 输出为：["100"]
</code></pre>
<p><strong>说明</strong>：如果在当前位置之前有一个文字字符串 "$"，<code>(?&lt;=\$)</code> 就会匹配该元素。反斜杠 <code>\</code> 用于转义特殊字符 "$"，将其视为字面字符。</p>
<h3 id="negative-lookbehind-">否定后顾断言</h3>
<p>否定后顾断言允许你仅在模式之前不是特定模式的情况下匹配该模式。这对于根据前面的内容排除某些模式的匹配非常有用。</p>
<p>示例：假设我们有一个包含不同货币的各种价格的字符串，并且我们想要匹配不是以货币符号 "$" 开头的数字。我们可以在正则表达式模式中使用否定后顾断言：</p>
<pre><code class="language-javascript">const priceString = "The price is $50, but not €100.";

const regex = /(?&lt;!\$)\b\d+\b/g;

console.log(priceString.match(regex)); // 输出为： ["100"]
</code></pre>
<p><strong>说明</strong>： <code>(?&lt;!\$)</code> 是否定后顾断言语法，它只在当前位置之前不是字面字符串"$"时匹配后面的模式。</p>
<h2 id="practical-examples-and-use-cases-of-regexpractical-examples-and-use-cases-of-regex">正则表达式的实际应用示例</h2>
<p>现在，让我们探索一些在JavaScript应用程序中使用正则表达式来解决常见问题和执行文本操作任务的实际示例。</p>
<h3 id="password-strength-checking-">密码强度检验函数</h3>
<p>你可以使用正则表达式来强制执行密码强度要求，例如最小长度和特殊字符的存在。</p>
<pre><code class="language-javascript">function checkPasswordStrength(password) {
    let pattern = /^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[!@#$%^&amp;*]).{8,}$/;
    return pattern.test(password);
}

console.log(checkPasswordStrength("Passw0rd!"));    // 输出为：true
console.log(checkPasswordStrength("weakpassword")); // 输出为：false
</code></pre>
<p>这里的正则表达式确保密码包含至少1个数字、1个小写字母、1个大写字母、1个特殊字符，并且密码长度至少为8个字符。</p>
<p>这个模式进行了如下操作:</p>
<ul>
<li><code>(?=.*\d)</code>: 要求至少一个数字。</li>
<li><code>(?=.*[a-z])</code>: 要求至少一个小写字母。</li>
<li><code>(?=.*[A-Z])</code>: 要求至少一个大写字母。</li>
<li><code>(?=.*[!@#$%^&amp;*])</code>: 要求至少一个特殊符号。</li>
<li><code>.{8,}</code>: 要求密码长度至少为8个字符。</li>
</ul>
<h3 id="email-validation-function-">电子邮箱地址校验函数</h3>
<p>电子邮件验证对于确保网络应用程序中的数据完整性和安全性至关重要。通过使用正则表达式，我们可以轻松实现强大的电子邮件验证机制。</p>
<pre><code class="language-javascript">function validateEmail(email) {
    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    return emailRegex.test(email);
}

console.log(validateEmail("example@email.com")); // true
console.log(validateEmail("invalid-email"));      // false
</code></pre>
<p>这个模式进行了如下操作：</p>
<ul>
<li><code>^</code>: 断言字符串的起始位置。</li>
<li><code>[^\s@]+</code>: 匹配一个或多个非空白字符或'@'字符。</li>
<li><code>@</code>: 匹配'@'符号。</li>
<li><code>[^\s@]+</code>: 匹配一个或多个非空白字符或'@'字符。</li>
<li><code>\.</code>: 匹配'.'符号 (因为'.'在正则表达式中具有特殊意义，所以需要转义。)。</li>
<li><code>[^\s@]+</code>: 匹配一个或多个非空白字符或'@'字符。</li>
<li><code>$</code>: 断言字符串的结束位置。</li>
</ul>
<h3 id="phone-number-formatting-function-">电话号码格式化函数</h3>
<p>在涉及电话号码输入和显示的应用程序中，电话号码格式化增强了用户体验和可读性。</p>
<p>通过定义一个匹配电话号码组件的regex模式，我们可以使用 <code>replace()</code> 方法轻松地将电话号码格式化为所需的模式。</p>
<pre><code class="language-javascript">function formatPhoneNumber(phoneNumber) {
    const phoneRegex = /(\d{3})(\d{3})(\d{4})/;
    return phoneNumber.replace(phoneRegex, "($1) $2-$3");
}

const formattedNumber = formatPhoneNumber("9876543210");
console.log(formattedNumber); // (987) 654-3210
</code></pre>
<p>这个函数接受一个电话号码字符串作为输入，并以标准的 <code>(XXX) XXX-XXXX</code> 格式返回。</p>
<p>在 <code>replace()</code> 方法中, <code>$1</code>, <code>$2</code>, 和 <code>$3</code> 表示以RegEx模式捕获的组，对应于电话号码中的三组数字。</p>
<h2 id="tips-and-best-practices-for-using-regular-expressions">使用正则表达式的技巧和最佳实践方式</h2>
<h4 id="1">1. 理解正则表达式语法</h4>
<p>了解正则表达式的语法和元字符，以便有效使用正则表达式。</p>
<h4 id="2">2. 测试正则表达式</h4>
<p>由于复杂的模式或特殊字符，正则表达式有时会表现出意外的行为。经常使用不同的输入字符串测试你的正则表达式，以确保它们在不同的场景中表现得像预期的那样。</p>
<h4 id="3">3. 优化性能</h4>
<p>考虑通过简化模式或尽可能使用更有效的替代方案来优化正则表达式的性能。</p>
<h4 id="4">4. 使用内置方法</h4>
<p>JavaScript提供了例如 <code>String.prototype.match()</code>, <code>String.prototype.replace()</code>, 和 <code>String.prototype.split()</code>等用于常见的字符串操作任务的内置方法。评估这些方法是否可以在不需要正则表达式的情况下完成任务。</p>
<h4 id="5">5. 为你的正则表达式添加注释</h4>
<p>使用 <code>(?#comment)</code> 语法为你的正则表达式添加注释来解释部分复杂的模式。例如:</p>
<pre><code class="language-javascript">const regex = /(\d{3})-(\d{3})-(\d{4})\s(?# Match a phone number in the format XXX-XXX-XXXX)/;
</code></pre>
<h4 id="6">6. 分解复杂模式</h4>
<p>如果你的正则表达式太过复杂而难以理解或维护，请考虑将其分解为更小、更易于管理的部分。使用变量来存储正则表达式模式的各个组件，并根据需要组合它们。</p>
<h4 id="7">7. 利用在线资源并坚持练习</h4>
<p>有许多在线资源和工具可以用来测试和学习正则表达式。<a href="https://regex101.com/">Regex101</a> 和 <a href="https://regexr.com/">RegExr</a> 等网站提供了测试和调试正则表达式的交互式平台。还可以利用在线教程和文档来学习 regex 概念。</p>
<p>MDN Web Docs 提供了关于正则表达式的有用指南<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_expressions">39</a>。以下是 JavaScript 中正则表达式的快速入门指南：<a href="https://www.freecodecamp.org/news/a-quick-and-simple-guide-to-javascript-regular-expressions-48b46a68df29/">RegExp 教程</a>。</p>
<h2 id="conclusion">总结</h2>
<p>正则表达式是 JavaScript 中用于模式匹配和操作的通用工具。</p>
<p>通过理解正则表达式的方法、高级特性和对标志的使用，利用在线资源和调试工具，你可以有效地学习它们，并将它们应用于各种场景，从简单的模式匹配到复杂的文本处理任务。</p>
<!--kg-card-end: markdown--> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 正则表达式中的美元符号 $ 是什么意思 ]]>
                </title>
                <description>
                    <![CDATA[ $ 符号是正则表达式中最常用的符号之一。它用于匹配一个字符串的结尾。换句话说，你可以称它为“行尾锚”，因为它将一个模式锚定在行的末端。 在这篇文章中，我将向你展示美元符号（$）在正则表达式中的确切作用以及如何使用它。 我们将讨论的内容  * 正则表达式中的 $ 符号是什么  * 如何匹配正则表达式中的美元符号 $  * 如何在 JavaScript 正则表达式中使用美元符号 $  * 总结 正则表达式中的 $ 符号是什么 $ 是 RegEx 中被称为元字符的字符之一。它与一个字符串的结尾相匹配，只有当它所连接的文本或字符串处于一行的结尾时，才会返回匹配。 这在你想确保一个字符串以某种模式或字符结束的情况下很有用。你可以将 $ 元字符与其他元字符一起使用，创建复杂的模式，以匹配特定的字符串或字符串中的模式。 在下面的例子中，你可以看到模式是 freecodecamp$ 与 i、g 和 m 标志。i 表示不区分大小写，g 表示全局，m 表示多行。 你还可以看到，只有行尾的 freeCoceCamp 这个词被返回匹配。这就是 $ 元字符的力量。 如何匹配正则表达式中的美元符号 ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/what-does-mean-in-regex/</link>
                <guid isPermaLink="false">6464506a0f634b07166530cc</guid>
                
                    <category>
                        <![CDATA[ 正则表达式 ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Miya Liu ]]>
                </dc:creator>
                <pubDate>Tue, 16 May 2023 03:56:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2023/05/start-graph--10-.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>原文：</strong> <a href="https://www.freecodecamp.org/news/what-does-mean-in-regex/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">What Does $ Mean in RegEx? Dollar Metacharacter in Regular Expressions</a>
      </p><p><code>$</code> 符号是正则表达式中最常用的符号之一。它用于匹配一个字符串的结尾。换句话说，你可以称它为“行尾锚”，因为它将一个模式锚定在行的末端。</p><p>在这篇文章中，我将向你展示美元符号（<code>$</code>）在正则表达式中的确切作用以及如何使用它。</p><p>我们将讨论的内容</p><ul><li>正则表达式中的 <code>$</code> 符号是什么</li><li>如何匹配正则表达式中的美元符号 <code>$</code></li><li>如何在 JavaScript 正则表达式中使用美元符号 <code>$</code></li><li>总结</li></ul><h2 id="-">正则表达式中的 <code>$</code> 符号是什么</h2><p><code>$</code> 是 RegEx 中被称为元字符的字符之一。它与一个字符串的结尾相匹配，只有当它所连接的文本或字符串处于一行的结尾时，才会返回匹配。</p><p>这在你想确保一个字符串以某种模式或字符结束的情况下很有用。你可以将 <code>$</code> 元字符与其他元字符一起使用，创建复杂的模式，以匹配特定的字符串或字符串中的模式。</p><p>在下面的例子中，你可以看到模式是 <code>freecodecamp$</code> 与 <code>i</code>、<code>g</code> 和 <code>m</code> 标志。<code>i</code> 表示不区分大小写，<code>g</code> 表示全局，<code>m</code> 表示多行。</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2023/04/Screenshot-2023-04-19-at-10.46.29.png" class="kg-image" alt="Screenshot-2023-04-19-at-10.46.29" width="600" height="400" loading="lazy"></figure><p>你还可以看到，只有行尾的 <code>freeCoceCamp</code> 这个词被返回匹配。这就是 <code>$</code> 元字符的力量。</p><h2 id="--1">如何匹配正则表达式中的美元符号 <code>$</code></h2><p>美元符号 <code>$</code> 是一个元字符，你如何在一个字符串中匹配它？你必须用反斜杠来转义它！这就是你在 RegEx 中匹配所有元字符的方法。</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2023/04/Screenshot-2023-04-19-at-10.46.37.png" class="kg-image" alt="Screenshot-2023-04-19-at-10.46.37" width="600" height="400" loading="lazy"></figure><h2 id="-javascript-">如何在 JavaScript 正则表达式中使用美元符号 <code>$</code></h2><p>美元符号 <code>$</code> 元字符在 JavaScript 中很有用。在下面的代码片段中，我将美元符号作为元字符和字符串进行测试：</p><pre><code class="language-js">const text1 =
  "At freeCodeCamp, we don't ask you to pay to learn coding, because freeCodeCamp is a charity";

const text2 =
  'You can also hang out with friends on a forum developed by freeCodeCamp';

const text3 = 'The sign of the naira is ₦ and dollar sign is $.';

const regex1 = /freecodecamp$/gim;
const regex2 = /\$/g;

// 测试美元符号作为元字符
console.log(regex1.test(text1)); //false
console.log(regex2.test(text2)); //true

// 测试美元符号作为字符串
console.log(regex2.test(text3)); //true
</code></pre><p>你可以看到，当我用正则表达式 <code>/freecodecamp$/gim;</code> 测试字符串 <code>At freeCodeCamp, we don't ask you to pay to learn coding, because freeCodeCamp is a charity</code> 时，它返回 <code>false</code>，因为在行尾没有 <code>freeCodeCamp</code>。但是当我用同样的正则测试 <code>You can also hang out with friends on a forum developed by freeCodeCamp'</code> 时，它返回了 <code>true</code>，因为在一行的末尾有一个 <code>freeCodeCamp</code>。</p><p>另外，你可以看到，当我用正则 <code>/\$/g</code> 测试它时，字符串 <code>'The sign of the naira is ₦ and dollar sign is $.'</code> 返回 <code>true</code>。</p><p>你也可以使用 <code>exec()</code> 方法代替 <code>test()</code> 来查看你的正则的精确匹配：</p><pre><code class="language-js">const text1 =
  "At freeCodeCamp, we don't ask you to pay to learn coding, because freeCodeCamp is a charity";

const text2 =
  'You can also hang out with friends on a forum developed by freeCodeCamp';

const text3 = 'The sign of the naira is ₦ and dollar sign is $.';

const regex1 = /freecodecamp$/gim;
const regex2 = /\$/g;

// 测试美元符号作为元字符
console.log(regex1.exec(text1));
console.log(regex1.exec(text2));

// 测试美元符号作为字符串
console.log(regex2.exec(text3));
</code></pre><p>输出：</p><pre><code class="language-js">null
[
  'freeCodeCamp',
  index: 59,
  input: 'You can also hang out with friends on a forum developed by freeCodeCamp',
  groups: undefined
]
[
  '$',
  index: 46,
  input: 'The sign of the naira is ₦ and dollar sign is $.',
  groups: undefined
]
</code></pre><p>第一个控制台日志返回 <code>null</code>，因为没有匹配。第二条返回了匹配信息，第三条也返回了匹配信息。</p><h2 id="--2">总结</h2><p>你了解了美元（<code>$</code>）元字符是如何在正则表达式和 JavaScript 中匹配行尾的。</p><p>你可以将美元元字符与其他几个元字符结合起来，创建一个复杂的模式。例如，脱字符（<code>^</code>）匹配一行的开始，你可以把它和美元元字符结合起来，只匹配一个特定的单词。</p><p>Happy coding!</p> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 如何用正则表达式匹配一个空字符串 ]]>
                </title>
                <description>
                    <![CDATA[ 正则表达式（RegEx 或 RegExp）是一个定义搜索模式的字符序列。 你可以在各种各样的应用程序（如文本编辑器、开发工具和命令行工具）中使用它们来搜索、替换和验证文本的字符串。 正则表达式在编程语言中也被广泛使用，许多语言都有对它的内置支持。 你可以用正则表达式匹配文本的字符串，这意味着你也可以匹配空字符串。 在这篇文章中，我将向你展示三种在正则表达式中匹配空字符串的方法。 我们将讨论的内容  * 如何在正则表达式中用脱字符和美元符号元字符匹配空字符串  * 如何在正则表达式中用先行断言匹配空字符串  * 如何在正则表达式中用负向先行断言匹配空字符串  * 总结 如何在正则表达式中用脱字符和美元符号元字符匹配空字符串 脱字符（^）和美元符号（$）元字符分别匹配字符串的开始和结束。 所以，如果你在 ^ 和 $ 之间不放任何东西，它就会匹配一个空字符串： ^$ 如何在正则表达式中用先行断言匹配空字符串 在正则表达式中，先行断言是一个零宽度的断言，它允许你只在一个字符串后面有另一个特定的字符串时进行匹配，而不将该特定的字符串包括在匹配结果中。 在正则表达式中，有正向 ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/how-to-include-empty-string-in-regex/</link>
                <guid isPermaLink="false">6433b21df3f6f8067537b260</guid>
                
                    <category>
                        <![CDATA[ 正则表达式 ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Chengjun.L ]]>
                </dc:creator>
                <pubDate>Mon, 03 Apr 2023 07:12:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2023/04/start-graph--7-.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>原文：</strong> <a href="https://www.freecodecamp.org/news/how-to-include-empty-string-in-regex/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">How to Include an Empty String in RegEx</a>
      </p><p>正则表达式（RegEx 或 RegExp）是一个定义搜索模式的字符序列。</p><p>你可以在各种各样的应用程序（如文本编辑器、开发工具和命令行工具）中使用它们来搜索、替换和验证文本的字符串。</p><p>正则表达式在编程语言中也被广泛使用，许多语言都有对它的内置支持。</p><p>你可以用正则表达式匹配文本的字符串，这意味着你也可以匹配空字符串。</p><p>在这篇文章中，我将向你展示三种在正则表达式中匹配空字符串的方法。</p><h2 id="-">我们将讨论的内容</h2><ul><li>如何在正则表达式中用脱字符和美元符号元字符匹配空字符串</li><li>如何在正则表达式中用先行断言匹配空字符串</li><li>如何在正则表达式中用负向先行断言匹配空字符串</li><li>总结</li></ul><h2 id="--1">如何在正则表达式中用脱字符和美元符号元字符匹配空字符串</h2><p>脱字符（<code>^</code>）和美元符号（<code>$</code>）元字符分别匹配字符串的开始和结束。</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2023/03/Screenshot-2023-03-31-at-08.22.21.png" class="kg-image" alt="Screenshot-2023-03-31-at-08.22.21" width="600" height="400" loading="lazy"></figure><p>所以，如果你在 <code>^</code> 和 <code>$</code> 之间不放任何东西，它就会匹配一个空字符串：</p><pre><code class="language-console">^$
</code></pre><h2 id="--2">如何在正则表达式中用先行断言匹配空字符串</h2><p>在正则表达式中，先行断言是一个零宽度的断言，它允许你只在一个字符串后面有另一个特定的字符串时进行匹配，而不将该特定的字符串包括在匹配结果中。</p><p>在正则表达式中，有正向和负向的先行断言。<code>?=</code> 表示正向先行断言，<code>?!</code> 表示负向先行断言。你可以用它们来创建更复杂的正则表达式。</p><p>让我们看看如何用正向先行断言来匹配一个空字符串：</p><pre><code class="language-console">^(?=\s*$)
</code></pre><p>在上面的模式中：</p><ul><li><code>^</code> 字符匹配字符串的开头</li><li><code>(?=\s*$)</code> 是一个正向先行断言，它匹配字符串中的一个位置，在这个位置上有以下情况：</li><li><code>\s*</code> 匹配零或更多的空白字符</li><li><code>$</code> 匹配字符串的结尾</li></ul><p>由于先行断言只匹配位置而不是任何字符，所以正则表达式只匹配空字符串。</p><h2 id="--3">如何在正则表达式中用负向先行断言匹配空字符串</h2><p>正如我前面提到的，<code>?!</code> 指定了一个负向先行断言。你可以使用下面的负向先行断言来匹配一个空字符串：</p><pre><code class="language-console">^(?!.*\S)
</code></pre><p>在上面的正则表达式中：</p><ul><li><code>^</code> 字符匹配字符串的开头</li><li><code>(?!.*\S)</code> 是一个负向先行断言，它匹配字符串中的一个位置，其中以下内容不为真：</li><li><code>.*</code> 匹配零个或多个字符</li><li><code>\S</code> 匹配任何非空格的字符</li></ul><p>由于负向先行断言只匹配位置而不是任何字符，所以正则表达式将匹配一个空字符串。</p><h2 id="--4">总结</h2><p>在许多正则表达式测试引擎中，如果你用开始和结束元字符、负向先行断言和正向先行断言测试一个空字符串，你不会得到一个匹配的结果。</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2023/03/Screenshot-2023-03-31-at-09.19.48.png" class="kg-image" alt="Screenshot-2023-03-31-at-09.19.48" width="600" height="400" loading="lazy"></figure><p>但是在 JavaScript 中，你会匹配到，例如：</p><pre><code class="language-js">// 开始和结束元字符
const regEx1 = /^$/g;

// 正向先行断言
const regEx2 = /^(?=\s*$)/g;

// 负向先行断言
const regEx3 = /^(?!.*\S)/g;

const text = '';

console.log(regEx1.exec(text)); // [ '', index: 0, input: '', groups: undefined ]
console.log(regEx2.exec(text)); // [ '', index: 0, input: '', groups: undefined ]
console.log(regEx3.exec(text)); // [ '', index: 0, input: '', groups: undefined ]

console.log('\n');

console.log(regEx1.test(text)); // true
console.log(regEx2.test(text)); // true
console.log(regEx3.test(text)); // true
</code></pre><p>Happy coding!</p> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 正则表达式元字符 \d 代表什么意思 ]]>
                </title>
                <description>
                    <![CDATA[ 正则表达式，也称为 RegEx 或 RegExp，是用于匹配整个字符串或字符串特定部分的定义模式。该字符串包括所有字符，无论是字母、符号还是数字都行。 在这篇文章中，我们将会讲解正则符号\d，这个符号可以匹配数字。 我们将讨论什么内容  * 正则表达式中的 \d 是什么  * 如何使用 \d 元字符  * 数字字符集 [0-9] 和元字符 \d 之间的区别是什么  * 总结 正则表达式中的 `\d` 是什么 \d  在正则表达式中不只是一个“字符”，它是用于匹配字符串的“元字符”之一。 按照定义，元字符是在定义匹配字符串模式时具有特殊含义的字符。 所以，\d  是一个可以匹配 0 到 9 中的任何数字的元字符。你可以使用它来匹配数字或数字集，例如电话号码、编号等。 除了\d，正则表达式中还有很多元字符，比如下面的：  * \w  匹配所有单词字符（小写字母a到z、大写字母A到Z、数字0到9和下划线_）  * \D  ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/what-does-d-mean-in-regex/</link>
                <guid isPermaLink="false">640aa03ff2e3690630c997be</guid>
                
                    <category>
                        <![CDATA[ 正则表达式 ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ songyp0505 ]]>
                </dc:creator>
                <pubDate>Fri, 10 Mar 2023 03:16:20 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2023/03/start-graph--2-.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>原文：</strong> <a href="https://www.freecodecamp.org/news/what-does-d-mean-in-regex/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">Regular Expression Metacharacters - What Does \d Mean in RegEx?</a>
      </p><!--kg-card-begin: markdown--><p>正则表达式，也称为 RegEx 或 RegExp，是用于匹配整个字符串或字符串特定部分的定义模式。该字符串包括所有字符，无论是字母、符号还是数字都行。</p>
<p>在这篇文章中，我们将会讲解正则符号<code>\d</code>，这个符号可以匹配数字。</p>
<h2 id="">我们将讨论什么内容</h2>
<ul>
<li><a href="#whatisdinregex">正则表达式中的 <code>\d</code> 是什么</a></li>
<li><a href="#howtousethedmetacharacter">如何使用 <code>\d</code> 元字符</a></li>
<li><a href="#whatisthedifferencebetweenthedigitcharacterset09anddmetacharacter">数字字符集 <code>[0-9]</code> 和元字符 <code>\d</code> 之间的区别是什么</a></li>
<li><a href="#conclusion">总结</a></li>
</ul>
<h2 id="whatisdinregex">正则表达式中的 `\d` 是什么</h2>
<p><code>\d</code> 在正则表达式中不只是一个“字符”，它是用于匹配字符串的“元字符”之一。</p>
<p>按照定义，元字符是在定义匹配字符串模式时具有特殊含义的字符。</p>
<p>所以，<strong><code>\d</code> 是一个可以匹配 0 到 9 中的任何数字的元字符</strong>。你可以使用它来匹配数字或数字集，例如电话号码、编号等。</p>
<p>除了<code>\d</code>，正则表达式中还有很多元字符，比如下面的：</p>
<ul>
<li><code>\w</code> 匹配所有单词字符（小写字母a到z、大写字母A到Z、数字0到9和下划线_）</li>
<li><code>\D</code> 匹配所有非数字字符。它是 <code>\d</code> 的补集</li>
<li><code>\W</code> 匹配所有非单词字符</li>
<li><code>\s</code> 匹配所有空格字符，包括空格、制表符和回车符等。</li>
</ul>
<h2 id="howtousethedmetacharacter">如何使用 `\d` 元字符</h2>
<p>下面我们一起看看怎样用<code>\d</code>元字符匹配数字。</p>
<p>第一个例子是匹配<code>7253289593</code>这个数字ID。</p>
<p>这是一个十位数，要想匹配它，你可以重复写十次<code>\d</code>元字符：</p>
<figure class="kg-card kg-card-image kg-card-hascaption"><img src="https://www.freecodecamp.org/news/content/images/2023/03/Screenshot-2023-03-02-at-12.10.33.png" alt="Screenshot-2023-03-02-at-12.10.33" class="kg-image" width="600" height="400" loading="lazy"><figcaption></figcaption></figure>
<p>或者也可以写一次<code>\d</code>，然后让它自己重复十次：</p>
<figure class="kg-card kg-card-image kg-card-hascaption"><img src="https://www.freecodecamp.org/news/content/images/2023/03/Screenshot-2023-03-02-at-12.11.39.png" alt="Screenshot-2023-03-02-at-12.11.39" class="kg-image" width="600" height="400" loading="lazy"><figcaption></figcaption></figure>
<p>你也可以匹配手机号，以美国手机号为例：</p>
<figure class="kg-card kg-card-image kg-card-hascaption"><img src="https://www.freecodecamp.org/news/content/images/2023/03/Screenshot-2023-03-02-at-12.16.25.png" alt="Screenshot-2023-03-02-at-12.16.25" class="kg-image" width="600" height="400" loading="lazy"><figcaption></figcaption></figure>
<p>或者这样匹配尼日利亚手机号码：</p>
<figure class="kg-card kg-card-image kg-card-hascaption"><img src="https://www.freecodecamp.org/news/content/images/2023/03/Screenshot-2023-03-02-at-12.17.33.png" alt="Screenshot-2023-03-02-at-12.17.33" class="kg-image" width="600" height="400" loading="lazy"><figcaption></figcaption></figure>
<p><code>\d</code>元字符同样可以在 JavaScript 中使用：</p>
<pre><code class="language-js">// 测试一个数字ID
let id = '7253289593';
let regex1 = /\d{10}/g;

console.log(regex1.test(id)); // true

// 测试美国手机号码
let UsPhoneNum = '(123) 456-7890';
let regex2 = /\(\d{3}\)\s\d{3}-\d{4}/g;

console.log(regex2.test(UsPhoneNum)); // true

// 测试尼日利亚手机号码
let naijaPhoneNum = '08133333333';
let regex3 = /\d{11}/g;

console.log(regex3.test(naijaPhoneNum)); // true
</code></pre>
<h2 id="whatisthedifferencebetweenthedigitcharacterset09anddmetacharacter">数字字符集 `[0-9]` 和元字符 `\d` 之间的区别是什么</h2>
<p><code>[0-9]</code> 字符集和 <code>\d</code> 元字符之间没有什么很大的区别</p>
<p>一个可能存在的区别，就是某些正则表达式的语法不支持 <code>\d</code> 元字符，但支持 <code>[0-9]</code> 字符集。例如，grep 的正则表达式不支持 <code>\d</code> 元字符。</p>
<p>所以如果你在正则表达式中使用 <code>[0-9]</code> 代替 <code>\d</code>，你仍然能够进行字符的匹配。</p>
<p>例如，我们可以使用 <code>[0-9]</code> 而不是 <code>\d</code> 来匹配前面例子中的数字ID：</p>
<figure class="kg-card kg-card-image kg-card-hascaption"><img src="https://www.freecodecamp.org/news/content/images/2023/03/Screenshot-2023-03-02-at-12.28.17.png" alt="Screenshot-2023-03-02-at-12.28.17" class="kg-image" width="600" height="400" loading="lazy"><figcaption></figcaption></figure>
<p>可以看到这样依然可以匹配成功，因此，<code>[0-9]</code> 字符集和 <code>\d</code> 元字符之间没有任何差别。</p>
<h2 id="conclusion">总结</h2>
<p>这篇文章介绍了 <code>\d</code> 正则表达式元字符是什么，以及如何使用它。我们还看了一些例子，并将<code>\d</code> 与数字字符集 <code>[0-9]</code> 进行了比较，以便了解它们的工作原理和区别。</p>
<p>感谢阅读。</p>
<!--kg-card-end: markdown--> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ JavaScript 正则表达式和 replace() 方法使用示例 ]]>
                </title>
                <description>
                    <![CDATA[ 正则表达式（在代码中常简写为 RegEx 或 RegExp）是一种强大的分析文本的方法。使用正则表达式，你可以在匹配特定字符（例如 JavaScript）或模式（例如 NumberStringSymbol-3a＆）的位置匹配字符串。 .replace 方法用于 JavaScript 中的字符串，以用字符替换字符串的一部分。这是它的使用示例： const str = 'JavaScript'; const newStr = str.replace("ava", "-"); console.log(newStr); // J-Script 如上所示，.replace 方法接受两个参数：要替换的字符串以及将替换为的字符串。 这就是正则的用武之地。 上面 .replace 的使用受到限制：要替换的字符是已知的——“ava”。如果我们想用某个模式，该怎么办？ 也许是一个数字、两个字母和单词 “foo” 或三个符号一起使用？ .replace 方法可以实现此目的。可以用正则重新创建模式，将其与 .replace 结合使用，我们可以替换模式，而不仅仅是替换精确的字符。 如何在 Jav ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/javascript-string-replace-example-with-regex/</link>
                <guid isPermaLink="false">60ab13f876aad305289cada4</guid>
                
                    <category>
                        <![CDATA[ 正则表达式 ]]>
                    </category>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Chengjun.L ]]>
                </dc:creator>
                <pubDate>Wed, 26 May 2021 10:00:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2021/05/5f9c982a740569d1a4ca1884.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>正则表达式（在代码中常简写为 RegEx 或 RegExp）是一种强大的分析文本的方法。使用正则表达式，你可以在匹配特定字符（例如 JavaScript）或模式（例如 NumberStringSymbol-3a＆）的位置匹配字符串。</p><p><code>.replace</code> 方法用于 JavaScript 中的字符串，以用字符替换字符串的一部分。这是它的使用示例：</p><pre><code class="language-js">const str = 'JavaScript';
const newStr = str.replace("ava", "-");
console.log(newStr);
// J-Script
</code></pre><p>如上所示，<code>.replace</code> 方法接受两个参数：要替换的字符串以及将替换为的字符串。</p><p>这就是<strong>正则</strong>的用武之地。</p><p>上面 <code>.replace</code> 的使用受到限制：要替换的字符是已知的——“ava”。如果我们想用某个模式，该怎么办？ 也许是一个数字、两个字母和单词 “foo” 或三个符号一起使用？</p><p><code>.replace</code> 方法可以实现此目的。可以用正则重新创建模式，将其与 <code>.replace</code> 结合使用，我们可以替换模式，而不仅仅是替换精确的字符。</p><h2 id="-javascript-replace"><strong>如何在 JavaScript 中使用正则表达式和 </strong><code><strong>.replace</strong></code></h2><p>要使用正则，需要将 <code>replace</code> 的第一个参数替换为正则语法（例如 <code>/regex/</code>）。此语法用作一种模式，其中与字符串匹配的任何部分都将被新的子字符串替换。</p><p>这是一个例子：</p><pre><code class="language-js">// matches a number, some characters and another number
const reg = /\d.*\d/
const str = "Java3foobar4Script"
const newStr = str.replace(reg, "-");
console.log(newStr);
// "Java-Script"
</code></pre><p>字符串 <code>3foobar4</code> 和正则 <code>/\d.*\d/</code> 匹配，所以它被替换了。</p><p>如果我们想在多个地方进行替换呢？</p><p>正则表达式中的 <code>g</code>（全局）标记可以实现这个功能，并且也可以将其与 <code>replace</code> 一起使用，如下示例：</p><pre><code class="language-js">const reg = /\d{3}/g
const str = "Java323Scr995ip4894545t";
const newStr = str.replace(reg, "");
console.log(newStr);
// JavaScrip5t
// 5 didn't pass the test :(
</code></pre><p>正则表达式匹配字符串中正好是 3 个连续数字的部分。<code>323</code>、<code>995</code>、<code>995</code>、<code>995</code> 都与模式匹配，但是最后的 <code>5</code> 与模式不匹配。</p><p>结果是 <code>JavaScrip5t</code> 显示了模式是如何正确匹配的，并用新的子字符串（空字符串）替换。</p><p>也可以使用大小写标记 <code>i</code>。这意味着你可以替换不区分大小写的模式。使用方法如下：</p><pre><code class="language-js">const reg1 = /\dA/
const reg2 = /\dA/i
const str = "Jav5ascript"
const newStr1 = str.replace(reg1, "--");
const newStr2 = str.replace(reg2, "--");
console.log(newStr1) // Jav5ascript
console.log(newStr2) // Jav--script
</code></pre><p><code>..5a ..</code> 与第一个语法不匹配，因为正则表达式默认情况下区分大小写。但是，如第二个语法所示，使用了 <code>i</code> 标记后，该字符串将按预期被替换。</p><h2 id="-split"><strong>如何使用正则表达式和 <code>split</code></strong></h2><p>也可以将 <code>split</code> &nbsp;和正则表达式一起使用。这意味着你不仅可以将字符串拆分为与确切字符或模式匹配的子字符串，如下所示：</p><pre><code class="language-js">const regex = /\d{2}a/;
const str = "Hello54 How 64aare you";
console.log(str.split(regex))
// ["Hello54 How ", "are you"]
</code></pre><p>字符串在 <code>64a</code> 处被拆分，因为子字符串与正则表达式相匹配。</p><p>注意，与 <code>i</code> 标记和其他标记不同，全局标记 <code>g</code> 在 <code>split</code> 中是无关紧要的。这是因为 <code>split</code> 在与正则表达式匹配的几个位置拆分了字符串。</p><h2 id="-">小结</h2><p>正则表达式使在 JavaScript 中替换字符串更加有效、更加强大，也更有趣。</p><p>不仅限于确切的字符，还可以同时使用模式和多个替换项。在本文中，我们通过一些示例了解了它们如何协同工作。</p><p>原文：<a href="https://www.freecodecamp.org/news/javascript-string-replace-example-with-regex/">JavaScript String.Replace() Example with RegEx</a>，作者：<a href="https://www.freecodecamp.org/news/author/dillionmegida/">Dillion Megida</a></p> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 做轮子·实现路由的两种思路 ]]>
                </title>
                <description>
                    <![CDATA[ > 本文指的路由，不是路由器，也不是PS4（5？），是特指后端API框架里的router，前端路由也有可以有借鉴的部分，但本文不承诺正确性。 今天不妨讨论个不那么大的话题，restful路由的实现。 我们眼中的路由是什么样子的？ 我们常见的路由是这个样子的 router.get('/what/i/want', ... ); router.get('/what/i/hate', ... ); router.get('/what/u/want', ... ); 我们今天就来讨论一下，当我们想访问 /what/i/want时，都有些什么玄机。 路由是怎么工作的 首先明确路由的目的，就是根据接收到的路径字符串，找到对应的业务处理逻辑。 为了弄明白业界常用框架对这一块，我专门查阅了若干开发框架的源码（express, koa, Gin, kratos, Lumen），最终概括成两种实现手段。  * Key - Value 型路由  * Tree 型路由 现在来分析一下两种路由的特性和适用场景。 Key - Value 型路由 这个模式，形如我们熟悉的hash tab ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/how-backend-router-work/</link>
                <guid isPermaLink="false">5f5103dccd07b005bfb5aeb6</guid>
                
                    <category>
                        <![CDATA[ 路由 ]]>
                    </category>
                
                    <category>
                        <![CDATA[ RESTful API ]]>
                    </category>
                
                    <category>
                        <![CDATA[ 正则表达式 ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ 开发者小蓝 ]]>
                </dc:creator>
                <pubDate>Sat, 13 Feb 2021 09:00:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2020/09/318-02.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <blockquote>
<p>本文指的路由，不是路由器，也不是PS4（5？），是特指后端API框架里的router，前端路由也有可以有借鉴的部分，但本文不承诺正确性。</p>
</blockquote>
<p>今天不妨讨论个不那么大的话题，<strong>restful路由</strong>的实现。</p>
<h4 id="">我们眼中的路由是什么样子的？</h4>
<p>我们常见的路由是这个样子的</p>
<pre><code>router.get('/what/i/want', ... );

router.get('/what/i/hate', ... );

router.get('/what/u/want', ... );
</code></pre>
<p>我们今天就来讨论一下，当我们想访问 <code>/what/i/want</code>时，都有些什么玄机。</p>
<h4 id="">路由是怎么工作的</h4>
<p>首先明确路由的目的，就是根据接收到的路径字符串，找到对应的业务处理逻辑。</p>
<p>为了弄明白业界常用框架对这一块，我专门查阅了若干开发框架的源码（express, koa, Gin, kratos, Lumen），最终概括成两种实现手段。</p>
<ul>
<li>Key - Value 型路由</li>
<li>Tree 型路由</li>
</ul>
<p><img src="https://lanhaooss.oss-cn-shenzhen.aliyuncs.com/images/328/318-03.jpg" alt="318-03" width="600" height="400" loading="lazy"></p>
<p>现在来分析一下两种路由的特性和适用场景。</p>
<h4 id="keyvalue">Key - Value 型路由</h4>
<p>这个模式，形如我们熟悉的<code>hash table</code>、字典，通过键值对维护这<code>uri</code>与函数的对应关系。</p>
<p><img src="https://lanhaooss.oss-cn-shenzhen.aliyuncs.com/images/328/318-04.jpg" alt="318-04" width="600" height="400" loading="lazy"></p>
<p>我们理所当然地认为，只要确定的访问地址， 如<code>/what/i/want</code>, 就能立即找到对应的业务方法<code>functionA</code>，是个铁骨铮铮的 <code>O(1)</code>操作。</p>
<blockquote>
<p>当一个操作的复杂度与规模无关，就是一个O(1)的操作。比如无论我的 uri 地址有多少个，只要确定 uri 的值，总是能立即确定与之对应的方法。</p>
</blockquote>
<p><strong>然而遗憾的是，我就没见到有这么实现路由的。</strong></p>
<p>真实的情况是，</p>
<p><strong>Key - Value 型路由会从前到后一个一个比较，直到找到一个能匹配当前 uri 的key，再确定它对应的 function</strong></p>
<p>所以实际上它是一个 <code>O(n)</code> 操作，平均情况下需要比较 <code>N/2</code> 次才能定位。</p>
<blockquote>
<p>一个 <code>O(n)</code> 操作，其复杂度会随着数据规模的增大而线性增长，而我们普遍认为，在经过多次测量观察后，平均情况下会在 N/2 的位置上找到答案。</p>
</blockquote>
<p>疑惑归疑惑，正如本文的作者一样，做轮子人并不是疯了。</p>
<p><strong>之所以采用这种逐个匹配的模式，是因为我们的uri通过是不能确定的。</strong></p>
<p>比如， 一些<code>uri</code>里甚至包含了变量</p>
<pre><code>GET /staff/9527

GET /staff/709394
</code></pre>
<p>所以，我们通常是吧 <strong>Key - Value</strong>里面的<strong>Key</strong>，设计成<strong>正则表达式</strong>。</p>
<p>相应的，在理论研究中的<strong>Hash Table</strong>也会被<strong>顺序表</strong>替代。</p>
<p>总结起来就是，</p>
<hr>
<p>当我们访问一个确定的<code>uri</code>时，路由会尝试按顺序逐个匹配注册好的<strong>正则表达式</strong>，直到<code>match</code>到一个结果。</p>
<p>否则，返回 404</p>
<hr>
<p><strong>升华一下</strong></p>
<blockquote>
<p>如何压榨这种路由的性能？ 既然是按顺序逐个匹配，那我们就根据自己的业务特点，把可能访问量最大的接口注册到前面去。</p>
<p>也有一些框架是会动态调整顺序的，这就是题外话。</p>
</blockquote>
<p>--</p>
<h4 id="tree">Tree 型路由</h4>
<p>这个模式相对来说思路要骚一些。</p>
<p>它把<code>uri</code>按照<code>/</code>分割一个，在之前构建好的路由树里，一个节点一个几点的检索，直到成功走到一个叶子节点。</p>
<p><img src="https://lanhaooss.oss-cn-shenzhen.aliyuncs.com/images/328/318-05.jpg" alt="318-05" width="600" height="400" loading="lazy"></p>
<p>与上面提到的<code>N/2</code>次比较相比，这个模式的比较次数比较稳定，就是<code>uri</code>的层数。</p>
<blockquote>
<p>对于 <code>/what/i/want</code>，我们认为它是一个 3 层的地址，如果这个地址是存在的，那么在比较3次以后，就能找到对应的答案。</p>
</blockquote>
<p><strong>真是 “遇事不决，数据结构” 啊</strong></p>
<p>这种树形的路由特别适合<code>restful</code>风格的API，因为我们设计<code>restful</code>接口的时候，通常每一层都有它自己的<strong>类聚含义</strong>，所以它会构建出一棵非常标致的树。</p>
<p><img src="https://lanhaooss.oss-cn-shenzhen.aliyuncs.com/images/328/318-06.jpg" alt="318-06" width="600" height="400" loading="lazy"></p>
<p><strong>问(gang)题(jing)来了</strong></p>
<p>Q1: 请问 Tree 型路由 怎么解决<code>uri</code>里有变量的情况呢？</p>
<blockquote>
<p>很巧妙。当你注册的路由包含了变量，比如 <code>/staff/:id</code>这样的，它会在这个树<code>staff</code>的子节点里，也就第二层，创建一个通配节点（可能是<code>*</code>）。</p>
<p>它认为，不管<code>staff</code>后面跟什么内容，都能匹配这个<code>*</code></p>
</blockquote>
<p>Q2: 如果我同时注册了<code>/staff/:id</code>和<code>/staff/list</code>呢？会匹配错吗？</p>
<blockquote>
<p>考虑到这种情况，Tree 型路由的一般实现是，优先匹配确定值<code>list</code>，次要匹配<code>*</code>。</p>
<p>有这种情况是要复杂一些，不过并不影响它的整体复杂度。</p>
</blockquote>
<h4 id="">最后，作者喜欢哪一种？</h4>
<p>尽管我在最近开发的一个框架里使用了<strong>Tree 型路由</strong>，这并不代表我的倾向。</p>
<p>实际上应该根据上文提到两种模式各自的特点，选择最好的实现方式。</p>
<p><strong>当然</strong></p>
<p>我期待读者自己做轮子的时候，能做出一个根据开发者注册的路由的特点，自动选择路由模型的<strong>智能路由</strong>。</p>
<hr>
<p>好了，今天先到这里，下次再会。</p>
<!--kg-card-end: markdown--> ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
