<?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[ JavaScript - 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[ JavaScript - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/chinese/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Thu, 14 May 2026 08:49:30 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/chinese/news/tag/javascript/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ 如何使用懒加载优化 Next.js 应用程序性能 ]]>
                </title>
                <description>
                    <![CDATA[ 人们不喜欢使用速度慢的应用程序。初始加载时间对网络应用程序和网站来说至关重要。 如果应用程序的加载时间超过 3 秒，就会被视为速度缓慢，可能导致用户离开应用程序或网站。 Next.js 是一个基于 React 的框架，可以用来构建可伸缩、性能良好且速度快的网络应用程序和网站。随着 React 服务器组件 [https://www.freecodecamp.org/news/how-to-use-react-server-components/] 在 Next.js 应用程序路由版本中的引入，开发人员有了一个新的心智模型来“建立服务器组件的开发思维模式”。它解决了 SEO 问题，帮助创建 零打包体积（zero bundle size） 的 React 组件，最终实现 UI 组件的更快加载。 但你的应用程序可能并不总是关于服务端组件。你可能还需要使用客户端组件。此外，你可能希望将它们作为应用程序初始加载的一部分或按需加载（比如点击按钮时）。 在浏览器中加载客户端组件包括将组件代码下载到浏览器、下载所有导入到该客户端组件中的库和其他组件，以及 React 为确保你的组件正常工作而处理 ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/next-js-performance-optimization/</link>
                <guid isPermaLink="false">67ebdbb1930d5ec015f6bd16</guid>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ luojiyin ]]>
                </dc:creator>
                <pubDate>Tue, 01 Apr 2025 12:00:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2025/04/lazyloading-next.js.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>原文：</strong> <a href="https://www.freecodecamp.org/news/next-js-performance-optimization/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">How to Optimize Next.js App Performance With Lazy Loading</a>
      </p><!--kg-card-begin: markdown--><p>人们不喜欢使用速度慢的应用程序。初始加载时间对网络应用程序和网站来说至关重要。</p>
<p>如果应用程序的加载时间超过 3 秒，就会被视为速度缓慢，可能导致用户离开应用程序或网站。</p>
<p><code>Next.js</code> 是一个基于 React 的框架，可以用来构建可伸缩、性能良好且速度快的网络应用程序和网站。随着 <a href="https://www.freecodecamp.org/news/how-to-use-react-server-components/">React 服务器组件</a> 在 Next.js 应用程序路由版本中的引入，开发人员有了一个新的心智模型来“建立服务器组件的开发思维模式”。它解决了 SEO 问题，帮助创建 <code>零打包体积（zero bundle size）</code> 的 React 组件，最终实现 UI 组件的更快加载。</p>
<p>但你的应用程序可能并不总是关于服务端组件。你可能还需要使用客户端组件。此外，你可能希望将它们作为应用程序初始加载的一部分或按需加载（比如点击按钮时）。</p>
<p>在浏览器中加载客户端组件包括将组件代码下载到浏览器、下载所有导入到该客户端组件中的库和其他组件，以及 React 为确保你的组件正常工作而处理的一些额外事项。</p>
<p>根据用户的互联网连接和其他网络因素，整个客户端组件的加载可能需要一段时间，这可能会阻止用户更快地使用应用程序。</p>
<p>这就是 <code>懒加载</code> 技术可以派上用场的地方。它们可以帮助你避免在浏览器上单体加载客户端组件。</p>
<p>在本文中，我们将讨论在 Next.js 中用于客户端组件加载优化的几种懒加载技术。我们还会讨论一些你需要知道的边缘情况。</p>
<p>如果你也喜欢通过视频内容学习，本文还提供了视频教程：🙂</p>
<figure class="kg-card kg-embed-card" data-test-label="fitted">
        <div class="fluid-width-video-container">
          <div style="padding-top: 56.25%;" class="fluid-width-video-wrapper">
            <iframe width="560" height="315" src="https://www.youtube.com/embed/gq9bBZru78Y" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube 视频播放器" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy" name="fitvid0"></iframe>
          </div>
        </div>
      </figure>
<p>在我们开始之前，有几件事需要告诉你：</p>
<ul>
<li>我们将编写大量代码来构建一个演示懒加载技术的应用程序。你可以从这个 GitHub 存储库找到所有源代码：<a href="https://github.com/tapascript/nextjs-lazy-load">https://github.com/tapascript/nextjs-lazy-load</a>。但我强烈建议你在我们进行的过程中自己编写代码，并仅将存储库作为参考。</li>
<li>你也可以通过 <a href="https://nextjs-lazy-load.netlify.app/">Netlify 上的公开部署</a> 访问应用程序。</li>
</ul>
<p>让我们开始吧 🚀。哦对了，如果你是汤姆和杰瑞的卡通迷，你会更喜欢这篇文章！</p>
<h2 id=""><strong>目录</strong></h2>
<ul>
<li><a href="#heading-what-is-lazy-loading">什么是懒加载</a></li>
<li><a href="#heading-lazy-loading-techniques-in-nextjs">Next.js 中的懒加载技术</a></li>
<li><a href="#heading-lazy-loading-with-dynamic-import-and-nextdynamic">使用 dynamic import 和 next/dynamic 进行懒加载</a></li>
<li><a href="#heading-lazy-loading-with-reactlazy-and-suspense">使用 React.lazy() 和 Suspense 进行懒加载</a></li>
<li><a href="#heading-how-to-lazy-load-the-named-exported-components">如何懒加载命名导出的组件</a></li>
<li><a href="#heading-lazy-loading-your-server-components-1">懒加载你的服务端组件</a></li>
<li><a href="#heading-lazy-loading-your-server-components-1">我们应该懒加载 Next.js 中的所有客户端组件吗</a></li>
<li><a href="#heading-whats-next">接下来是什么</a></li>
</ul>
<h2 id="">什么是懒加载</h2>
<p>在现代 Web 应用程序开发中，我们不会将所有逻辑编写到一个 JavaScript/TypeScript 文件中，或将所有样式编写到一个巨大的 CSS 文件中。而是将它们在源代码级别拆分，创建逻辑模块、业务逻辑、展示组件和样式相关文件。这帮助我们更好地组织代码。</p>
<p>然后我们使用一种称为打包器的工具，它在应用程序开发过程的构建阶段启动。它为我们的脚本和样式创建包。一些著名的打包器包括 Webpack、Rollup 和 Parcel 等。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/07/image-43.png" alt="图片" width="600" height="400" loading="lazy"> <em>打包器从源代码创建包</em></p>
<p>现在，由于我们有了包，如果尝试将它们一起加载到浏览器中，我们会遇到一些缓慢。这是因为需要将完整的包加载到浏览器中，用户界面才能正常工作。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/07/image-44.png" alt="图片" width="600" height="400" loading="lazy"> <em>加载大量包会导致加载体验差</em></p>
<p>因此，现代 Web 开发库和工具系统允许我们将包分块加载，而不是等待大量包加载到浏览器中。</p>
<p>我们可能希望立即加载一些块，因为用户可能会在应用程序加载时更早需要它们。与此同时，我们可能希望等待加载网页的某些部分，直到它们真正需要时。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/07/image-45.png" alt="图片" width="600" height="400" loading="lazy"> <em>拆分成块并加载所需的部分</em></p>
<p>这种等待加载网页或应用程序部分的机制，并仅在绝对必要时加载它们，被称为 <code>懒加载</code>。懒加载的概念并不是 React 或 Next.js 特有的。这是一种性能优化技术，你可以使用各种库和框架来实现。</p>
<p>Next.js 中的懒加载技术用于减少一个路径所需的 JavaScript 量。这有助于提高应用程序的初始加载性能。我们可以推迟加载客户端组件和导入的库，直到它们真正需要时。</p>
<p>我们可以通过两种方式在 Next.js 中实现懒加载技术：</p>
<ul>
<li>使用 <code>next/dynamic</code> 包的动态导入。</li>
<li>使用 <code>React.lazy()</code> 和 <code>Suspense</code> 的组合。</li>
</ul>
<p>让我们通过代码示例来理解这些技术。</p>
<h2 id="dynamicimportnextdynamic">使用 <code>dynamic import</code> 和 <code>next/dynamic</code> 实现懒加载</h2>
<p><code>next/dynamic</code> 是 ReactJS 中的 React.lazy() 和 Suspense 的组合。使用 <code>next/dynamic</code> 包进行动态导入是实现 Next.js 中懒加载的首选方法。</p>
<p>为了演示这一点，我们首先用以下命令创建一个 Next.js 应用：</p>
<pre><code class="language-shell">npx create-next-app@latest
</code></pre>
<p>您可以使用以下命令在本地启动应用：</p>
<pre><code class="language-shell">## 使用 npm
npm run dev

## 使用 yarn
yarn dev

## 或者使用 pnpm, bun，您喜欢的都可以！
</code></pre>
<p>现在在 <code>app/</code> 目录下创建一个名为 <code>components</code> 的文件夹。我们将在这个组件文件夹下创建所有的组件。现在，在 <code>app/components/</code> 下创建一个名为 <code>tom</code> 的文件夹。最后，在 <code>app/components/tom/</code> 目录下创建一个名为 <code>tom.jsx</code> 的 React 组件，代码如下：</p>
<pre><code class="language-jsx">// tom.jsx

const LazyTom = () =&gt; {
  return (
    &lt;div className="flex flex-col"&gt;
      &lt;h1 className="text-3xl my-2"&gt;The Lazy Tom&lt;/h1&gt;
      &lt;p className="text-xl my-1"&gt;
        Tom, named &amp;quot;Jasper&amp;quot; in his debut appearance, is a gray and white
        domestic shorthair cat 🐈. &amp;quot;Tom&amp;quot; is a generic name for a male cat. He is
        usually but not always, portrayed as living a comfortable, or even
        pampered life. Tom is no match for Jerry&amp;apos;s wits.
      &lt;/p&gt;
      &lt;p className="text-xl my-1"&gt;
        Although cats typically chase mice to eat them, it is quite rare for Tom
        to actually try to eat Jerry. He tries to hurt or compete with him just
        to taunt Jerry, even as revenge, or to obtain a reward from a human,
        including his owner(s)/master(s), for catching Jerry, or for generally
        doing his job well as a house cat. By the final &amp;quot;fade-out&amp;quot; of each
        cartoon, Jerry usually gets the best of Tom.
      &lt;/p&gt;
    &lt;/div&gt;
  );
};

export default LazyTom;
</code></pre>
<p>解释上述代码：</p>
<ul>
<li>我们创建了一个名为 <code>LazyTom</code> 的 ReactJS 组件。</li>
<li>这是一个简单的展示组件，包含一个标题和几段关于著名的 <code>Tom &amp; Jerry</code> 动画片中的猫 Tom 的描述。</li>
<li>最后，我们默认导出了该组件以便在其他地方导入。</li>
</ul>
<p>现在，在 <code>app/components/tom/</code> 目录下创建另一个名为 <code>tom-story.jsx</code> 的文件，代码如下：</p>
<pre><code class="language-jsx">// tom-story.jsx

"use client";

import { useState } from "react";
import dynamic from "next/dynamic";

const LazyTom = dynamic(() =&gt; import("./tom"), {
    loading: () =&gt; &lt;h1&gt;Loading Tom&amp;apos;s Story...&lt;/h1&gt;,
});

function TomStory() {
    const [shown, setShown] = useState(false);

    return (
        &lt;div className="flex flex-col m-8 w-[300px]"&gt;
            &lt;h2 className="text-xl my-1"&gt;Demonstrating &lt;strong&gt;dynamic&lt;/strong&gt;&lt;/h2&gt;
            &lt;button
                className="bg-blue-600 text-white rounded p-1"
                onClick={() =&gt; setShown(!shown)}
            &gt;
                Load 🐈 Tom&amp;apos;s Story
            &lt;/button&gt;

            {shown &amp;&amp; &lt;LazyTom /&gt;}
        &lt;/div&gt;
    );
}

export default TomStory;
</code></pre>
<p>以上代码实现了懒加载的主要部分：</p>
<ul>
<li>我们创建了一个名为 <code>TomStory</code> 的客户端组件，并使用 <code>"use client"</code> 指令。</li>
<li>首先，我们导入了用于管理切换状态的 <code>useState</code> 钩子，以及用于懒加载我们之前创建的组件的 <code>dynamic</code> 函数。</li>
<li><code>dynamic</code> 函数接受一个返回导入组件的函数作为参数。你还可以通过提供一个可选的配置对象来配置自定义加载消息。</li>
<li><code>dynamic()</code> 函数返回懒加载的组件实例，即 <code>LazyTom</code>（可以是任何名称）。但这个组件尚未加载。</li>
<li>在 JSX 中，我们有一个切换按钮用于显示和隐藏 <code>LazyTom</code> 组件。请注意，该组件将在首次点击按钮时懒加载到用户浏览器中。之后，如果您再次隐藏和显示它，<code>LazyTom</code> 组件将不会重新加载，除非我们硬刷新浏览器或清除浏览器缓存。</li>
<li>最后，我们默认导出了 <code>TomStory</code> 组件。</li>
</ul>
<p>现在我们需要测试它。为此，打开 <code>app/</code> 目录下的 <code>page.js</code> 文件，并用以下代码替换内容：</p>
<pre><code class="language-typescript">import TomStory from "./components/tom/tom-story";

export default function Home() {
  return (
    &lt;div className="flex flex-wrap justify-center "&gt;
      &lt;TomStory /&gt;
    &lt;/div&gt;
  );
}
</code></pre>
<p>这是一个简单的 ReactJS 组件，导入了 <code>TomStory</code> 组件并在其 JSX 中使用它。现在打开您的浏览器窗口。打开浏览器的开发者工具并打开 <code>网络</code> 选项卡。确保选中 <code>全部</code> 过滤器。</p>
<p><code>LazyTom</code>组件从 <code>tom.jsx</code> 文件还没有被下载，因为我们还没有点击 <code>加载 Tom 的故事</code> 按钮。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/07/Screenshot-2024-07-17-at-9.21.10-AM.png" alt="Image" width="600" height="400" loading="lazy"> <em>按钮来懒加载 Tom 的故事</em></p>
<p>现在，点击按钮。你应该会看到一个加载信息，然后组件会加载 Tom 的故事。你可以在 <code>Network</code> 选项卡中看到 <code>tom.jsx</code> 组件被列出，并且页面上也会渲染出 Tom 的故事。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/07/Screenshot-2024-07-17-at-9.27.55-AM.png" alt="Image" width="600" height="400" loading="lazy"> <em>现在 Tom 的故事已被懒加载</em></p>
<p>现在你已经体验了 <code>next/dynamic</code> 的 <code>dynamic</code> 函数如何帮助我们懒加载组件，让我们开始使用 <code>React.lazy()</code> 和 <code>Suspense</code> 的另一种技术。</p>
<h2 id="reactlazysuspense">使用 <code>React.lazy()</code> 和 <code>Suspense</code> 懒加载</h2>
<p>为展示此技术，让我们从 Jerry 的故事开始，这是我最喜欢的《猫和老鼠》卡通角色。</p>
<p>首先，我们将在 <code>app/components/</code> 目录下创建一个名为 <code>jerry</code> 的文件夹。现在，在 <code>app/components/jerry/</code> 目录下创建一个名为 <code>jerry.jsx</code> 的文件，并添加以下代码：</p>
<pre><code class="language-tsx">// jerry.jsx

const LazyJerry = () =&gt; {
  return (
    &lt;div className="flex flex-col justify-center"&gt;
      &lt;h1 className="text-3xl my-2"&gt;The Lazy Jerry&lt;/h1&gt;
      &lt;p className="text-xl my-1"&gt;
        Jerry 🐀, whose name is not explicitly mentioned in his debut appearance,
        is a small, brown house mouse who always lives in close proximity to
        Tom. Despite being very energetic, determined and much larger, Tom is no
        match for Jerry&amp;apos;s wits. Jerry possesses surprising strength for his
        size, approximately the equivalent of Tom&amp;apos;s, lifting items such as
        anvils with relative ease and withstanding considerable impacts.
      &lt;/p&gt;
      &lt;p className="text-xl my-1"&gt;
        Although cats typically chase mice to eat them, it is quite rare for Tom
        to actually try to eat Jerry. He tries to hurt or compete with him just
        to taunt Jerry, even as revenge, or to obtain a reward from a human,
        including his owner(s)/master(s), for catching Jerry, or for generally
        doing his job well as a house cat. By the final &amp;quot;fade-out&amp;quot; of each
        cartoon, Jerry usually gets the best of Tom.
      &lt;/p&gt;
    &lt;/div&gt;
  );
};

export default LazyJerry;
</code></pre>
<p><code>jerry.jsx</code> 的内容结构上与 <code>tom.jsx</code> 类似。这里我们发布了 Jerry 的故事，而不是 Tom 的，并默认导出了这个组件。</p>
<p>像上次一样，我们来创建一个 <code>jerry-story.jsx</code> 文件来展示 Jerry 的故事懒加载。将该文件创建在 <code>app/components/jerry/</code> 目录下，添加以下代码：</p>
<pre><code class="language-tsx">// jerry-story.jsx

"use client";

import React, { useState, Suspense } from "react";

const LazyJerry = React.lazy(() =&gt; import('./jerry'));

function JerryStory() {
    const [shown, setShown] = useState(false);

    return (
        &lt;div className="flex flex-col m-8 w-[300px]"&gt;
            &lt;h2 className="text-xl my-1"&gt; 演示 &lt;strong&gt;React.lazy()&lt;/strong&gt;&lt;/h2&gt;
            &lt;button
                className="bg-pink-600 text-white rounded p-1"
                onClick={() =&gt; setShown(!shown)}
            &gt;
                加载 🐀 Jerry 的故事
            &lt;/button&gt;

            {shown &amp;&amp; &lt;Suspense fallback={&lt;h1&gt;加载 Jerry 的故事&lt;/h1&gt;}&gt;
                &lt;LazyJerry /&gt;
            &lt;/Suspense&gt;}
        &lt;/div&gt;
    );
}

export default JerryStory;
</code></pre>
<p>这里我们也有一个客户端组件，我们将使用 React 的 <code>lazy()</code> 方法和 <code>Suspense</code>，所以我们导入了它们。像上次的 <code>dynamic()</code> 函数一样，<code>lazy()</code> 函数也需要一个返回懒加载组件的函数作为参数。我们还提供了要加载的组件的相对路径。</p>
<p>注意，用 <code>dynamic()</code> 我们可以将加载信息作为函数的一部分进行定制。而用 <code>lazy()</code>，我们将在 <code>Suspense</code> 的 fallback 中进行处理。</p>
<p>Suspense 在等待数据加载时会使用 fallback。如果你想深入了解 ReactJS 的 Suspense 和 Error Boundary，可以<a href="https://www.youtube.com/watch?v=OpHbSHp8PcI">查看这个视频教程</a>。</p>
<p>在这里，由于我们的 <code>LazyJerry</code> 组件是懒加载的，我们提供了一个 fallback 信息，在组件代码成功下载并渲染到浏览器之前显示一个加载信息。</p>
<pre><code class="language-tsx">{shown &amp;&amp; 
    &lt;Suspense fallback={&lt;h1&gt;加载 Jerry 的故事&lt;/h1&gt;}&gt;
                &lt;LazyJerry /&gt;
    &lt;/Suspense&gt;
}
</code></pre>
<p>此外，正如你所看到的，我们在第一次点击按钮时加载组件。这里组件不会在每次点击按钮时重新加载，除非我们刷新浏览器或清除浏览器缓存。</p>
<p>现在通过将其导入 <code>page.js</code> 文件并将组件添加到 JSX 中来进行测试。</p>
<pre><code class="language-tsx">// page.js

import TomStory from "./components/tom/tom-story";
import JerryStory from "./components/jerry/jerry-story"; 

export default function Home() {
  return (
    &lt;div className="flex flex-wrap justify-center "&gt;
      &lt;TomStory /&gt;
      &lt;JerryStory /&gt;
    &lt;/div&gt;
  );
}
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/07/Screenshot-2024-07-17-at-9.33.36-AM.png" alt="Image" width="600" height="400" loading="lazy"> <em>懒加载 Jerry 故事的按钮</em></p>
<p>现在，点击按钮。你会看到组件被加载，并且你可以在网络选项卡的列表中看到它。你应该能够看到作为懒加载组件一部分渲染的 Jerry 的故事。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/07/Screenshot-2024-07-17-at-9.37.30-AM.png" alt="Image" width="600" height="400" loading="lazy"> <em>Jerry 的故事已经被懒加载</em></p>
<h2 id="">如何懒加载命名导出的组件</h2>
<p>到目前为止，我们已经用两种技术导入了用 <code>default export</code> 导出的组件，并且懒加载了它。在 JavaScript（以及在 React 中），你可以通过两种不同的方式导入和导出模块：</p>
<ul>
<li>使用 <code>default</code> 关键字。在这种情况下，导出的模块可以用任何名称导入。如果你想从一个模块中只导出一个功能，你会使用这种方式。</li>
<li>不使用 <code>default</code> 关键字，这被称为 <code>命名导出</code>。在这种情况下，你必须在导出和导入时保持相同的模块名称。在导入时，还需要将模块名包含在大括号（{...}）中。如果你想从一个模块中导出多个功能，你会使用这种方式。</li>
</ul>
<p>如果你想深入了解 JavaScript 模块以及它们是如何工作的，我建议你浏览一下 freeCodeCamp YouTube 频道上发布的<a href="https://www.youtube.com/watch?v=KeBxopnhizw">这门速成课</a>。</p>
<p>为了演示 <code>命名导出</code> 组件的懒加载，让我们创建另一个简单的展示用 React 组件。这次我们将使用 Tom &amp; Jerry 动画中的愤怒但可爱的狗 <code>Spike</code>。</p>
<p>在 <code>app/components/</code> 目录下创建一个名为 <code>spike</code> 的文件夹。现在，在 <code>app/components/spike/</code> 目录下创建一个名为 <code>spike.jsx</code> 的文件，并添加以下代码：</p>
<pre><code class="language-jsx">// spike.jsx

export const LazySpike = () =&gt; {
  return (
    &lt;div className="flex flex-col"&gt;
      &lt;h1 className="text-3xl my-2"&gt;The Lazy Spike&lt;/h1&gt;
      &lt;p className="text-xl my-1"&gt;
        In his attempts to catch Jerry, Tom often has to deal with Spike 🦮, known
        as &amp;quot;Killer&amp;quot; and &amp;quot;Butch&amp;quot; in some shorts, an angry, vicious but easily
        duped bulldog who tries to attack Tom for bothering him or his son Tyke
        while trying to get Jerry. Originally, Spike was unnamed and mute, aside
        from howls and biting noises as well as attacking indiscriminately, not
        caring whether it was Tom or Jerry though usually attacking Tom.
      &lt;/p&gt;
      &lt;p className="text-xl my-1"&gt;
      In
        later cartoons, Spike spoke often, using a voice and expressions,
        performed by Billy Bletcher and later Daws Butler, modeled after
        comedian Jimmy Durante. Spike&amp;apos;s coat has altered throughout the years
        between gray and creamy tan. The addition of Spike&amp;apos;s son Tyke in the
        late 1940s led to both a slight softening of Spike&amp;apos;s character and a
        short-lived spin-off theatrical series called Spike and Tyke.
      &lt;/p&gt;
    &lt;/div&gt;
  );
};
</code></pre>
<p>同样，这个组件在结构上与之前看到的 <code>tom.jsx</code> 和 <code>jerry.jsx</code> 组件完全相同，但有两个主要区别：</p>
<ol>
<li>这里，我们没有使用默认关键词导出组件，因此它是一个 <code>命名导出</code>。</li>
<li>我们在讲述的是狗，Spike。</li>
</ol>
<p>现在，我们需要处理一个命名导出组件的懒加载，并且这与默认导出组件略有不同。</p>
<p>在 <code>app/components/spike/</code> 目录下创建一个名为 <code>spike-story.jsx</code> 的文件，并添加以下代码：</p>
<pre><code class="language-jsx">// spike-story.jsx

"use client";

import { useState } from "react";
import dynamic from "next/dynamic";

const LazySpike = dynamic(() =&gt; import("./spike").then((mod) =&gt; mod.LazySpike), {
    loading: () =&gt; &lt;h1&gt;Loading Spike&amp;apos;s Story...&lt;/h1&gt;,
});

function SpikeStory() {
    const [shown, setShown] = useState(false);

    return (
        &lt;div className="flex flex-col m-8 w-[300px]"&gt;
            &lt;h2 className="text-xl my-1"&gt;Demonstrating &lt;strong&gt;Named Export&lt;/strong&gt;&lt;/h2&gt;
            &lt;button
                className="bg-slate-600 text-white rounded p-1"
                onClick={() =&gt; setShown(!shown)}
            &gt;
                Load 🦮 Spike&amp;apos;s Story
            &lt;/button&gt;

            {shown &amp;&amp; &lt;LazySpike /&gt;}
        &lt;/div&gt;
    );
}

export default SpikeStory;
</code></pre>
<p>像 <code>tom-story</code> 一样，我们使用了 next/dynamic 的动态导入。但是让我们来深入分析一下上面的代码块：</p>
<pre><code>const LazySpike = dynamic(() =&gt; import("./spike").then((mod) =&gt; mod.LazySpike), {
    loading: () =&gt; &lt;h1&gt;加载 Spike 的故事...&lt;/h1&gt;,
});
</code></pre>
<p>你会注意到的变化是我们使用 <code>.then()</code> 处理函数明确地从 <code>import("./spike")</code> 函数中解析了 promise。我们首先获取模块，然后通过其实际名称选择导出的组件——在这种情况下是 <code>LazySpike</code>。其余的事情与 <code>tom-story</code> 中的情况保持不变。</p>
<p>现在，为了测试它，再次将组件导入到 <code>page.js</code> 文件中，并像前两次那样在 JSX 中使用它。</p>
<pre><code class="language-javascript">// page.js

import TomStory from "./components/tom/tom-story";
import JerryStory from "./components/jerry/jerry-story";
import SpikeStory from "./components/spike/spike-story"; 
</code></pre>
<p>那里 – 你应该会在浏览器上看到新组件，并带有一个按钮，从尚未加载的 <code>spike.jsx</code> 文件中加载 Spike 的故事。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/07/Screenshot-2024-07-17-at-9.59.55-AM.png" alt="图片" width="600" height="400" loading="lazy"> <em>Lazy 加载 Spike 的故事的按钮</em></p>
<p>点击按钮将加载文件到浏览器，并渲染组件来展示 Spike 的故事。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/07/Screenshot-2024-07-17-at-10.02.01-AM.png" alt="图片" width="600" height="400" loading="lazy"> <em>Spike 的故事已懒加载</em></p>
<p>下面你可以看到所有三个组件侧并肩展示三种不同的技术和使用案例。你可以一起测试它们。下面的图片展示了两个组件并行地懒加载，而另一个组件已经被懒加载了。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/07/Screenshot-2024-07-17-at-10.14.21-AM.png" alt="图片" width="600" height="400" loading="lazy"> <em>并行懒加载多个组件</em></p>
<p>这是另一个案例，通过点击各自的按钮，所有三个组件都按需懒加载了。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/07/Screenshot-2024-07-17-at-10.05.35-AM-1.png" alt="图片" width="600" height="400" loading="lazy"> <em>所有的故事都被懒加载</em></p>
<h2 id="">懒加载你的服务器组件</h2>
<p>我们谈到了客户端组件的懒加载技术。我们是否也可以对服务器组件使用相同的技术呢？其实你可以，但不需要，因为服务器组件已经是 <code>代码分割</code> 的，加载方面已经由 Next.js 处理好了。如果你尝试这样做也不会出现任何类型的错误，但这是不必要的。</p>
<p>如果你动态导入一个拥有一个或多个客户端组件作为子组件的服务器组件，那些客户端组件将被懒加载。但对于（父）服务器组件本身不会有任何影响。</p>
<p>下面是一个包含两个客户端组件作为子组件的服务器组件的示例：</p>
<pre><code class="language-jsx">// server-comp.jsx

import ComponentA from "./a-client-comp";
import ComponentB from "./b-client-comp";

import React from 'react'

const AServerComp = () =&gt; {
  return (
    &lt;div className="flex flex-col m-8 w-[300px]"&gt;
      &lt;ComponentA /&gt;
      &lt;ComponentB /&gt;
    &lt;/div&gt;
  )
}

export default AServerComp
</code></pre>
<p>现在，我们将动态导入服务器组件到 <code>page.js</code> 文件中并在 JSX 中使用它。动态导入服务器组件的子客户端组件将被懒加载，但服务器组件本身不会。</p>
<pre><code class="language-jsx">// page.js

import dynamic from "next/dynamic";

import TomStory from "./components/tom/tom-story";
import JerryStory from "./components/jerry/jerry-story";
import SpikeStory from "./components/spike/spike-story";

const AServerComp = dynamic(() =&gt; import('./components/server-comps/server-comp'), {
  loading: () =&gt; &lt;h1&gt;Loading Through Server Component...&lt;/h1&gt;,
})


export default function Home() {
  return (
    &lt;div className="flex flex-wrap justify-center "&gt;
      &lt;TomStory /&gt;
      &lt;JerryStory /&gt;
      &lt;SpikeStory /&gt;

      &lt;AServerComp /&gt;
    &lt;/div&gt;
  );
}
</code></pre>
<h2 id="nextjs">我们应该在 Next.js 中懒加载所有客户端组件吗</h2>
<p>当我第一次开始学习懒加载时，我也有这个问题。现在我对这项技术有了更多的了解，这是我的看法：</p>
<p>你并不需要懒加载所有的客户端组件。优化是很好的，但过度优化可能会有反作用。你需要识别哪些地方需要这些优化。</p>
<ul>
<li>你的客户端组件是否真的很笨重？</li>
<li>你是否不必要地把很多东西放进一个组件，你应该拆分并重构它吗？</li>
<li>你是否在你的客户端组件中导入了沉重的库？</li>
<li>你选择了 tree-shaking 吗？</li>
<li>你是否可以按路由标记笨重的客户端组件，并且没有问题在该路由的页面初始加载时不加载它们中的一些或全部？</li>
</ul>
<p>如你所见，这些是在你开始优化前需要问的几个有意义的问题。一旦你有了答案，并决定需要懒加载，那么你可以实现你从这篇文章中学到的技术。</p>
<h2 id="">接下来是什么</h2>
<p>暂时就这些了。你喜欢阅读这篇文章并学到了一些新东西吗？如果是这样，我很想知道内容是否对你有帮助。以下是我的社交媒体账号。</p>
<p>接下来，如果你愿意学习 <code>Next.js</code> 及其生态系统如 <code>Next-Auth(V5)</code> 的基本概念和项目，我有个好消息给你：你可以在我的 <a href="https://www.youtube.com/watch?v=VSB2h7mVhPg&amp;list=PLIJrr73KDmRwz_7QUvQ9Az82aDM9I8L_8">YouTube</a> 频道上查看这个播放列表，目前有 20+ 个视频教程和超过 11 小时的精彩内容，免费提供。我希望你也喜欢这些内容。</p>
<p>欢迎和我联系。</p>
<ul>
<li>订阅我的 <a href="https://www.youtube.com/tapasadhikary?sub_confirmation=1">YouTube 频道</a>。</li>
<li><a href="https://twitter.com/tapasadhikary">在 X (Twitter)</a> 或者 <a href="https://www.linkedin.com/in/tapasadhikary/">LinkedIn</a> 上关注我，不要错过每日技能提升的推文。</li>
<li>查看并关注我在 <a href="https://github.com/atapas">GitHub</a> 上的开源作品。</li>
<li>我定期在我的 <a href="https://blog.greenroots.info/">GreenRoots 博客</a> 上发布有意义的文章，你可能也会发现它们对你有帮助。</li>
</ul>
<p>下篇文章见。不管怎样，请照顾好自己，并保持学习。</p>
<!--kg-card-end: markdown--> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 如何编写出整洁的代码——开发者指南与示例 ]]>
                </title>
                <description>
                    <![CDATA[ 试想一下，一个房间里衣服、书籍和各种物品散落得到处都是。在这样的凌乱环境里，你觉得要找到一样东西会不会很困难呢？ 现在来想一想，当我们写出乱糟糟的代码时，它是不是也会使人感到困惑，甚至比凌乱的房间还要让人头疼呢！ 相反，简洁的代码就像一个井然有序的房间：你可以轻松找到所需的内容，和快速理解正在发生的事情，使你更快地完成工作。 现在，我们来瞧瞧这个图表。该图表呈现并对照了两种不同的代码编写方式，以及它们对添加更多代码行所需要时间的影响：  1. ⚠️ 仓促 & 混乱的代码     （红线）：这种代码是在没有规划和整理的情况下匆忙写出的代码。起初，这种方式看起来高效，但随着代码行数增加，代码结构变得越来越难以理解和维护。因此，随着时间推移，每添加一行代码所需的时间都会越来越长。            2. ⚡ 谨慎 & 整洁的代码     （蓝线）：这种代码是经过精心规划和整理的，使其更容易理解和修改。虽然一开始可能会稍微慢一些，但随着代码的增长，它依然保持清晰易读，因此添加新代码不会变得越来越困难。           简单来说，编写整洁的代码一开始可能会缓慢一些，但从长远 ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/how-to-write-clean-code-tips-for-developers/</link>
                <guid isPermaLink="false">67dbea8e66481204770e045c</guid>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Qingfeng Huang ]]>
                </dc:creator>
                <pubDate>Thu, 20 Mar 2025 10:18:11 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2025/03/5cb4369b-9f2e-4f97-9765-ac00a62800c6.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>原文：</strong> <a href="https://www.freecodecamp.org/news/how-to-write-clean-code-tips-for-developers/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">How to Write Clean Code – Tips for Developers with Examples</a>
      </p><!--kg-card-begin: markdown--><p>试想一下，一个房间里衣服、书籍和各种物品散落得到处都是。在这样的凌乱环境里，你觉得要找到一样东西会不会很困难呢？</p>
<p>现在来想一想，当我们写出乱糟糟的代码时，它是不是也会使人感到困惑，甚至比凌乱的房间还要让人头疼呢！</p>
<p>相反，简洁的代码就像一个井然有序的房间：你可以轻松找到所需的内容，和快速理解正在发生的事情，使你更快地完成工作。</p>
<p>现在，我们来瞧瞧这个图表。该图表呈现并对照了两种不同的代码编写方式，以及它们对添加更多代码行所需要时间的影响：</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1730728342241/3fa8f5a1-0af4-4ffd-aa3a-bb70001b026d.png" alt="3fa8f5a1-0af4-4ffd-aa3a-bb70001b026d" width="600" height="400" loading="lazy"></p>
<ol>
<li>
<p>⚠️ <strong>仓促 &amp; 混乱的代码</strong>（红线）：这种代码是在没有规划和整理的情况下匆忙写出的代码。起初，这种方式看起来高效，但随着代码行数增加，代码结构变得越来越难以理解和维护。因此，随着时间推移，每添加一行代码所需的时间都会越来越长。</p>
</li>
<li>
<p><strong>⚡ 谨慎 &amp; 整洁的代码</strong>（蓝线）：这种代码是经过精心规划和整理的，使其更容易理解和修改。虽然一开始可能会稍微慢一些，但随着代码的增长，它依然保持清晰易读，因此添加新代码不会变得越来越困难。</p>
</li>
</ol>
<p>简单来说，编写整洁的代码一开始可能会缓慢一些，但从长远来看，它能节省后续所耗费的时间，让工作更轻松。同时，还能提高软件的可靠性，打造更优质的产品。</p>
<p>编写整洁的代码是专业开发人员培养的一种良好习惯，体现了专业人员对质量的承诺和坚定的职业操守。在本文中，我将带领你了解一些保持代码整洁的最佳实践。</p>
<h3 id="">我们将涵盖的内容</h3>
<ol>
<li>
<p><a href="#heading-1-use-meaningful-names">使用有意义的名称</a></p>
</li>
<li>
<p><a href="#heading-2-follow-the-single-responsibility-principle-srp">遵循单一责任原则 (SRP)</a></p>
</li>
<li>
<p><a href="#heading-3-avoid-unnecessary-comments">避免不必要的注释</a></p>
</li>
<li>
<p><a href="#heading-4-make-your-code-readable">提高代码可读性</a></p>
</li>
<li>
<p><a href="#heading-5-write-unit-tests">编写单元测试</a></p>
</li>
<li>
<p><a href="#heading-6-be-careful-with-dependencies">谨慎处理依赖项</a></p>
</li>
<li>
<p><a href="#heading-7-organize-your-project">合理组织项目结构</a></p>
</li>
<li>
<p><a href="#heading-8-use-consistent-formatting">保持一致的代码格式</a></p>
</li>
<li>
<p><a href="#heading-9-avoid-hardcoding-values">避免硬编码</a></p>
</li>
<li>
<p><a href="#heading-10-limit-function-length">限制函数长度</a></p>
</li>
<li>
<p><a href="#heading-conclusion">总结</a></p>
</li>
</ol>
<h2 id="10">编写整洁代码的 10 个实用技巧</h2>
<p>为了帮助你开启编写整洁代码的旅程，这里为你提供了 10 个实用技巧，可以帮助你保持代码的可读性、条理性和高效性。</p>
<h3 id="1">1. 使用有意义的名称</h3>
<p>在为变量、函数和类命名时，选择能清晰描述其用途的名称。</p>
<p>不要简单地命名变量为 <code>b</code>，试着用 <code>numberOfUsers</code>这样的名称。这样，任何阅读你的代码的人都能毫不费力地理解其用途，而无需额外注释。一个有意义的名称可以减少猜测，避免产生混淆。</p>
<p><strong>示例</strong>：</p>
<pre><code>// 正确的例子
let numberOfUsers = 5; // 清晰易懂

// 错误的示范
let b = 5; // 含糊不清
</code></pre>
<p><strong>💡 命名技巧</strong></p>
<ul>
<li>
<p><strong>变量</strong>：使用描述数据的名词，例如 <code>userAge</code> 或 <code>totalAmount</code>。</p>
</li>
<li>
<p><strong>函数</strong>：使用动词，如 <code>calculateTotal()</code> 或 <code>fetchUserData()</code>。</p>
</li>
<li>
<p><strong>类</strong>：使用单数名词来表示其含义，如 <code>User</code> 或 <code>Order</code> 来表示它们的类型。</p>
</li>
</ul>
<pre><code>// 变量：描述其存储的数据
let userAge = 25;

// 函数：使用动词来描述其功能
function calculateTotal(price, quantity) {
    return price * quantity;
}

// 类：单数名词，表示一种对象类型
class User {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }
}
</code></pre>
<h3 id="2srp">2. 遵循单一责任原则（SRP）</h3>
<p><strong>单一职责原则</strong>意味着每个函数或方法都应该<strong>只负责一项特定的任务</strong>。</p>
<p>这样可以让你的函数保持简洁、专注，从而提高代码的可读性，有便于后续的测试和维护。</p>
<p>想象一下一个工具箱，其中每个工具都有其独特的用途——干净的代码函数也应如此运作。</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1730728643183/77666f78-7ec9-4a5c-af4f-253e6de4acac.jpeg" alt="77666f78-7ec9-4a5c-af4f-253e6de4acac" width="600" height="400" loading="lazy"></p>
<p>例如，如果你有一个名为 <code>calculateTotal</code> 的函数，它就只应该负责计算总和。如果你给它添加额外的任务，可能会导致代码变得混乱，难以维护。</p>
<p>下面是一个示范例子，展示了为什么保持函数的单一职责很重要：</p>
<p>假定您期望计算一个总和并返回一个带有额外信息的对象，诸如谁进行了计算，以及何时计算的。与其直接将这些内容添加到 <code>calculateTotal</code> 中，不如我们创建第二个函数来处理额外的信息。</p>
<ol>
<li>
<p><strong>优良范例（任务分离）</strong></p>
<pre><code> // 此函数仅用于计算总和
 function calculateTotal(a, b) {
     return a + b;
 }

 // 此函数用于创建包含额外信息的对象
 function createCalculationRecord(a, b, user) {
     let sum = calculateTotal(a, b); // 调用上面的计算总和函数
     return {
         user: user,
         total: sum,
         timestamp: new Date()
     };
 }

 let record = createCalculationRecord(5, 10, "Shahan");
 console.log(record);
</code></pre>
</li>
</ol>
<p><strong>👍 为什么这样更好</strong>：这样使每个函数都有一个明确且集中的任务。其中 <code>calculateTotal</code> 只需负责数学运算，而 <code>createCalculationRecord</code> 则负责添加额外的细节。如果你想更改总数的计算方式，只需更新 <code>calculateTotal</code>函数；如果你想改变记录格式，只需更新 <code>createCalculationRecord</code>的函数。</p>
<ol start="2">
<li>
<p><strong>错误示例（在一个函数中混合多个任务）</strong></p>
<pre><code> // 此函数在一个步骤中同时计算总和创建对象
 function calculateTotalAndReturnRecord(a, b, user) {
     let sum = a + b;
     return {
         user: user,
         total: sum,
         timestamp: new Date()
     };
 }

 let record = calculateTotalAndReturnRecord(5, 10, "Shahan");
 console.log(record);
</code></pre>
</li>
</ol>
<p><strong>👎 为什么这样不好</strong>：函数名称 <code>calculateTotalAndReturnRecord</code> 表明它试图完成多项任务。如果你只想使用计算部分，就无法在不涉及记录部分的情况下复用这个函数。此外，单独更新和测试每一项任务也会更困难。</p>
<h3 id="3">3. 避免不必要的注释</h3>
<p>一个良好的代码应该能够具有自身的解释性，而不需要过多的注释来解释。而专注于编写清晰且易于理解的代码，便使其本身就能表达出意图。</p>
<p>注释在解释复杂逻辑或特殊实现时很有用，但同时过多的注释会使代码显得杂乱无章，难以阅读和理解。</p>
<p><strong>💬 什么时候使用注释</strong>：</p>
<ul>
<li>
<p>用于说明为什么要以特定方式进行操作。</p>
</li>
<li>
<p>在处理复杂的算法或计算时。</p>
</li>
<li>
<p>添加有关潜在局限性的注释。</p>
</li>
</ul>
<p><strong>示例</strong>：</p>
<pre><code>// 命名清晰，无需注释
let userAge = 25;

// 命名不清晰，需要注释
let a; // 用户的年龄
</code></pre>
<h3 id="4">4. 提高代码可读性</h3>
<p>可读性良好的代码使用<strong>缩进</strong>、<strong>换行</strong>和<strong>空格</strong>来保持整洁和有序。</p>
<p>可以把它想象成写一篇故事文章：段落的划分能让阅读变得更轻松，同样，在代码中，合理的换行可以起到相同的作用，使其更易于理解。</p>
<p><strong>示例</strong>：</p>
<pre><code>// 优良范例的代码结构
if (isLoggedIn) {
    console.log("欢迎回来!");
} else {
    console.log("请登入账号。");
}

// 错误示例的代码结构
if(isLoggedIn){console.log("欢迎回来!");}else{console.log("请登入账号。");}
</code></pre>
<p>在 VS Code 中，<strong>Prettier</strong> 和 <strong>Black</strong> 是常见的代码格式化工具，可以自动为多种编程语言应用整洁的代码风格。</p>
<p><strong>PyCharm</strong> 和 <strong>IntelliJ</strong> 具备强大的内置格式化功能，并支持自定义规则，例如 Python 中的 PEP 8 代码风格指南和其他标准规范。这些工具可以帮助项目保持一致的代码格式，减少手动调整的工作量，从而提高代码的可读性和维护性。</p>
<h3 id="5">5. 编写单元测试</h3>
<p>单元测试有助于确保代码的每个部分按预期运行。</p>
<p>通过测试小的、独立的部分（比如函数），你可以尽早发现错误，并防止其蔓延到代码的其他部分。</p>
<p>具体来说，单元测试实际上是对你代码的每个部分所进行的小型质量检查，从而确保它们按预期来工作。</p>
<p><strong>🍎 现实中的案例：</strong></p>
<p>让我们来看一下，如何测试具有多个方法的复杂 JavaScript 对象，以 <code>Calculator</code> 类为例。</p>
<p>这种方法将帮助你明白，为何要让每个方法专注于一项任务，并通过单元测试确保每个方法都能正确运行。</p>
<p>这里有一个 <code>Calculator</code> 类，其中包含执行基本算术运算的方法：加法、减法、乘法和除法。</p>
<pre><code>class Calculator {
    constructor() {
        this.result = 0;
    }

    add(a, b) {
        return a + b;
    }

    subtract(a, b) {
        return a - b;
    }

    multiply(a, b) {
        return a * b;
    }

    divide(a, b) {
        if (b === 0) throw new Error("无法除以零");
        return a / b;
    }
}
</code></pre>
<p>如你所见，每个方法都执行一个特定的操作。其中，<code>divide</code> 方法有额外的逻辑来处理除以零的情况，否则这会导致错误。</p>
<p>现在，我们将编写单元测试来验证每个方法是否按预期工作。🔬</p>
<p><strong>🧪 为每个方法编写单元测试</strong></p>
<p>为了测试我们的 <code>Calculator</code> 类，我们可以编写单元测试，涵盖正常情况和边缘情况。以下是如何为每个方法设置测试的示例：</p>
<pre><code>// 初始化 Calculator 实例
const calculator = new Calculator();

// 测试 add 方法
console.assert(calculator.add(2, 3) === 5, '测试失败: 2 + 3 应该等于 5');
console.assert(calculator.add(-1, 1) === 0, '测试失败: -1 + 1 应该等于 0');

// 测试 subtract 方法
console.assert(calculator.subtract(5, 3) === 2, '测试失败: 5 - 3 应该等于 2');
console.assert(calculator.subtract(0, 0) === 0, '测试失败: 0 - 0 应该等于 0');

// 测试 multiply 方法
console.assert(calculator.multiply(2, 3) === 6, '测试失败: 2 * 3 应该等于 6');
console.assert(calculator.multiply(-1, 2) === -2, '测试失败: -1 * 2 应该等于 -2');

// 测试 divide 方法
console.assert(calculator.divide(6, 3) === 2, '测试失败: 6 / 3 应该等于 2');
try {
    calculator.divide(1, 0);
    console.assert(false, '测试失败: 除以零应该抛出错误');
} catch (e) {
    console.assert(e.message === "无法除以零", '测试失败: 除以零的错误信息不正确');
}
</code></pre>
<p><strong>🫧 测试解释：</strong></p>
<ol>
<li>
<p><strong>加法</strong>（<code>add</code> 方法）：我们测试 <code>add(2, 3)</code> 返回 <code>5</code>，以及 <code>add(-1, 1)</code> 返回 <code>0</code>。如果这些测试通过，就说明我们的加法逻辑是正确的。</p>
</li>
<li>
<p><strong>减法</strong>（<code>subtract</code> 方法）：我们验证 <code>subtract(5, 3)</code> 返回 <code>2</code>，以及 <code>subtract(0, 0)</code> 返回 <code>0</code>。这些检查确保减法计算准确无误。</p>
</li>
<li>
<p><strong>乘法</strong>（<code>multiply</code> 方法）：我们测试乘法函数，包括正数和负数的情况，确保 <code>multiply(2, 3)</code> 返回 <code>6</code>，以及 <code>multiply(-1, 2)</code> 返回 <code>-2</code>。</p>
</li>
<li>
<p><strong>除法</strong>（<code>divide</code> 方法）：我们验证 <code>6</code> 除以 <code>3</code> 返回 <code>2</code>。对于除以零的情况，我们使用 <code>try...catch</code> 块来确保方法抛出正确的错误消息。这可以验证该方法是否能正确处理错误情况。</p>
</li>
</ol>
<p>你可以看到，如果任何方法失败，测试都会生成清晰的错误消息，使我们能够迅速识别并修复问题。单独测试方法有助于我们在项目发展过程中尽早发现错误，并保持可靠、整洁的代码。</p>
<h3 id="6">6. 谨慎处理依赖项</h3>
<p>依赖项，是指你的代码所依赖的外部软件组件。🔌</p>
<p>想象一下，您正在构建一个能发送电子邮件的网络应用程序。与其自己编写邮件发送功能，你可以使用像 <a href="https://nodemailer.com/"><strong>Nodemailer</strong></a> 这样的外部库。在这种情况下，Nodemailer就是一个<strong>依赖项</strong>——你的应用依赖它来处理邮件发送功能。</p>
<p><strong>示例：</strong></p>
<pre><code class="language-javascript">const nodemailer = require('nodemailer');

function sendEmail(to, subject, message) {
    const transporter = nodemailer.createTransport({
        service: 'gmail',
        auth: {
            user: 'your-email@gmail.com',
            pass: 'your-email-password'
        }
    });

    const mailOptions = {
        from: 'your-email@gmail.com',
        to: to,
        subject: subject,
        text: message
    };

    return transporter.sendMail(mailOptions);
}
</code></pre>
<p>在这段代码中，<code>nodemailer</code> 被引入并用于创建一个邮件发送的传输器。如果没有它，你将不得不从零编写整个邮件发送功能，这将非常复杂且耗时。通过使用 Nodemailer 作为依赖项，你的应用可以轻松实现邮件发送功能。</p>
<p>尽管依赖项非常有用，但过度依赖外部软件或库可能会带来问题。因此，只有当它们能简化你的工作或提供重要功能时，才应该使用依赖项。</p>
<p>有效管理依赖是编写简洁代码的关键。以下是一些提示：</p>
<ul>
<li>
<p><strong>限制依赖项</strong>：仅包含对项目真正必要的库或模块，避免不必要的依赖项。</p>
</li>
<li>
<p><strong>保持版本更新</strong>：使用最新版本的库，以减少安全风险。</p>
</li>
<li>
<p><strong>分离核心逻辑</strong>：尽可能自己编写核心功能，这样即使未来需要移除某个依赖，也不会影响你代码的正常运行。</p>
</li>
</ul>
<p>让我用之前的 Nodemailer 代码来给你举个例子，说明如何在代码中实现逻辑分离。</p>
<p>你可以创建一个封装函数，将发送电子邮件的细节抽象出来。这样，你就可以更改底层的电子邮件服务或移除对 Nodemailer 的依赖，而不影响到代码的其他部分。</p>
<p>以下是如何组织代码以实现此目的：</p>
<pre><code class="language-javascript">const nodemailer = require('nodemailer');

// 用于发送电子邮件的核心函数
function sendEmail(to, subject, message) {
    const transporter = createTransporter();
    const mailOptions = createMailOptions(to, subject, message);
    return transporter.sendMail(mailOptions);
}

// 创建邮件发送器的函数
function createTransporter() {
    return nodemailer.createTransport({
        service: 'gmail',
        auth: {
            user: 'your-email@gmail.com',
            pass: 'your-email-password'
        }
    });
}

// 创建邮件选项的函数
function createMailOptions(to, subject, message) {
    return {
        from: 'your-email@gmail.com',
        to: to,
        subject: subject,
        text: message
    };
}

// 示例用法
sendEmail('recipient@example.com', 'Test Subject', 'Hello, this is a test email.')
    .then(() =&gt; {
        console.log('Email sent successfully!');
    })
    .catch((error) =&gt; {
        console.error('Error sending email:', error);
    });
</code></pre>
<p><strong>🗝️ 关键要点：</strong></p>
<ol>
<li>
<p><strong>核心函数</strong>：<code>sendEmail</code>、<code>createTransporter</code> 和 <code>createMailOptions</code> 的函数是相互独立的，这样你就可以修改其中一个而不影响其他函数。</p>
</li>
<li>
<p><strong>轻松修改</strong>：如果您希望在将来切换到其它电子邮件服务，你只需修改 <code>createTransporter</code> 的函数即可。</p>
</li>
<li>
<p><strong>可维护性</strong>：这种结构使你的代码更易于维护和理解。</p>
</li>
</ol>
<h3 id="7">7. 合理组织项目结构</h3>
<p>一个良好的项目结构与代码本身同样重要。</p>
<p>可以这样想，就像整理你的工作空间一样——你需要为每样东西指定位置，这样就能轻松找到它们。对于编码项目，应该为不同的部分创建专门的文件夹，例如 <code>components</code>、<code>utils</code> 和 <code>services</code>等格式。</p>
<p><strong>📂 如何组织你的项目</strong></p>
<p>为了建立一个整洁有序的项目，你应当将代码的不同部分的分类放入指定的文件夹中。以下是一个组织良好的项目结构的简单示例：</p>
<pre><code>myProject # 我的项目
├── src # 资源文件夹
│   ├── components # 组件文件夹
│   ├── services # 服务文件夹
│   ├── utils # 实用工具文件夹
└── tests # 测试文件夹
</code></pre>
<h4 id="">项目结构解析：</h4>
<ol>
<li>
<p><strong>myProject（我的项目）</strong>：这是你项目的根文件夹。包含与您的应用所有相关的内容。</p>
</li>
<li>
<p><strong>src（资源）</strong>：此文件夹包含您项目的所有源代码。您大部分时间都将在这里编码。</p>
</li>
<li>
<p><strong>components（组件）</strong>：此子文件夹用于存放可复用的 UI 组件。例如，如果你正在构建网络应用程序，你可能会在这里存放按钮、导航栏、表单等组件的单独文件。每个组件都应有独立的文件，以保持模块化。</p>
<ul>
<li><code>components</code> 中的示例结构：</li>
</ul>
</li>
</ol>
<pre><code>    components
    ├── Button.js # 按钮组件文件
    ├── Header.js # 头部导航组件文件
    └── Form.js # 表单组件文件
</code></pre>
<ol start="4">
<li>
<p><strong>services（服务）</strong>：此文件夹用于存放执行特定任务或处理业务逻辑的函数。例如，如果你的应用涉及发送邮件，你可以在这里创建一个专门的文件来管理所有与邮件相关的功能。</p>
<ul>
<li><code>services</code> 中的示例结构：</li>
</ul>
</li>
</ol>
<pre><code>    services
    ├── emailService.js # 邮件服务文件
    ├── userService.js # 用户服务文件
    └── productService.js # 产品服务文件
</code></pre>
<ol start="5">
<li>
<p><strong>utils（实用工具）</strong>：在这里，你可以放置一些可以跨项目使用的辅助函数。这些通常包括格式化日期、验证输入或其他不属于特定组件或服务的通用任务。</p>
<ul>
<li><code>utils</code> 目录内的示例结构：</li>
</ul>
</li>
</ol>
<pre><code>    utils
    ├── formatDate.js # 格式化日期文件
    ├── validateEmail.js # 验证邮箱格式文件
    └── generateId.js # 生成客户ID号码文件
</code></pre>
<ol start="6">
<li>
<p><strong>tests（测试）</strong>：此文件夹专用于存放测试文件。保持测试的有序排列有助于确保在构建新功能时，你可以轻松地测试它们，而无需在代码库中四处查找相关文件。</p>
<ul>
<li><code>tests</code> 目录内的示例结构：</li>
</ul>
</li>
</ol>
<pre><code>    tests
    ├── emailService.test.js # 测试邮件服务的功能
    ├── userService.test.js # 测试用户服务的功能
    └── component.test.js # 测试 UI 组件的功能
</code></pre>
<p><strong>📨 真实案例：使用 Nodemailer 进行邮件发送</strong></p>
<p>假设你正在构建一个向用户发送电子邮件的应用程序。你可以按照以下结构组织项目：</p>
<pre><code>myEmailApp # 我的邮件应用程序
├── src # 资源文件夹
│   ├── components # 存放组件的文件夹
│   │   ├── EmailForm.js # 邮件表单组件
│   │   └── SuccessMessage.js # 邮件发送成功消息的组件
│   ├── services # 存放服务组件的文件夹
│   │   └── emailService.js # 处理邮件服务功能的组件
│   ├── utils # 存放工具组件的文件夹
│   │   └── validateEmail.js  # 验证邮箱格式的组件
└── tests # 存放测试组件的文件夹
    ├── emailService.test.js # 测试邮件服务的组件
    └── EmailForm.test.js # 测试邮件表单组件
</code></pre>
<ul>
<li>
<p><strong>EmailForm.js</strong>：此组件负责处理发送电子邮件的用户界面，例如收件人、主题和邮件正文的输入字段。</p>
</li>
<li>
<p><strong>SuccessMessage.js</strong>：这个组件在邮件发送成功后显示一条成功消息。</p>
</li>
<li>
<p><strong>emailService.js</strong>：该服务包含使用 Nodemailer 发送电子邮件的逻辑，使你的代码保持模块化和清晰。</p>
</li>
<li>
<p><strong>validateEmail.js</strong>：一个实用的工具函数，用于检查电子邮件地址格式是否正确。</p>
</li>
<li>
<p><strong>tests</strong>：该文件夹用于编写测试代码，以确保你的电子邮件服务和组件按预期运行。</p>
</li>
</ul>
<p><strong>🍱 良好项目组织的优势</strong></p>
<ol>
<li>
<p><strong>易于导航</strong>：查看你的项目的任何人都可以快速了解，在哪里找到特定部分的代码，帮助理解其结构。</p>
</li>
<li>
<p><strong>更好的协作</strong>：如果你与他人合作，清晰的结构可以帮助团队成员明确各自的贡献范围，避免相互干扰。</p>
</li>
<li>
<p><strong>良好的扩展性</strong>：随着项目的发展，保持清晰的结构有助于管理复杂性，并保持代码库的整洁。</p>
</li>
<li>
<p><strong>提高可维护性</strong>：当你需要更新或修复某些内容时，可以快速找到相关文件，从而节省时间并减少错误。</p>
</li>
</ol>
<h3 id="8">8. 保持一致的代码格式</h3>
<p>一致的代码格式 有助于提高可读性，使代码更加清晰、有条理。</p>
<p>为你的项目确立一种编写代码的模式，例如使用两个空格缩进，或者在注释前始终包括一个换行符。</p>
<p>遵循一致的代码格式可以让代码更易读、更易维护，并提升项目的整体质量。</p>
<p><strong>🛠️ 代码格式化工具</strong></p>
<ul>
<li>
<p><a href="https://prettier.io/"><strong>Prettier</strong></a>：基于一套规则自动格式化代码，确保代码风格一致。<a href="https://www.freecodecamp.org/news/how-to-use-prettier-in-visual-studio-code/">这里有一个教程</a>，其中讲解如何在 VSCode 中设置和使用 Prettier，从而来帮助提高你的代码的可读性。</p>
</li>
<li>
<p><a href="https://eslint.org/"><strong>ESLint</strong></a>：用于强制执行编码规范，突出潜在问题。<a href="https://www.freecodecamp.org/news/how-to-set-up-eslint-prettier-stylelint-and-lint-staged-in-nextjs/#heading-set-up-eslint">这里有一个教程</a>，其中包含关于为你的项目设置 ESLint 的实用且深入的部分。</p>
</li>
</ul>
<h3 id="9">9. 避免硬编码</h3>
<p>硬编码指的是，直接在代码中嵌入数据值，例如将用户 ID 设置为 <code>123</code> 而不是使用变量。</p>
<p>避免硬编码可以提高代码的复用性，减少手动修改的需求。相反，你应该将值存储在变量、常量或配置文件中，以便更灵活地调整和管理数据。</p>
<p>以下是硬编码可能导致问题的一个场景：：</p>
<pre><code>// 错误示例：硬编码的用户限制
function createUser(name) {
    let numberOfUsers = 100; // 硬编码值
    if (numberOfUsers &gt;= 100) {
        return '用户数量已达上限。';
    }
    // 创建用户的代码
    return '用户已创建。';
}
</code></pre>
<p>在这个错误示例中，<code>numberOfUsers</code> 被硬编码为 <code>100</code>。如果您想要更改用户限制，您必须在代码中找到并修改此值。如果这个值在多个地方被重复使用，那么修改起来会变得繁琐且容易出错。</p>
<h4 id=""><strong>🏗️ 使用常量的优化示例</strong></h4>
<p>现在，让我们对代码进行重构，使用常量来替代硬编码：</p>
<pre><code>// 良好示例：使用常量
const MAX_USERS = 100; // 将限制存储在常量中

function createUser(name) {
    let numberOfUsers = getCurrentUserCount(); // 从函数或数据库获取当前计数
    if (numberOfUsers &gt;= MAX_USERS) {
        return '用户数量已达上限。';
    }
    // 创建用户的代码
    return '用户已创建。';
}

// 获取当前用户数量的示例函数
function getCurrentUserCount() {
    // 模拟从数据库获取当前计数
    return 90; // 示例计数
}
</code></pre>
<p><strong>🥣 优化示例解析：</strong></p>
<ol>
<li>
<p><strong>使用常量</strong>：<code>MAX_USERS</code> 常量被定义在代码顶部。如果未来需要更改最大用户数，只需修改这一处，而不必在代码中四处查找并修改硬编码的值</p>
</li>
<li>
<p><strong>动态值</strong>：<code>getCurrentUserCount()</code> 函数会从数据库或其他数据源中动态获取当前的用户数量。这种方法避免了对数量进行硬编码，便于轻松更改。</p>
</li>
<li>
<p><strong>可维护性</strong>：将值存储在常量中，使你的代码更易于维护。如果业务需求发生变化，并且您需要将用户限制增加到 <code>150</code>，只需将 <code>MAX_USERS</code> 从 <code>100</code> 更改为 <code>150</code>，整个应用程序都会反映出这一更改。</p>
</li>
<li>
<p><strong>清晰性</strong>：为常量使用描述性的名称（如 <code>MAX_USERS</code>）可提高代码的可读性。任何查看您代码的人都能迅速明白这个值所代表的含义。</p>
</li>
</ol>
<p><strong>🤐 何时使用配置文件</strong></p>
<p>在大型应用程序中，你可能还会考虑使用配置文件（如 JSON、YAML 或环境变量）来存储在不同环境（开发、预发布、生产）之间可能会改变的值。</p>
<p>例如，在你的 <strong>config.json</strong> 文件中，你可以将 <code>maxUsers</code> 硬编码（但请注意，在 config.json 中，建议使用驼峰命名法，以保持格式一致性）：</p>
<pre><code>{
    "maxUsers": 100,
    "emailService": {
        "service": "gmail",
        "user": "your-email@gmail.com",
        "pass": "your-email-password"
    }
}
</code></pre>
<p><strong>🪴 在代码中使用配置文件：</strong></p>
<pre><code>const config = require('./config.json');

function createUser(name) {
    let numberOfUsers = getCurrentUserCount(); 
    if (numberOfUsers &gt;= config.maxUsers) {
        return 'User limit reached.';
    }
    // 代码以创建用户
    return 'User created.';
}
</code></pre>
<h3 id="10">10. 限制函数长度</h3>
<p>较长的函数难以理解和维护。</p>
<p>虽然没有严格的规定，但通常来说，函数的长度最好控制在 20 到 30 行以内。如果一个函数承担了多个职责或包含过多的步骤，这往往意味着它太长了。需将这些函数拆分成为较小的“辅助”函数，可以使代码更易管理和理解。</p>
<p>下面是一个冗长而复杂的函数示例：</p>
<pre><code>function updateCart(cart, item, discountCode) {
    // 将商品加入购物车
    cart.items.push(item);

    // 计算新的总价
    let total = 0;
    cart.items.forEach(cartItem =&gt; {
        total += cartItem.price * cartItem.quantity;
    });

    // 如果有折扣码则应用折扣
    if (discountCode) {
        total = applyDiscount(total, discountCode);
    }

    // 记录交易
    console.log(`Item added: ${item.name}, New total: $${total}`);

    return total;
}
</code></pre>
<p>⚠️ <strong>这个函数执行了多个任务：</strong></p>
<ol>
<li>
<p>将商品加入购物车。</p>
</li>
<li>
<p>计算总价格。</p>
</li>
<li>
<p>如果有折扣码，则应用折扣。</p>
</li>
<li>
<p>记录交易信息。</p>
</li>
</ol>
<p>虽然该函数目前看起来可以管理，但如果继续增加任务，它会迅速变得难以调试和维护。</p>
<p>让我们将这个冗长的函数拆分为更小、单一职责的函数：</p>
<pre><code>function updateCart(cart, item, discountCode) {
    addItemToCart(cart, item); 
    let total = calculateTotal(cart);

    if (discountCode) {
        total = applyDiscount(total, discountCode);  
    }

    logTransaction(item, total); 
    return total;  
}

function addItemToCart(cart, item) {
    cart.items.push(item);
}

function calculateTotal(cart) {
    return cart.items.reduce((total, cartItem) =&gt; total + cartItem.price * cartItem.quantity, 0);
}

function logTransaction(item, total) {
    console.log(`Item added: ${item.name}, New total: $${total}`);
}
</code></pre>
<h4 id="">🧩 详细解释：</h4>
<ol>
<li>
<p><code>addItemToCart</code>：该函数仅负责将商品添加到购物车。它简单且目标明确，不包含任何额外逻辑。</p>
</li>
<li>
<p><code>calculateTotal</code>：函数计算购物车内所有商品的总价。这样代码更易读、易理解，如果你未来需要修改计算方式，只需调整这个函数即可。</p>
</li>
<li>
<p><code>logTransaction</code>：负责记录详细信息。如果你以后需要更改日志内容（例如，添加时间戳），只需修改该函数，而不会影响其他部分。</p>
</li>
<li>
<p><code>updateCart</code>：作为主函数，它现在更像是一个流程总结——先添加商品，然后计算总价、再应用折扣，最好记录交易结果。这种方式使代码一目了然，更清晰易懂。</p>
</li>
</ol>
<p><strong>📒 让我们总结一下限制函数长度的要点：</strong></p>
<ol>
<li>
<p><strong>🎯 专注于单一任务</strong>: 每个函数最好只执行一个任务。如果一个函数同时处理多个任务，不妨考虑将其拆分成更小的函数。。</p>
</li>
<li>
<p><strong>🩼 使用辅助函数</strong>: ：辅助函数是小型且专注的函数，用于执行特定任务并支持主函数的运行。例如，上述示例中的<code>addItemToCart</code>、<code>calculateTotal</code> 和 <code>logTransaction</code> 就是辅助函数。</p>
</li>
<li>
<p><strong>🪦 使用描述性命名</strong>: 函数名称应该能够清楚表达其功能（例如 <code>addItemToCart</code>），这样可以让代码自解释，减少对额外注释的依赖。</p>
</li>
</ol>
<h2 id="">整洁代码的最佳实践</h2>
<p>现在我们已经介绍了一些重要的技巧，现在让我们来看一下整洁代码背后的核心原则：</p>
<ol>
<li>
<p><strong>🎏 简单性</strong>: 始终让代码尽可能简单，避免不必要的复杂度。</p>
</li>
<li>
<p><strong>🧂 一致性</strong>: 保持代码在风格和结构上的统一性，使其更易读和维护。</p>
</li>
<li>
<p><strong>🌾 清晰性</strong>: 代码应该清楚地表达它的功能，无需额外的解释。</p>
</li>
<li>
<p><strong>⛽ 高效性</strong>: 在不牺牲可读性的前提下，编写优化的代码，以提升性能。</p>
</li>
</ol>
<p>这些原则表明，编写代码不仅仅是写代码，更是设计解决方案。整洁代码是一种需要不断练习的技能，所以请保持学习，不断提升自己！</p>
<p><strong>🔌 关于依赖项的说明</strong></p>
<p>与其直接在代码中硬编码依赖项，不如使用包管理工具来管理它们，如 <a href="https://www.npmjs.com/"><strong>npm</strong></a>（用于 JavaScript）或 <strong>pip</strong>（用于 Python）这样的包管理器来统一规划它们。这样，你可以轻松更新或移除依赖项，确保代码的灵活性和可维护性。</p>
<h3 id="">总结 🏁</h3>
<p>编写整洁的代码就像为房子打下坚实的地基。它能让整个项目井然有序，使你在项目扩展时轻松添加新功能或修复问题。</p>
<p>掌握这些技巧后，你可以逐步养成让代码更易读、更易维护的良好习惯，同时让编写代码变得更加高效且愉悦。</p>
<h3 id="">建议的下一步 📘</h3>
<p>如果你想在六个月内成为后端开发者，可以参考我的<a href="https://codewithshahan.gumroad.com/l/pcela">后端开发人员路线图</a>。该路线图旨在帮助初学者按照每周目标稳步学习，其中涵盖关键的技能、工具和技术。这个路线图可以让你的学习过程更加有条理、易坚持。</p>
<p><strong>你可以在</strong> <a href="https://x.com/shahancd"><strong>𝕏</strong></a> <strong>上关注我，以获取实时更新。</strong></p>
<p>期待我们的下次再见！</p>
<!--kg-card-end: markdown--> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 如何在 JavaScript 中使用模板字面量 ]]>
                </title>
                <description>
                    <![CDATA[ 模板字面量是 JavaScript 中的一项特性，于 ES6 中引入。它们为在 JavaScript 中处理字符串提供了一种更灵活和可维护的方式。 在本文结束时，你将了解如何使用模板字面量。你将学习语法、优点及一些使用案例。你还将了解一种更强大的特性，叫作标记模板字面量。 目录  * 什么是模板字面量  * 模板字面量与普通字符串的比较  * 模板字面量的一些使用案例  * 标记模板字面量  * 标记模板字面量示例  * 总结 什么是模板字面量 模板字面量是 JavaScript 中的一项特性，它让开发者以一种方便的方式处理字符串。你可以使用双引号 "" 或单引号 ''  表示普通字符串。但使用模板字面量时，你可以使用反引号 `` 来表示字符串。这让你可以在字符串中嵌入变量和表达式。 以下是一个例子： const website = 'freeCodeCamp' const message = `Welcome to ${website}` console.log(message) ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/template-literals-in-javascript/</link>
                <guid isPermaLink="false">66a06e7cdb861c043196e103</guid>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Miya Liu ]]>
                </dc:creator>
                <pubDate>Wed, 24 Jul 2024 05:10:11 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2024/07/JavaScript-template-literal---freecodecamp-1.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>原文：</strong> <a href="https://www.freecodecamp.org/news/template-literals-in-javascript/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">How to Use Template Literals in JavaScript</a>
      </p><!--kg-card-begin: markdown--><p>模板字面量是 JavaScript 中的一项特性，于 ES6 中引入。它们为在 JavaScript 中处理字符串提供了一种更灵活和可维护的方式。</p>
<!-- more -->
<p>在本文结束时，你将了解如何使用模板字面量。你将学习语法、优点及一些使用案例。你还将了解一种更强大的特性，叫作标记模板字面量。</p>
<h2 id="">目录</h2>
<ul>
<li>什么是模板字面量</li>
<li>模板字面量与普通字符串的比较</li>
<li>模板字面量的一些使用案例</li>
<li>标记模板字面量</li>
<li>标记模板字面量示例</li>
<li>总结</li>
</ul>
<h2 id="">什么是模板字面量</h2>
<p>模板字面量是 JavaScript 中的一项特性，它让开发者以一种方便的方式处理字符串。你可以使用双引号 <code>""</code> 或单引号 <code>''</code> 表示普通字符串。但使用模板字面量时，你可以使用反引号 <code>``</code> 来表示字符串。这让你可以在字符串中嵌入变量和表达式。</p>
<p>以下是一个例子：</p>
<pre><code class="language-javascript">const website = 'freeCodeCamp'
const message = `Welcome to ${website}`

console.log(message)
</code></pre>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2024/01/Screenshot-2024-01-03-at-6.57.20-AM.png" alt="模板字面量示例的输出" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>模板字面量示例的输出</figcaption>
</figure>
<p><code>message</code> 变量的值是一个模板字面量的例子。它包括用于定义模板字面量的反引号。它还包括用于在字符串中嵌入变量的 <code>${}</code> 语法。</p>
<h2 id="">模板字面量与普通字符串的比较</h2>
<p>现在让我们看看模板字面量与普通字符串有何不同，以及使用模板字面量的一些优点。</p>
<h3 id="">字符串连接</h3>
<p>在引入模板字面量之前，当你想连接字符串时，你必须使用加号 <code>+</code>。</p>
<pre><code class="language-javascript">const userName = 'Marie'
const balance = 10

// 使用普通字符串
const str1 = 'Hi ' + userName + ',' + ' your balance is ' + balance + '.'
console.log("Regular string: ", str1)

// 使用模板字面量
const str2 = `Hi ${userName}, your balance is ${balance}.`
console.log("Template literal: ", str2)
</code></pre>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2024/01/Screenshot-2024-01-03-at-7.07.37-AM.png" alt="普通字符串和模板字面量在这个示例中产生相同的输出" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>普通字符串和模板字面量在这个示例中产生相同的输出</figcaption>
</figure>
<p>注意，使用普通字符串时需要添加许多加号，并且还需要在正确的位置考虑空格。这在处理较长字符串时会难上加难。</p>
<p>在模板字面量的示例中，不需要添加任何加号。你可以将所有内容一起写成一个字符串。使用 <code>${}</code> 语法可以直接嵌入变量。</p>
<h3 id="">多行字符串</h3>
<p>处理多行字符串时，模板字面量也能让字符串的处理变得更简单。对于普通字符串，你不得不使用加号 <code>+</code> 和 <code>\n</code> 的组合来表示新行，但模板字面量不需要这些。</p>
<pre><code class="language-javascript">const regularString = 
'Hello there! \n' +
'Welcome to our website. \n' +
'How can we help you today?'

const templateLiteral = 
`Hello there!
Welcome to our website.
How can we help you today?`

console.log(regularString)
console.log(templateLiteral)
</code></pre>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2024/01/Screenshot-2024-01-03-at-7.44.20-AM.png" alt="多行字符串的普通字符串和模板字面量示例" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>多行字符串的普通字符串和模板字面量示例</figcaption>
</figure>
<p><code>regularString</code> 和 <code>templateLiteral</code> 变量产生相同的输出。模板字符串会自动识别空格和换行。</p>
<h3 id="">可读性和可维护性</h3>
<p>模板字面量还使你的代码更具可读性和更容易维护。正如你已经看到的，它们不需要使用加号 <code>+</code> 进行连接。你也无需担心转义引号。</p>
<p>这是一个例子：</p>
<pre><code class="language-javascript">const city = "Paris"
const str1 = 'She said, "I love ' + city + ', it\'s a beautiful place."'
const str2 = `She said, "I love ${city}, it's a beautiful place`

console.log(regularString)
console.log(templateLiteral)
</code></pre>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2024/01/Screenshot-2024-01-04-at-7.00.42-AM.png" alt="普通字符串和模板字面量示例的结果" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>普通字符串和模板字面量示例的结果</figcaption>
</figure>
<p>与模板字面量不同，普通字符串需要以下内容：</p>
<ul>
<li>使用加号 <code>+</code> 进行连接。</li>
<li>小心使用单引号和双引号。</li>
<li>需要在字符串中使用 <code>\</code> 转义单引号。</li>
</ul>
<h2 id="">模板字面量的一些实际使用案例</h2>
<p>到目前为止，你已经了解了什么是模板字面量，以及它们与普通字符串的比较。现在，让我们看一些实际示例。</p>
<p>你会经常看到模板字面量用于生成 HTML 标记。它们允许你将 JavaScript 表达式直接嵌入到 HTML 字符串中。这使得创建动态和数据驱动的内容变得容易。</p>
<p>例如：</p>
<pre><code class="language-javascript">const user = {
  name: "Marie",
  age: 25,
};

const userProfile = `
&lt;div&gt;
  &lt;h2&gt;Name: ${user.name}&lt;/h2&gt;
  &lt;p&gt;Age: ${user.age}&lt;/p&gt;
&lt;/div&gt;
`
</code></pre>
<p>注意 <code>${}</code> 语法如何允许你在字符串中直接运行 JavaScript 表达式。在此案例中，它被用来读取 <code>user</code> 对象属性的值。</p>
<h3 id="sql">创建动态 SQL 查询</h3>
<p>你还可以使用模板字面量通过直接将变量或表达式嵌入到查询字符串来创建动态 SQL 查询。这意味着你可以基于运行时值轻松创建查询。</p>
<p>例如：</p>
<pre><code class="language-javascript">const tableName = "users";
const columnName = "name";
const searchValue = "John";

const sqlQuery = 
  `SELECT * FROM ${tableName} WHERE ${columnName} = '${searchValue}'`
</code></pre>
<h3 id="">本地化和国际化</h3>
<p>模板字面量的另一个实际用途是在应用程序中处理本地化和国际化。有了模板字面量，你就可以直接在字符串中嵌入本地化内容的变量或语言键，从而更容易管理翻译。</p>
<p>例如：</p>
<pre><code class="language-javascript">const user = {
  name: "Marie",
};

const locale = "fr";
const greetings = {
  en: "Hello",
  es: "Hola",
  fr: "Bonjour"
};

const localizedGreeting = `${greetings[locale]} ${user.name}!`;
console.log(localizedGreeting)
</code></pre>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2024/01/Screenshot-2024-01-04-at-7.51.34-AM.png" alt="使用模板字面量本地化内容的示例" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>使用模板字面量本地化内容的示例</figcaption>
</figure>
<p>这个例子创建了一个 <code>localizedGreeting</code> 字符串而不依赖任何繁琐的连接。</p>
<h2 id="">标记模板字面量</h2>
<p>这是 JavaScript 模板字面量的一个功能，可以用于执行高级字符串操作。</p>
<p>要使用此功能，你需要一个标签函数和一个标签模板。</p>
<p>标签函数接收一个字符串和多个变量作为参数，然后根据你设置的某些条件或规则格式化它们。</p>
<p>然后，你通过将标签函数放在模板字面量的反引号前面来调用（或运行）该标签函数。</p>
<h3 id="">标记模板字面量的语法</h3>
<pre><code class="language-JavaScript">function taggedFunc(strings, ...values) {
  console.log(strings)
  console.log(values)
}

const name = 'Sarah'
const city = 'Rome'

taggedFunc`Hello ${name}. Welcome to ${city}.`
</code></pre>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2024/01/Screenshot-2024-01-04-at-10.03.12-AM.png" alt="记录标记模板字面量的字符串和值参数的结果" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>记录标记模板字面量的字符串和值参数的结果</figcaption>
</figure>
<p>这里有三点需要注意。</p>
<p>首先，第一个参数（本例中为 <code>strings</code>）始终是模板字面量中所有字符串块的数组。在这种情况下，它是下面的数组。</p>
<pre><code class="language-javascript">['Hello ', '. Welcome to ', '.']
</code></pre>
<p>接下来，其余参数将是所有模板字面量中的变量和评估表达式。JavaScript 的剩余参数语法 <code>...values</code> 使获取所有这些变得简单。</p>
<pre><code class="language-javascript">['Sarah', 'Rome']
</code></pre>
<p>最后，注意如何使用 <code>taggedFunc</code>。与常规函数不同，你不会用括号调用它，而是将其附加在模板字面量的第一个反引号之前。</p>
<pre><code class="language-javascript">taggedFunc`Hello ${name}. Welcome to ${city}.` ✅

taggedFunc(Hello ${name}. Welcome to ${city}.) ❌
</code></pre>
<h2 id="">标记模板字面量的实际例子</h2>
<p>现在让我们看看一些使用标记模板字面量处理字符串操作的实际例子。</p>
<h3 id="1">例子 1</h3>
<pre><code class="language-javascript">function receiptTag(strings, ...values) {  

  let finalString = ''
  for (let i = 0; i &lt; values.length; i++) {
    finalString += strings[i] + values[i]
  }

  // 添加最后一个字面量
  finalString += strings[strings.length - 1] 

  return finalString
}

const item = 'apple'
const price = 2.5
const quantity = 3

const message = receiptTag`
  You have ${quantity} ${item}s.
  Unit cost: $${price}. 
  Total cost: $${quantity * price}.
`

console.log(message)
</code></pre>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2024/01/Screenshot-2024-01-04-at-10.37.18-AM.png" alt="使用标记模板字面量格式化字符串的示例" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>使用标记模板字面量格式化字符串的示例</figcaption>
</figure>
<p>在此示例中，<code>recieptTag</code> 函数接收一个包含四个表达式的模板字面量：</p>
<ul>
<li><code>${quantity}</code></li>
<li><code>${item}</code></li>
<li><code>${price}</code></li>
<li><code>${quantity * price}</code></li>
</ul>
<p><code>values</code> 数组将包含这些表达式相应的值。</p>
<pre><code class="language-javascript">[3, 'apple', 2.5, 7.5]
</code></pre>
<p>你可以在标签函数中相应地处理它们。</p>
<p>结果，记录到控制台的是包含数量、项目、单价和总成本信息的 <code>message</code>。</p>
<h3 id="2">例子 2</h3>
<pre><code class="language-javascript">function greetUser(strings, name) {
  const now = new Date()
  const currentHour = now.getHours()
  
  const timeOfDay = currentHour &lt; 12 ? 'morning' : currentHour &lt; 17 ? 'afternoon' : 'evening'
  
  return `Good ${timeOfDay} ${name}${strings[1]}`
}

</code></pre>
<pre><code class="language-javascript">console.log(greetUser`Hello ${userName}, nice to meet you!`)
</code></pre>
<p>这个例子使用标记模板字面量来根据一天中的时间来确定如何问候用户。</p>
<p>该函数首先使用日期对象获取当前小时。然后使用三个运算符来确定一天中的时间。并返回一个带有 <code>timeOfDay</code> 变量的字符串，指示一天中的时间。</p>
<p>此外，请注意日志语句的第一个词，并将其与传递给标记的字符串的第一个词进行比较，以查看函数如何更改了字符串。</p>
<h2 id="">总结</h2>
<p>模板字面量为在 JavaScript 中处理字符串提供了一种方便的方法。在本文中，你了解了语法，以及如何在项目中使用它们。</p>
<p>你还了解了模板字面量的一个高级功能：标记模板字面量。这些是接收字符串块和表达式数组的函数。它们根据你为函数编写的逻辑返回一个字符串。</p>
<p>感谢你阅读本文，happy coding！如果你需更深入的教程，请<a href="https://www.youtube.com/@DevAfterHours">订阅我的 YouTube 频道</a>。</p>
<!--kg-card-end: markdown--> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ JavaScript forEach()——JS 数组 forEach 循环示例 ]]>
                </title>
                <description>
                    <![CDATA[ 在使用数组时，有时需要循环或遍历数组的值，以便输出或操作它们。 这些数组可以保存各种数据类型，包括对象、数字、字符串等。 本文将介绍如何使用 JavaScript forEach() 数组方法来遍历各种类型的数组，以及它与 for 循环方法的区别。 JavaScript 中有许多迭代方法，包括 forEach()  方法，它们几乎都执行相同的功能，只是略有不同。是否使用特定的循环方法完全取决于你自己，但重要的是我们要了解每种循环方法及其工作原理。 JavaScript forEach() forEach() 数组方法对任意数组进行循环，对每个数组元素按升序索引顺序执行一次所提供的函数。该函数被称为回调函数。 > 注：数组是元素的集合，元素可以是任何数据类型。 forEach() 循环的语法和参数 以下是 forEach 循环的标准写法： array.forEach(callbackFunction); array.forEach(callbackFunction, thisValue); 回调函数最多可接受三个不同的参数，但并非所有参数都是必需的。下面是一些使用普通函数和 ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/javascript-foreach-js-array-for-each-example/</link>
                <guid isPermaLink="false">6698806edd1680043183e312</guid>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Miya Liu ]]>
                </dc:creator>
                <pubDate>Thu, 18 Jul 2024 03:40:30 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2024/07/cover-template.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>原文：</strong> <a href="https://www.freecodecamp.org/news/javascript-foreach-js-array-for-each-example/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">JavaScript forEach() – JS Array For Each Loop Example</a>
      </p><p>在使用数组时，有时需要循环或遍历数组的值，以便输出或操作它们。</p><p>这些数组可以保存各种数据类型，包括对象、数字、字符串等。</p><p>本文将介绍如何使用 JavaScript <code>forEach()</code> 数组方法来遍历各种类型的数组，以及它与 for 循环方法的区别。</p><p>JavaScript 中有许多迭代方法，包括 <code>forEach()</code> 方法，它们几乎都执行相同的功能，只是略有不同。是否使用特定的循环方法完全取决于你自己，但重要的是我们要了解每种循环方法及其工作原理。</p><h2 id="javascript-foreach-"><strong>JavaScript forEach()</strong></h2><p><code>forEach()</code> 数组方法对任意数组进行循环，对每个数组元素按升序索引顺序执行一次所提供的函数。该函数被称为回调函数。</p><blockquote><strong>注：</strong>数组是元素的集合，元素可以是任何数据类型。</blockquote><h3 id="foreach-">forEach() 循环的语法和参数</h3><p>以下是 forEach 循环的标准写法：</p><pre><code class="language-js">array.forEach(callbackFunction);
array.forEach(callbackFunction, thisValue);</code></pre><p>回调函数最多可接受三个不同的参数，但并非所有参数都是必需的。下面是一些使用普通函数和 ES6 方法声明回调函数的 <code>forEach()</code> 循环示例：</p><pre><code class="language-js">// 只使用当前元素
array.forEach((currentElement) =&gt; { /* ... */ })
array.forEach(function(currentElement) { /* ... */ })

// 只使用当前元素和索引值
array.forEach((currentElement, index) =&gt; { /* ... */ })
array.forEach(function(currentElement, index) { /* ... */ })

// 只使用当前元素、当前元素的索引值和当前元素所属的数组对象
array.forEach((currentElement, index, array) =&gt; { /* ... */ })
array.forEach(function(currentElement, index, array){ /* ... */ })

// 使用所有参数和 thisValue（回调中 this 的值）
array.forEach((currentElement, index, array) =&gt; { /* ... */ }, thisValue)
array.forEach(function(currentElement, index, array) { /* ... */ }, thisValue)</code></pre><p>上面的语法可能看起来有些混乱，但这是编写 forEach 循环的一般语法，具体取决于要使用的值。让我们来看看我们使用的所有参数：</p><p>1、<code>callbackFunction</code>：回调函数是一个对每个元素只执行一次的函数，可以接受以下参数用于回调函数中：</p><ul><li><code>currentElement</code>：顾名思义，当前元素就是循环发生时数组中正在处理的元素。这是唯一必需的参数。</li><li><code>index</code>：这是一个可选参数，指的是 <code>currentElement</code> 的索引。</li><li><code>array</code>：这是一个可选参数，用于返回传递给 <code>forEach()</code> 方法的数组。</li></ul><p>2、<code>thisValue</code>：这是一个可选参数，用于指定将在回调函数中使用的值。</p><p>总之，<code>forEach()</code> 数组迭代方法接受一个回调函数，该回调函数有可在回调函数中用于每个数组项的参数，如数组项、项的索引和整个数组。</p><h2 id="javascript-foreach--1">JavaScript forEach() 示例</h2><p>在了解其他可能的示例之前，我们先看看传入回调函数的所有参数及其用途。</p><h3 id="-currentelement-">如何使用 currentElement 参数</h3><p>假设我们有一个雇员详细信息数组，其中包括姓名、年龄、工资金额和货币：</p><pre><code class="language-js">const staffsDetails = [
  { name: "Jam Josh", age: 44, salary: 4000, currency: "USD" },
  { name: "Justina Kap", age: 34, salary: 3000, currency: "USD" },
  { name: "Chris Colt", age: 37, salary: 3700, currency: "USD" },
  { name: "Jane Doe", age: 24, salary: 4200, currency: "USD" }
];</code></pre><p>如果我们想单独显示所有名字，并在名字周围加上一些词语，我们可以使用 <code>forEach()</code> 方法，具体如下：</p><pre><code class="language-js">staffsDetails.forEach((staffDetail) =&gt; {
  let sentence = `I am ${staffDetail.name} a staff of Royal Suites.`;
  console.log(sentence);
});</code></pre><p>输出：</p><pre><code class="language-bash">"I am Jam Josh a staff of Royal Suites."
"I am Justina Kap a staff of Royal Suites."
"I am Chris Colt a staff of Royal Suites."
"I am Jane Doe a staff of Royal Suites."</code></pre><p><strong>注：</strong>如果 <code>currentElement</code> 值是一个包含键/值对的对象，我们也可以通过这种方式对其进行解构：</p><pre><code class="language-js">staffsDetails.forEach(({ name }, index) =&gt; {
  let sentence = `I am ${name} a staff of Royal Suites.`;
  console.log(sentence);
});</code></pre><h3 id="-index-">如何使用 index 参数</h3><p>我们也可以这样使用未构建的 <code>index</code> 参数来获取每个数组项的索引：</p><pre><code class="language-js">staffsDetails.forEach((staffDetail, index) =&gt; {
  let sentence = `index ${index} : I am ${staffDetail.name} a staff of Royal Suites.`;
  console.log(sentence);
});</code></pre><p>输出：</p><pre><code class="language-bash">"index 0 : I am Jam Josh a staff of Royal Suites."
"index 1 : I am Justina Kap a staff of Royal Suites."
"index 2 : I am Chris Colt a staff of Royal Suites."
"index 3 : I am Jane Doe a staff of Royal Suites."</code></pre><h3 id="-array-"><strong>如何使用 array 参数</strong></h3><p><code>array</code> 参数是第三个参数，用于保存正在迭代的原始数组。例如，我们可以尝试这样在控制台中显示数值：</p><pre><code class="language-js">staffsDetails.forEach((staffDetail, index, array) =&gt; {
  console.log(array);
});</code></pre><p>这将输出整个数组 4 次，因为我们有 4 个项，迭代运行 4 次。让我们对一个只有几个值的数组进行迭代，这样我就可以在这里添加输出结果了：</p><pre><code class="language-js">let scores = [12, 55, 70];

scores.forEach((score, index, array) =&gt; {
  console.log(array);
});</code></pre><p>输出：</p><pre><code class="language-bash">[12,55,70]
[12,55,70]
[12,55,70]</code></pre><p>到目前为止，我们已经使用了回调函数的所有参数。在与 for 循环方法进行快速比较之前，让我们看看其他一些示例，以充分了解它是如何工作的。</p><h3 id="-foreach-">如何使用 forEach() 将数字数组中的所有值相加</h3><p>假设我们有一个 <code>scores</code> 数组。我们可以使用 <code>forEach()</code> 数组方法来循环并将这些数字相加：</p><pre><code class="language-js">const scores = [12, 55, 70, 47];

let total = 0;
scores.forEach((score) =&gt; {
  total += score;
});

console.log(total);</code></pre><p>回想一下，前面我们使用的是员工详细信息数组。现在，让我们尝试将所有员工的工资加在一起，看看如何使用对象：</p><pre><code>let totalSalary = 0;
staffsDetails.forEach(({salary}) =&gt; {
  totalSalary += salary;
});

console.log(totalSalary + " USD"); // "14900 USD"</code></pre><p><strong><strong>注</strong>：</strong>我们解构了 <code>currentElement</code> 对象。</p><h3 id="-foreach--1">如何在 forEach() 回调函数中使用条件</h3><p>在遍历数组时，我们可能需要检查特定条件，这通常与 for 循环方法相同。我们可以将这些条件传递到回调函数中，或者传递到我们想在每个数组项上运行的任何其他操作中。</p><p>例如，如果我们只想从之前声明的员工详细信息数组中显示工资大于或等于 4000 的人员姓名，可以执行以下操作：</p><pre><code class="language-js">staffsDetails.forEach(({name, salary}) =&gt; {
  if(salary &gt;= 4000){
    console.log(name);
  }
});</code></pre><p>输出：</p><pre><code class="language-bash">"Jam Josh"
"Jane Doe"</code></pre><h2 id="-foreach-for-">比较 forEach() 和 for 循环</h2><p>for 循环与 forEach 方法非常相似，但各自都有一些独特的功能，例如：</p><h3 id="-">跳出和继续循环</h3><p>在循环遍历数组时，我们可能希望在满足特定条件时跳出或继续循环。这可以通过 <code>break</code> 和 <code>continue</code> 指令来实现，但在 <code>forEach()</code> 方法中却行不通，如下图所示：</p><pre><code class="language-js">const scores = [12, 55, 70, 47];

scores.forEach((score) =&gt; {
  console.log(score);

  if (score === 70) 
    break;
});</code></pre><p>这将产生 <code>Illegal break statement</code> 的语法错误。<code>continue</code> 指令也是这样，它也会产生 <code>Illegal continue statement: no surrounding iteration statement</code>。</p><pre><code class="language-js">const scores = [12, 55, 70, 47];

scores.forEach((score) =&gt; {
  if (score === 70) 
    continue;
  
  console.log(score);
});</code></pre><p>但幸运的是，这与 for 循环方法配合得天衣无缝：</p><pre><code class="language-js">const scores = [12, 55, 70, 47];

for (i = 0; i &lt; scores.length; i++) {
  console.log(scores[i]);

  if (scores[i] === 70) 
    break;
}
</code></pre><p>输出：</p><pre><code class="language-bash">12
55
70</code></pre><p><code>continue</code> 指令也是如此：</p><pre><code class="language-js">const scores = [12, 55, 70, 47];

for (i = 0; i &lt; scores.length; i++) {
  if (scores[i] === 70) 
    continue;
  
  console.log(scores[i]);
}
</code></pre><p>输出：</p><pre><code class="language-bash">12
55
47</code></pre><h3 id="--1">元素缺失的数组</h3><p>另一个需要进行比较的重要情况是，我们正在迭代的数组有一些缺失值/数组项，如下所示：</p><pre><code class="language-js">const studentsScores = [70, , 12, 55, , 70, 47];</code></pre><p>这可能是由于开发人员的错误或其他原因造成的，但这两种方法采用了两种不同的方式来循环这些类型的数组。如果存在缺失值，for 循环会返回 undefined，而 <code>forEach()</code> 方法则会跳过缺失值。</p><p><strong><strong>For</strong> 循环</strong></p><pre><code class="language-js">const studentsScores = [70, , 12, 55, , 70, 47];

for (i = 0; i &lt; studentsScores.length; i++) {
  console.log(studentsScores[i]);
}</code></pre><p>输出：</p><pre><code class="language-bash">70
undefined
12
55
undefined
70
47</code></pre><p><strong><strong>forEach()</strong></strong></p><pre><code class="language-js">const studentsScores = [70, , 12, 55, , 70, 47];

studentsScores.forEach((stundentScore) =&gt; {
  console.log(stundentScore);
});</code></pre><p>输出：</p><pre><code class="language-bash">70
12
55
70
47</code></pre><p><strong><strong>注</strong>：</strong>Async/Await 对 <code>forEach()</code> 数组方法无效，但对 for 循环方法有效。</p><h2 id="--2"><strong>总结</strong></h2><p>在本文中，我们学习了如何使用 <code>forEach()</code> 数组方法，该方法允许我们遍历包含任何类型项的数组。与 for 循环相比，它还能让我们写出更简洁、更易读的代码。</p> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 如何从 JavaScript 数组中删除元素 ]]>
                </title>
                <description>
                    <![CDATA[ 在 JavaScript 中，你经常需要从数组中移除元素，无论是队列数据结构还是 React State。 在本文的前半部分，你将学习所有允许你在不更改原始数组的情况下从数组中移除元素的方法。事实上，这也是你最常要做的事情。例如，如果你不想更改 React State。或者在代码的其他部分使用了数组，改变数组会导致意想不到的问题。 不过，为了完整起见，本文的后半部分将列出从数组中就地移除项的方法。这些方法确实会改变数组本身。 如何在不更改数组的情况下从数组中移除元素 如果你有一个输入数组，比如作为函数参数的数组，那么根据最佳实践，你不应该更改数组，而是创建一个新数组。 有几种方法可以在不更改数组的情况下从数组中移除特定项。 为了避免对数组进行改变，我们将创建一个新的数组，但不包含要移除的元素。 你可以使用以下方法：  * Array.prototype.slice()  * Array.prototype.slice() 加上 Array.prototype.concat()  * Array.prototype.filter()  * for 循环和 Array.pro ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/how-to-remove-an-element-from-a-javascript-array-removing-a-specific-item-in-js/</link>
                <guid isPermaLink="false">668655a09100b5049d88a74b</guid>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Miya Liu ]]>
                </dc:creator>
                <pubDate>Thu, 04 Jul 2024 09:16:06 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2024/07/vinicius-amnx-amano-dbOV1qSiL-c-unsplash.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>原文：</strong> <a href="https://www.freecodecamp.org/news/how-to-remove-an-element-from-a-javascript-array-removing-a-specific-item-in-js/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">How to Remove an Element from a JavaScript Array – Removing a Specific Item in JS</a>
      </p><p>在 JavaScript 中，你经常需要从数组中移除元素，无论是队列数据结构还是 React State。</p><p>在本文的前半部分，你将学习所有允许你在不更改原始数组的情况下从数组中移除元素的方法。事实上，这也是你最常要做的事情。例如，如果你不想更改 React State。或者在代码的其他部分使用了数组，改变数组会导致意想不到的问题。</p><p>不过，为了完整起见，本文的后半部分将列出从数组中就地移除项的方法。这些方法确实会改变数组本身。</p><h2 id="-">如何在不更改数组的情况下从数组中移除元素</h2><p>如果你有一个输入数组，比如作为函数参数的数组，那么根据最佳实践，你不应该更改数组，而是创建一个新数组。</p><p>有几种方法可以在不更改数组的情况下从数组中移除特定项。</p><p>为了避免对数组进行改变，我们将创建一个新的数组，但不包含要移除的元素。</p><p>你可以使用以下方法：</p><ul><li><code>Array.prototype.slice()</code></li><li><code>Array.prototype.slice()</code> 加上 <code>Array.prototype.concat()</code></li><li><code>Array.prototype.filter()</code></li><li><code>for</code> 循环和 <code>Array.prototype.push()</code></li></ul><p>让我们详细看看如何在不改变原有数组的情况下，使用上述每一种方法从数组中移除一个元素。</p><h3 id="-slice-">使用 slice 删除数组中的第一个元素</h3><p>如果要删除数组中的第一个元素，可以在名为 <code>arr</code> 的数组上使用 <code>Array.prototype.slice()</code>：<code>arr.slice(1)</code>。</p><p>下面是一个完整的示例，你想从包含字母表前 6 个字母的数组中移除第一个元素。</p><pre><code class="language-javascript">// 初始数组
const arrayOfLetters = ['a', 'b', 'c', 'd', 'e', 'f'];

// 数组被复制，不包含第一个元素
const copyWithoutFirstElement = arrayOfLetters.slice(1);

// arrayOfLetters 没有被改变
console.log(arrayOfLetters) // ['a', 'b', 'c', 'd', 'e', 'f']

// copyWithoutFirstElement 包含字母 b 到 f
console.log(copyWithoutFirstElement) // ['b', 'c', 'd', 'e', 'f']</code></pre><p><code>slice</code> 方法可以使用一个数字作为参数，在这种情况下，它会从该索引复制到数组的末尾。因此，使用 <code>arrayOfLetters.slice(1)</code> 将创建一个不包含第一个元素的 <code>arrayOfLetters</code> 数组副本。</p><h3 id="-slice--1">使用 slice 删除数组的最后一个元素</h3><p>如果要删除的元素是数组的最后一个元素，可以在名为 <code>arr</code> 的数组上使用 <code>Array.prototype.slice()</code>：<code>arr.slice(0, -1)</code>。</p><p>下面是一个完整的示例，使用的是上面的字母数组，从包含前 6 个字母的数组开始。</p><pre><code class="language-javascript">const arrayOfLetters = ['a', 'b', 'c', 'd', 'e', 'f'];
const copyWithoutLastElement = arrayOfLetters.slice(0, -1);

// arrayOfLetters 没有被改变
console.log(arrayOfLetters) // ['a', 'b', 'c', 'd', 'e', 'f']

console.log(copyWithoutLastElement) // ['a', 'b', 'c', 'd', 'e']</code></pre><p><code>slice</code> 方法最多需要两个参数。<code>slice</code> 的第一个索引表示要从哪个索引开始复制，第二个参数表示要复制到哪个元素，但并不包含它。</p><p><code>slice</code> 接受负数索引，从末尾开始计数。这意味着 <code>-1</code> 表示最后一个索引。因此，从 <code>0</code> 到 <code>-1</code> 意味着创建一个从索引 <code>0</code> 到（但不包括）最后一个索引的副本。最终的结果是，副本中不包括最后一个元素。</p><h3 id="-slice-concat-">使用 slice 和 concat 在数组的任意位置删除元素</h3><p>如果想创建一个缺少任意索引处元素的副本，可以使用 <code>Array.prototype.slice</code> 和 <code>Array.prototype.concat</code>：<code>arrayOfLetters.slice(0, n).concat(arrayOfLetters.slice(n+1))</code>，其中 <code>n</code> 是要删除元素的索引。</p><pre><code class="language-javascript">const arrayOfLetters = ['a', 'b', 'c', 'd', 'e', 'f'];

const halfBeforeTheUnwantedElement = arrayOfLetters.slice(0, 2)

const halfAfterTheUnwantedElement = arrayOfLetters(3);

const copyWithoutThirdElement = halfBeforeTheUnwantedElement.concat(halfAfterTheUnwantedElement);

// arrayOfLetters 没有被改变
console.log(arrayOfLetters) // ['a', 'b', 'c', 'd', 'e', 'f']

console.log(copyWithoutFifthElement) // ['a', 'b', 'd', 'e', 'f']</code></pre><p>这次使用 <code>slice</code> 是将前两次使用结合起来的一种方法。</p><p>第一次使用 <code>slice</code> 会创建一个从开始到要删除的元素之前的数组。</p><p>第二次使用 <code>slice</code> 会创建一个从要删除的元素之后到数组末尾的数组。</p><p>使用 <code>concat</code> 将这两个数组连接在一起，形成一个与初始数组相似的数组，但没有特定的元素。</p><h3 id="-filter-">使用 filter 移除具有特定值的元素</h3><p>如果想删除具有某个值的元素，可以使用 <code>Array.prototype.filter()</code>。让我们以同样的 <code>arrayOfLetters</code> 为例，创建一个不带 <code>d</code> 的副本。</p><pre><code class="language-javascript">const arrayOfLetters = ['a', 'b', 'c', 'd', 'e', 'f'];

const arrayWithoutD = arrayOfLetters.filter(function (letter) {
    return letter !== 'd';
});

// arrayOfLetters 没有被改变
console.log(arrayOfLetters); // ['a', 'b', 'c', 'd', 'e', 'f']

console.log(arrayWithoutD); // ['a', 'b', 'c', 'e', 'f']</code></pre><p><code>filter</code> 接收一个回调，并用该回调测试数组中的所有元素。它会保留回调返回 <code>true</code>（或真值）的元素，并排除回调返回 <code>false</code>（或假值）的元素。</p><p>在本例中，回调检查 <code>letter !== "d"</code>，因此对字母 <code>d</code> 返回 <code>false</code>，对所有其他元素返回 <code>true</code>，从而得到一个不包含字母 <code>d</code> 的数组。</p><p><code>filter</code> 回调传递三个参数，依次是元素本身、元素索引和整个数组。</p><p>你可以根据需要创建比本例更复杂的条件。</p><h3 id="-for-push-">使用 for 循环和 push 从数组中移除元素</h3><p>在不改变原始数组的情况下从数组中移除元素的最后一种方法是使用 <code>push</code> 方法。</p><p>只需以下简单步骤：</p><ul><li>创建一个空数组</li><li>遍历原始数组</li><li>将想要保留的元素推送到空数组中</li></ul><pre><code class="language-javascript">const arrayOfLetters = ['a', 'b', 'c', 'd', 'e', 'f'];

const arrayWithoutB = [];

for (let i = 0; i &lt; arrayOfLetters.length; i++) {
    if (arrayOfLetters[i] !== 'b') {
        arrayWithoutH.push(arrayOfLetters[i]);
    }
}

// arrayOfLetters 没有被改变
console.log(arrayOfLetters); // ['a', 'b', 'c', 'd', 'e', 'f']

console.log(arrayWithoutB); // ['a', 'c', 'd', 'e', 'f']</code></pre><p>对于更复杂的语句，<code>if</code>语句条件可以同时检查索引（<code>i</code>）和元素值。</p><h3 id="-rest-">使用解构赋值和 rest 操作符移除数组的第一个元素</h3><p>数组解构和 rest 操作符是两个有点令人困惑的概念。</p><p>如果你想深入了解这个话题，我建议你阅读<a href="https://www.freecodecamp.org/chinese/news/array-and-object-destructuring-in-javascript/">《JavaScript 数组解构和对象解构》</a>这篇文章，其中介绍了如何对数组进行解构。</p><p>你可以使用解构删除第一个元素——比方说删除一个名为 <code>arr</code> 的数组——然后创建一个名为 <code>newArr</code> 的新数组，方法是：<code>const [ , ...newarr] = arr;</code>。</p><p>现在，我们来看一个使用解构和 rest 操作符的实际例子。</p><pre><code>const arrayOfFruits = ['olive', 'apricot', 'cherry', 'peach', 'plum', 'mango'];

const [ , ...arrayOfCulinaryFruits] = arrayOfFruits;

// arrayOfFruits 没有被改变
console.log(arrayOfFruits); // ['olive', 'apricot', 'cherry', 'peach', 'plum', 'mango']

console.log(arrayOfCulinaryFruits); // ['apricot', 'cherry', 'peach', 'plum', 'mango']</code></pre><p>在 rest 运算符前加逗号表示避开数组中的第一个元素，其他所有元素都被复制到 <code>arrayOfCulinaryFruits</code> 数组中。</p><h2 id="--1">如何在改变数组的同时从数组中移除一个元素</h2><p>在某些情况下，可能需要改变原始数组。在这种情况下，你也可以使用下面的方法。</p><ul><li><code>Array.prototype.pop()</code></li><li><code>Array.prototype.shift()</code></li><li><code>Array.prototype.splice()</code></li></ul><h3 id="-pop-">使用 pop 删除数组的最后一个元素</h3><p>你可以使用 <code>Array.prototype.pop()</code> 删除数组的最后一个元素。</p><p>如果你有一个名为 <code>arr</code> 的数组，则是 <code>arr.pop()</code>。</p><pre><code class="language-javascript">const arrayOfNumbers = [1, 2, 3, 4];

const previousLastElementOfTheArray = arrayOfNumbers.pop();

console.log(arrayOfNumbers); // [1, 2, 3]

console.log(previousLastElementOfTheArray); // 4</code></pre><p><code>pop</code> 方法用于数组，它通过删除数组的最后一项来改变数组。</p><p>pop 方法还会返回移除的元素。</p><h3 id="-shift-">用 shift 删除数组的第一个元素</h3><p>可以在数组中使用 <code>shift</code> 方法移除数组的第一个元素。</p><p>如果有一个名为 <code>arr</code> 的数组，可以这样使用：<code>arr.shift()</code>。</p><pre><code class="language-javascript">const arrayOfNumbers = [1, 2, 3, 4];

const previousFirstElementOfTheArray = arrayOfNumbers.shift();

console.log(arrayOfNumbers); // [2, 3, 4]

console.log(previousFirstElementOfTheArray); // 1</code></pre><p><code>shift</code> 方法移除数组的第一个元素。</p><p>它还会返回移除的元素。</p><h3 id="-splice-">使用 splice 删除任意索引处的元素</h3><p>使用 <code>splice</code> 方法可以删除任意索引处的元素。</p><p>如果你有一个名为 <code>arr</code> 的数组，可以使用这种方法移除任意索引处的元素：<code>arr.splice(n, 1)</code>，其中 <code>n</code> 是要移除元素的索引。</p><pre><code class="language-javascript">const arrayOfNumbers = [1, 2, 3, 4];

const previousSecondElementOfTheArray = arrayOfNumbers.splice(1, 1);

console.log(arrayOfNumbers); // [1, 3, 4]

console.log(previousSecondElementOfTheArray); // [2]</code></pre><p><code>splice</code> 方法可以接受多个参数。</p><p>要移除任意索引处的元素，需要给 <code>splice</code> 两个参数：第一个参数是要移除元素的索引，第二个参数是要移除元素的个数。</p><p>因此，如果有一个名为 <code>arr</code> 的数组，要删除索引为 <code>4</code> 的元素，使用 <code>splice</code> 方法的方法是：<code>arr.splice(4, 1)</code>。</p><p>然后，<code>splice</code> 方法会返回一个包含移除元素的数组。</p><h2 id="--2"><strong>总结</strong></h2><p>在 JavaScript 中，有许多不同的方法来做同一件事。</p><p>在本文中，你已经学会了从数组中移除元素的九种不同方法。其中六种不会改变原始数组，三种会改变原始数组。</p><p>你也许会在某个时候用到所有这些方法或者其中一种，也许你还会学到更多方法实现同样的目的。</p> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ JavaScript Promises 工作原理——初学者手册 ]]>
                </title>
                <description>
                    <![CDATA[ 许多操作，如网络请求，本质上是异步的。在处理异步代码时，Promise 是最有用和最强大的工具之一。在这本手册中，您将了解有关 JavaScript Promises 的所有信息以及如何使用它们。 目录  1.  Promise 是什么  2.  Promise 与其他异步模式的比较  3.  如何创建一个 Promise  4.  如何获取 Promise 的结果  5.  如何使用then处理错误  6.  Promise 链  7.  如何创建立即兑现或拒绝的 Promises  8.  如何使用async和await  9.  反模式 Promise ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/the-javascript-promises-handbook/</link>
                <guid isPermaLink="false">667bd663e2419d0432abffba</guid>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ James Z. Zhang ]]>
                </dc:creator>
                <pubDate>Wed, 26 Jun 2024 09:16:58 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2024/06/How-JavaScript-Promises-Work-Cover-1.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>原文：</strong> <a href="https://www.freecodecamp.org/news/the-javascript-promises-handbook/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">How JavaScript Promises Work – Handbook for Beginners</a>
      </p><!--kg-card-begin: markdown--><p>许多操作，如网络请求，本质上是异步的。在处理异步代码时，Promise 是最有用和最强大的工具之一。在这本手册中，您将了解有关 JavaScript Promises 的所有信息以及如何使用它们。</p>
<h2 id="">目录</h2>
<ol>
<li><a href="#what-is-promise">Promise 是什么</a></li>
<li><a href="#comparison-between-promise-and-other-patterns">Promise 与其他异步模式的比较</a></li>
<li><a href="#how-to-create-a-promise">如何创建一个 Promise</a></li>
<li><a href="#get-the-result">如何获取 Promise 的结果</a></li>
<li><a href="#use-then-to-handle-errors">如何使用<code>then</code>处理错误</a></li>
<li><a href="#promise-chain">Promise 链</a></li>
<li><a href="#immediately-resolve-or-reject">如何创建立即兑现或拒绝的 Promises</a></li>
<li><a href="#use-async-and-await">如何使用<code>async</code>和<code>await</code></a></li>
<li><a href="#anti-pattern">反模式 Promise</a></li>
<li><a href="#conclusion">总结</a></li>
</ol>
<h2 id="what-is-promise"> Promise 是什么</h2>
<p>首先，让我们了解一下什么是 Promise。</p>
<p>简单来说，Promise 是一个代表异步操作的对象。这个对象可以告诉你操作何时成功，或者何时失败。</p>
<p>当你调用一个基于 Promise 的 API 时，函数返回一个 Promise 对象来告诉你最终的操作结果。</p>
<h3 id="promise">Promise 的状态</h3>
<p>在其生命周期中，Promise 可以处于以下三种状态之一：</p>
<ul>
<li><strong>待定（Pending）</strong>: 当操作仍在进行中时，Promise 处于待定状态。它处于闲置状态，等待最终结果或错误。</li>
<li><strong>已兑现（Fulfilled）</strong>：返回 Promise 的异步任务成功完成。Promise 已经被一个值兑现，该值是操作的结果。</li>
<li><strong>已拒绝（Rejected）</strong>: 如果异步操作失败，Promise 被认为是拒绝的。Promise 会有一个 <em>拒绝理由</em>。这通常是一个错误（Error）对象，也可以是任何错误值——可以是一个简单的数字或字符串！</li>
</ul>
<p>Promise 一开始处于待定（pending）状态，然后根据结果，会转换到已兑现（fulfilled）或已拒绝（rejected）状态。一旦 Promise 达到已兑现或已拒绝状态，就被认为是 <em>已敲定（settled）</em>。</p>
<p>不是所有的异步任务都会最终完成。一个 <code>Promise</code> 可能由于异步任务的代码中存在错误而永远保持在待定状态。</p>
<h2 id="comparison-between-promise-and-other-patterns">Promise 与其他异步模式的比较</h2>
<p>Promise 的工作方式与 JavaScript 的其他异步模式有点不同。在深入学习 Promise 之前，我们来简要比较一下。</p>
<h3 id="">回调函数</h3>
<p>回调函数是你传递给另一个函数的函数。当你调用的函数完成其工作后，它将用结果执行你的回调函数。</p>
<p>想象一下，一个名为 <code>getUsers</code> 的函数将发起网络请求以获取用户数组。你可以将一个回调函数传递给 <code>getUsers</code>，一旦网络请求完成，这个回调函数就会被调用，并传入用户数组：</p>
<pre><code class="language-javascript">console.log('Preparing to get users');
getUsers(users =&gt; {
  console.log('Got users:', users);
});
console.log('Users request sent');
</code></pre>
<p>一个回调函数的例子</p>
<p>首先，上述代码会打印（在 console 中显示）“Preparing to get users（准备获取用户）”。然后它调用 <code>getUsers</code>来启动网络请求。但是 JavaScript 不会等待请求完成。而是会立即执行下一个 console.log 语句（Users request sent）。</p>
<p>稍后，一旦用户被加载成功，你的回调函数将被执行，屏幕上将会显示“已获取用户”。</p>
<p>一些基于回调的 API，如许多 Node.js API，使用 <em>错误优先的回调（error-first callbacks）</em>。这些回调函数有两个参数。第一个参数是错误（error），第二个是结果（result）。</p>
<p>通常来说，取决于操作的结果，这两者中只有一个会有值。这与 Promise 的已兑现和已拒绝状态类似。</p>
<p>回调 API 的问题在于嵌套。如果你需要按顺序进行多个异步调用，你最终需要不断嵌套函数和回调函数。</p>
<p>例如，如果你想异步进行 3 个任务：读取一个文件，处理该文件中的一些数据，然后写一个新文件。</p>
<pre><code class="language-javascript">readFile('sourceData.json', data =&gt; {
	processData(data, result =&gt; {
		writeFile(result, 'processedData.json', () =&gt; {
			console.log('Done processing');
		});
	});
});
</code></pre>
<p>嵌套回调序列</p>
<p>在错误处理方面，甚至会更加笨拙。假如这些函数使用错误优先的回调：</p>
<pre><code class="language-javascript">readFile('sourceData.json', (error, data) =&gt; {
	if (error) {
		console.error('Error reading file:', error);
		return;
	}
	
	processData(data, (error, result) =&gt; {
		if (error) {
			console.error('Error processing data:', error);
			return;
		}
		
		writeFile(result, 'processedData.json', error =&gt; {
			if (error) {
				console.error('Error writing file:', error);
				return;
			}
			
			console.log('Done processing');
		});
	});
});
</code></pre>
<p>嵌套的错误优先回调序列</p>
<p>现代 API 中的异步机制通常不直接使用回调函数，但它们是其他类型异步工具（如 Promise）的基础。</p>
<h3 id="events">事件（Events）</h3>
<p>事件是你可以监听并做出响应的东西。在 JavaScript 中，一些对象是事件的 <em>发射器（emitters）</em>，这意味着你可以在这些对象上注册事件监听器（译者注：event listener，用来监视软件的变化，比如用户点击了鼠标）。</p>
<p>在 DOM 中，许多元素实现了 <code>EventTarget</code> 接口，它提供了 <code>addEventListener</code> 和 <code>removeEventListener</code> 方法。</p>
<p>一个给定类型的事件可以发生多次。例如，你可以监听按钮上的点击事件（click event）：</p>
<pre><code class="language-javascript">myButton.addEventListener('click', () =&gt; {
   console.log('button was clicked!'); 
});
</code></pre>
<p>监听按钮的点击</p>
<p>每次按钮被点击时，控制台都会打印“button was clicked!”（按钮被点击了！）。</p>
<p><code>addEventListener</code> 本身接受一个回调函数。每当事件发生时，回调函数就会被执行。</p>
<p>一个对象可以发出多种类型的事件。考虑一个图像对象。如果指定 URL 的图像成功加载，<code>加载事件（load）</code> 将被触发。如果发生错误，<code>错误事件（error）</code>将被触发。</p>
<pre><code class="language-javascript">myImage.addEventListener('load', () =&gt; {
    console.log('Image was loaded');
});

myImage.addEventListener('error', error =&gt; {
   console.error('Image failed to load:', error); 
});
</code></pre>
<p>监听图像的加载和错误事件</p>
<p>假设在你添加事件监听器之前，图像已经完成了加载。那就什么也不会发生！基于事件的 API 的一个缺点是，如果你在事件发生后添加事件监听器，你的回调将不会被执行。这是有道理的——毕竟，当你向按钮添加点击监听器时，你不会想接收所有过去的点击事件。</p>
<p>现在我们已经探讨了回调和事件，让我们看看 Promise。</p>
<h2 id="how-to-create-a-promise">如何创建 Promiose</h2>
<p>你可以使用 <code>new</code> 关键字和 <code>Promise</code> 构造器来创建一个 <code>Promise</code>。<code>Promise</code> 构造器接受一个回调函数，该函数带有两个参数，称为 <code>resolve</code> 和 <code>reject</code>。这两个参数都是由 <code>Promise</code> 提供的函数，用于将 <code>Promise</code> 转换到已兑现或已拒绝状态。</p>
<p>在你的回调中，执行你的异步工作。如果任务成功，你应该调用 <code>resolve</code> 函数并传递成功的结果。如果发生错误，则应该调用 <code>reject</code> 函数，并传递错误。</p>
<p>以下是一个创建 Promise 的示例，该示例包装了浏览器的 <code>setTimeout</code> 函数：</p>
<pre><code class="language-javascript">function wait(duration) {
	return new Promise(resolve =&gt; {
        setTimeout(resolve, duration);
    });
}
</code></pre>
<p>将 <code>setTimeout</code> 包装在 Promise 中</p>
<p><code>resolve</code> 函数作为第一个参数传递给 <code>setTimeout</code>。在由 <code>duration</code> 指定的时间过后，浏览器会调用 <code>resolve</code> 函数来兑现 Promise。</p>
<p>注意：在这个示例中，调用 <code>resolve</code> 函数之前的延迟可能比 duration 指定的时间要长。这是因为 <code>setTimeout</code> 并不能保证在指定时间内执行。</p>
<p>注意，在通常情况下，你只需要处理其他 API 返回的 Promise。而不需要手动构建自己的 Promise。</p>
<h2 id="get-the-result">如何获取 Promise 的结果</h2>
<p>我们已经看到了如何创建一个 Promise，但你如何实际获取异步操作的结果呢？为此，你需要在 Promise 对象本身上调用 <code>then</code>。<code>then</code> 接受一个回调函数作为其参数。当 Promise 被兑现时，会执行回调函数，并传入结果。</p>
<p>让我们看一个这样的例子。一个名为 <code>getUsers</code> 的函数，它异步加载用户对象列表并返回一个 Promise。你可以通过在 <code>getUsers</code> 返回的 Promise 上调用 <code>then</code> 来获取用户列表。</p>
<pre><code class="language-javascript">getUsers()
  .then(users =&gt; {
    console.log('Got users:', users);
  });
</code></pre>
<p>在 Promise 上调用 <code>then</code></p>
<p>就像处理事件或基于回调的 API 一样，你的代码将继续执行而不是等待结果。稍后，当用户数据加载完毕时，你的回调函数将被安排执行。</p>
<pre><code class="language-javascript">console.log('Loading users');
getUsers()
  .then(users =&gt; {
    console.log('Got users:', users);
  });
console.log('Continuing on');
</code></pre>
<p>在上面的例子中，首先会打印“Loading users（正在加载用户）”。因为 <code>getUsers</code> 调用仍在加载用户，所以接下来打印的将是“Continuing on（继续进行）”。稍后，你将看到打印出“Got users:（已获取用户）”。</p>
<h2 id="use-then-to-handle-errors">如何使用 `then` 处理错误</h2>
<p>我们已经看到了如何使用 <code>then</code> 来获取 Promise 提供的结果，但如果我们加载用户列表失败会发生什么？</p>
<p><code>then</code> 函数还有第二个参数，另一个回调。那就是错误处理器（error handler）。如果 Promise 被拒绝，这个回调将会用拒绝/错误值执行。</p>
<pre><code class="language-javascript">getUsers()
  .then(users =&gt; {
    console.log('Got users:', users);
  }, error =&gt; {
    console.error('Failed to load users:', error);  
  });
</code></pre>
<p>由于一个 Promise 只能被兑现（fulfilled）或被拒绝（rejected），而不能同时是两者，所以只有其中一个回调函数会被执行。</p>
<p>使用 Promise 时，务必处理错误，这一点很重要。如果你有一个 Promise 拒绝的情况没有通过错误回调处理，你的控制台会出现一个未处理拒绝的异常，这会在运行时对用户造成问题。</p>
<h2 id="promise-chain">Promise 链</h2>
<p>如果你需要连续处理多个 Promise 怎么办？我们从一个文件加载了一些数据，进行了一些处理，然后将结果写入一个新文件。假设 <code>readFile</code>，<code>processData</code> 和 <code>writeFile</code> 函数使用 Promise 而不是回调。</p>
<p>像这样：</p>
<pre><code class="language-javascript">readFile('sourceData.json')
  .then(data =&gt; {
    processData(data)
      .then(result =&gt; {
        writeFile(result, 'processedData.json')
          .then(() =&gt; {
            console.log('Done processing');
          });
      });
  });
</code></pre>
<p>嵌套的 Promise</p>
<p>这看起来并不理想，我们仍然面临着我们在回调方法中遇到的嵌套问题。幸运的是，有一个更好的方法。你可以将 promise 以平面序列方式链接起来。</p>
<p>让我们更深入地看看 <code>then</code> 是如何工作的。关键思想是：用 <code>then</code> 方法返回 <em>另一个 promise</em>。你从 <code>then</code> 回调中返回的任何值都会成为这个新 promise 的兑现值。</p>
<p>想一下，有一个 <code>getUsers</code> 函数，它返回一个 Promise，当该 Promise 被兑现时，会得到一个包含用户对象的数组。假设我们在这个 Promise 上调用 <code>then</code>，并在回调中返回数组中的第一个用户（<code>users[0]</code>）：</p>
<pre><code class="language-javascript">getUsers().then(users =&gt; users[0]);
</code></pre>
<p>这个整个表达式会产生一个新的 Promise ，将会用第一个用户对象兑现 Promise！</p>
<pre><code class="language-javascript">getUsers()
  .then(users =&gt; users[0])
  .then(firstUser =&gt; {
    console.log('First user:', firstUser.username);
  });
</code></pre>
<p>从 <code>then</code> 返回一个值</p>
<p>这种返回一个 Promise，调用 <code>then</code> 并返回另一个值，从而产生另一个 Promise 的过程，称为链式调用。</p>
<p>让我们延伸这个想法。如果我们不从 <code>then</code> 处理程序返回一个值，而是返回另一个 Promise 会怎样？再次考虑文件处理的例子，其中 <code>readFile</code> 和 <code>processData</code> 都是返回 Promise 的异步函数：</p>
<pre><code class="language-javascript">readFile('sourceData.json')
  .then(data =&gt; processData(data));
</code></pre>
<p>从 <code>then</code> 返回另一个 Promise</p>
<p><code>then</code> 处理程序调用 <code>processData</code>，并返回生成的 Promise。和之前一样，这会返回一个新的 Promise。在这种情况下，当 <code>processData</code> 返回的 Promise 被兑现时，新的 Promise 也会被兑现，并赋予相同的值。因此，上述示例中的代码将返回一个包含处理后的数据的 Promise。</p>
<p>你可以一个接一个地链式调用多个 Promises，直到你得到所需的最终值：</p>
<pre><code class="language-javascript">readFile('sourceData.json')
  .then(data =&gt; processData(data))
  .then(result =&gt; writeFile(result, 'processedData.json'))
  .then(() =&gt; console.log('Done processing'));
</code></pre>
<p>链式调用多个 Promises</p>
<p>在上述示例中，整个表达式将返回一个 Promise，该 Promise 在处理后的数据写入文件后才会被兑现。控制台打印“Done processing!（处理完成）”，然后最终的 Promise 才会被兑现。</p>
<h3 id="promise">链式调用 Promise 的错误处理</h3>
<p>在我们的文件处理示例中，过程中的任何阶段都可能发生错误。你可以使用 Promise 的 <code>catch</code> 方法来处理任何步骤中的错误。</p>
<pre><code class="language-javascript">readFile('sourceData.json')
  .then(data =&gt; processData(data))
  .then(result =&gt; writeFile(result, 'processedData.json'))
  .then(() =&gt; console.log('Done processing'))
  .catch(error =&gt; console.log('Error while processing:', error));
</code></pre>
<p>使用 <code>catch</code> 来处理错误</p>
<p>如果链式调用中的某个 Promise 被拒绝，传递给 <code>catch</code> 的回调函数将会执行，并跳过整个链式调用中的剩余部分。</p>
<h3 id="finally">如何使用 <code>finally</code></h3>
<p>如果你希望无论 Promise 的结果如何都要执行一些操作。例如，关闭一个数据库或文件。可以使用 finally 方法。</p>
<pre><code class="language-javascript">openDatabase()
  .then(data =&gt; processData(data))
  .catch(error =&gt; console.error('Error'))
  .finally(() =&gt; closeDatabase());
</code></pre>
<h3 id="promiseall">如何使用 <code>Promise.all</code></h3>
<p>Promise 链式调用可以让你按顺序运行多个任务，但如果你想同时运行多个任务，<code>Promise.all</code> 方法可以让你做到这一点。</p>
<p><code>Promise.all</code> 接受一个 Promise 数组，并返回一个新的 Promise。当所有其他 Promise 都被兑现时，这个新的 Promise 也将被兑现。其兑现值是一个数组，包含了输入数组中每个 Promise 的兑现值。</p>
<p>假设你有一个函数 <code>loadUserProfile</code> 用于加载用户的个人资料数据，另一个函数 <code>loadUserPosts</code> 用于加载用户的帖子。它们都以用户 ID 作为参数。还有第三个函数 <code>renderUserPage</code> 需要同时获取用户的个人资料和帖子列表。</p>
<pre><code class="language-javascript">const userId = 100;

const profilePromise = loadUserProfile(userId);
const postsPromise = loadUserPosts(userId);

Promise.all([profilePromise, postsPromise])
  .then(results =&gt; {
    const [profile, posts] = results;
    renderUserPage(profile, posts);
  });
</code></pre>
<p>使用 <code>Promise.all</code> 等待多个 Promise</p>
<p>如果出现错误怎么办呢？如果传递给 <code>Promise.all</code> 的任何一个 Promise 因错误而被拒绝，生成的 Promise 也将被该错误拒绝。其他任何被兑现的 Promise 的值都将会丢失。</p>
<h3 id="promiseallsettled">如何使用 <code>Promise.allSettled</code></h3>
<p><code>Promise.allSettled</code> 方法的工作方式类似于 <code>Promise.all</code>。主要区别在于 <code>Promise.allSettled</code> 返回的 Promise 永远不会被拒绝。</p>
<p>相反，它会以一个对象数组来兑现，这些对象的顺序对应于输入数组中的 Promise 的顺序。每个对象都有一个 <code>status</code> 属性，该属性根据结果分别为 "fulfilled（已兑现）" 或 "rejected（已拒绝）"。</p>
<p>如果 <code>status</code> 是 "fulfilled（已兑现）"，该对象还会有一个 <code>value</code> 属性，表示 Promise 的兑现值。如果 <code>status</code> 是 "rejected"，对象将有一个 <code>reason</code> 属性，用来表示 Promise 被拒绝时的错误或其他对象。</p>
<p>再看一个 <code>getUser</code> 函数，该函数接受一个用户 ID 并返回一个 Promise，该 Promise 将会被具有该 ID 的用户实现。你可以使用 <code>Promise.allSettled</code> 并行加载这些用户，确保获取所有成功加载的用户。</p>
<pre><code class="language-javascript">Promise.allSettled([
  getUser(1),
  getUser(2),
  getUser(3)
]).then(results =&gt; {
   const users = results
     .filter(result =&gt; result.status === 'fulfilled')
     .map(result =&gt; result.value);
   console.log('Got users:', users);
});
</code></pre>
<p>尝试加载三个用户，并显示成功加载的用户</p>
<p>你可以创建一个通用的 <code>loadUsers</code> 函数，该函数接收一个用户 ID 数组，并且并行加载用户。该函数返回一个 Promise，当所有用户成功加载时，该 Promise 将被兑现，并包含一个用户数组。</p>
<pre><code class="language-javascript">function getUsers(userIds) {
  return Promise.allSettled(userIds.map(id =&gt; getUser(id)))
    .then(results =&gt; {
      return results
        .filter(result =&gt; result.status === 'fulfilled')
        .map(result =&gt; result.value);
    });
}
</code></pre>
<p>一个辅助函数，用于并行加载多个用户，过滤掉任何失败的请求。</p>
<p>然后，你只需使用一个用户 ID 数组调用 <code>getUsers</code>：</p>
<pre><code class="language-javascript">getUsers([1, 2, 3])
	.then(users =&gt; console.log('Got users:', users));
</code></pre>
<p>使用 <code>getUsers</code> 辅助函数</p>
<h2 id="immediately-resolve-or-reject">如何创建立即兑现或拒绝的 Promise</h2>
<p>你希望将一个值包装在一个已兑现的 Promise 中。例如，你有一个返回 Promise 的异步函数，并且你提前知道值，不需要进行任何异步工作。</p>
<p>可以调用 <code>Promise.resolve</code> 并且指定一个值，这会返回一个立即兑现的 Promise，并带有你指定的值：</p>
<pre><code class="language-javascript">Promise.resolve('hello')
  .then(result =&gt; {
    console.log(result); // prints "hello"
  });
</code></pre>
<p>使用 <code>Promise.resolve</code></p>
<p>这等同于：</p>
<pre><code class="language-javascript">new Promise(resolve =&gt; {
   resolve('hello'); 
}).then(result =&gt; {
    console.log(result); // also prints "hello"
});
</code></pre>
<p>你可以创建一个立即兑现的 Promise 并在这种情况下返回来使你的 API 更加一致。这样，调用你函数的代码就知道无论如何总是会得到一个 Promise。</p>
<p>例如，考虑之前定义的 <code>getUsers</code> 函数。如果用户 ID 数组为空，你可以简单地返回一个空数组，因为不会加载任何用户。</p>
<pre><code class="language-javascript">function getUsers(userIds) {
  // immediately return the empty array
  if (userIds.length === 0) {
    return Promise.resolve([]);
  }
    
  return Promise.allSettled(userIds.map(id =&gt; getUser(id)))
    .then(results =&gt; {
      return results
        .filter(result =&gt; result.status === 'fulfilled')
        .map(result =&gt; result.value);
    });
}
</code></pre>
<p>在 <code>getUsers</code> 辅助函数中添加一个提前返回的功能</p>
<p><code>Promise.resolve</code> 的另一个用途是处理你可能获得一个值，该值可能是也可能不是一个 Promise，但你想总是将它视为一个 Promise。</p>
<p>你可以安全地对任何值调用 <code>Promise.resolve</code>。如果它已经是一个 Promise，你将得到另一个具有相同兑现值或拒绝值的 Promise。如果它不是一个 Promise，它将被包装在一个立即兑现的 Promise 中。</p>
<p>这种方法的好处是你不必做这样的事情：</p>
<pre><code class="language-javascript">function getResult(result) {
  if (result.then) {
     result.then(value =&gt; {
         console.log('Result:', value);
     });
  } else {
      console.log('Result:', result);
  }
}
</code></pre>
<p>根据是否是 Promise 有条件地调用 <code>then</code></p>
<p>同样，你可以使用 <code>Promise.reject</code> 创建一个立即拒绝的 Promise。再回到 <code>getUsers</code> 函数，如果用户 ID 数组是 <code>null</code>、<code>undefined</code> 或不是数组，我们希望立即拒绝。</p>
<pre><code class="language-javascript">function getUsers(userIds) {
  if (userIds == null || !Array.isArray(userIds)) {
    return Promise.reject(new Error('User IDs must be an array'));
  }
    
  // immediately return the empty array
  if (userIds.length === 0) {
    return Promise.resolve([]);
  }
    
  return Promise.allSettled(userIds.map(id =&gt; getUser(id)))
    .then(results =&gt; {
      return results
        .filter(result =&gt; result.status === 'fulfilled')
        .map(result =&gt; result.value);
    });
}
</code></pre>
<p>如果参数不是一个有效数组则返回一个错误对象</p>
<h3 id="promiserace">如何使用 <code>Promise.race</code></h3>
<p>就像 <code>Promise.all</code> 或 <code>Promise.allSettled</code> 一样，<code>Promise.race</code> 静态方法接收一个 Promise 数组，并返回一个新的 Promise。只是它的工作方式有些不同。</p>
<p><code>Promise.race</code> 返回的 Promise 会等待给定的 Promise 中第一个被兑现或被拒绝的 Promise，然后该 Promise 也会以相同的值被兑现或被拒绝。当这种情况发生时，其他 Promise 的兑现或拒绝值将会丢失。</p>
<h3 id="promiseany">如何使用 <code>Promise.any</code></h3>
<p><code>Promise.any</code> 的工作方式与 <code>Promise.race</code> 类似，但有一个关键区别 —— <code>Promise.race</code> 会在任何一个 Promise 被兑现或被拒绝时完成，而 <code>Promise.any</code> 会等待第一个被 <em>兑现的</em> Promise。</p>
<h2 id="use-async-and-await">如何使用 `async` 和 `await`</h2>
<p><code>async</code> 和 <code>await</code> 是简化处理 Promise 的特殊关键字。它们消除了对回调函数和调用 <code>then</code> 或 <code>catch</code> 的需求。它们也可以与 try-catch 模块一起使用。</p>
<p>它们的工作原理如下。你无需在 Promise 上调用 <code>then</code>，而是通过在它前面加上 <code>await</code> 关键词来“等待”它。这实际上会“暂停”函数的执行，直到 Promise 被兑现。</p>
<p>以下是一个标准 Promise 的例子：</p>
<pre><code class="language-javascript">getUsers().then(users =&gt; {
    console.log('Got users:', users);
});
</code></pre>
<p>使用 <code>then</code> 等待一个 promise</p>
<p>以下是使用 <code>await</code> 关键字的等效代码：</p>
<pre><code class="language-javascript">const users = await getUsers();
console.log('Got users:', users);
</code></pre>
<p>使用 <code>await</code> 等待一个 promise</p>
<p>Promise 连续操作也更加简洁：</p>
<pre><code class="language-javascript">const data = await readFile('sourceData.json');
const result = await processData(data);
await writeFile(result, 'processedData.json');
</code></pre>
<p>使用 <code>await</code> 链接 promise</p>
<p>记住，每次使用 <code>await</code> 都会暂停函数的其余部分的执行，直到你等待的 Promise 被兑现。如果你想等待多个并行运行的 Promise，你可以使用 <code>Promise.all</code>：</p>
<pre><code class="language-javascript">const users = await Promise.all([getUser(1), getUser(2), getUser(3)]);
</code></pre>
<p>使用 <code>await</code> 的 Promise.all</p>
<p>要使用 <code>await</code> 关键字，你的函数必须标记为异步函数。你可以在函数前面加上 <code>async</code> （异步）关键字来实现这一点：</p>
<pre><code class="language-javascript">async function processData(sourceFile, outputFile) {
  const data = await readFile(sourceFile);
  const result = await processData(data);
  writeFile(result, outputFile);
}
</code></pre>
<p>标记函数为 <code>async</code></p>
<p>添加 <code>async</code> 关键字还对函数产生了另一个重要影响。异步函数始终会隐匿地返回一个 Promise。如果你从异步函数中返回一个值，该函数实际上会返回一个以该值兑现的 Promise。</p>
<pre><code class="language-javascript">async function add(a, b) {
  return a + b;   
}

add(2, 3).then(sum =&gt; {
   console.log('Sum is:', sum); 
});
</code></pre>
<p>一个 <code>async</code> 函数用于求两个数字的和</p>
<p>在上面的例子中，该函数返回两个参数 <code>a</code> 和 <code>b</code> 的 <em>和</em>。但由于它是一个 <code>async</code> 函数，它不会直接返回 <em>和</em>，而是返回一个带有 <em>和</em> 的被兑现的 Promise 。</p>
<h3 id="asyncawait">使用 <code>async</code> 和 <code>await</code> 进行错误处理</h3>
<p>我们使用 <code>await</code> 来等待 Promise 被兑现，但是如果你等待一个 Promise，并且它被拒绝了，就会抛出一个错误。你可以将其放在一个 try-catch 模块中来处理错误：</p>
<pre><code class="language-javascript">try {
    const data = await readFile(sourceFile);
    const result = await processData(data);
    await writeFile(result, outputFile);
} catch (error) {
    console.error('Error occurred while processing:', error);
}
</code></pre>
<p>使用 try-catch 模块进行错误处理</p>
<h2 id="anti-pattern">Promise 的反模式</h2>
<h3 id="promise">不需要创建新 Promise 的情况</h3>
<p>有时候无法避免创建新的 Promise。但是，如果已经有 API 返回的 Promise 你就不需要创建自己的：</p>
<pre><code class="language-javascript">function getUsers() {
  return new Promise(resolve =&gt; {
     fetch('https://example.com/api/users')
       .then(result =&gt; result.json())
       .then(data =&gt; resolve(data))
  });
}
</code></pre>
<p>创建不必要的 Promise 的例子</p>
<p>在这个例子中，我们创建一个新的 Promise 来包装 Fetch API，而 Fetch API 已经返回 Promise。这是不必要的。我们可以直接返回 Fetch API 的 Promise 链式调用：</p>
<pre><code class="language-javascript">function getUsers() {
  return fetch('https://example.com/api/users')
    .then(result =&gt; result.json());
}
</code></pre>
<p>使用现有的 Fetch promise</p>
<p>在这两种情况下，调用 <code>getUsers</code> 的代码看起来都是一样的：</p>
<pre><code class="language-javascript">getUsers()
  .then(users =&gt; console.log('Got users:', users))
  .catch(error =&gt; console.error('Error fetching users:', error));
   
</code></pre>
<p>任一种 <code>getUsers()</code> 函数实现都可以使用这段客户端代码来调用</p>
<h3 id="">吞噬错误</h3>
<p>来看这个版本的 <code>getUsers</code> 函数：</p>
<pre><code class="language-javascript">function getUsers() {
    return fetch('https://example.com/api/users')
    	.then(result =&gt; result.json())
    	.catch(error =&gt; console.error('Error loading users:', error));
}
</code></pre>
<p>吞噬 Fetch 错误</p>
<p>错误处理很好，对吧？如果我们调用这个 <code>getUsers</code> 函数，你可能会对结果感到惊讶：</p>
<pre><code class="language-javascript">getUsers()
  .then(users =&gt; console.log('Got users:', users))
  .catch(error =&gt; console.error('error:', error);)
</code></pre>
<p>调用 <code>getUsers</code></p>
<p>你可能期望这会打印出 "error（错误）"，但实际上它会打印出 "Got users: undefined"。这是因为 <code>catch</code> 调用"吞噬"了错误，并返回一个新的 Promise，该 Promise 用 <code>catch</code> 回调的返回值 <code>undefined</code>（<code>console.error</code> 返回 <code>undefined</code>）来兑现。你仍然会看到来自 <code>getUsers</code> 的 "Error loading users" 日志消息，但返回的 Promise 将会被兑现，而不是被拒绝。</p>
<p>如果你想在 <code>getUsers</code> 函数内部捕获错误并且仍然拒绝返回的 Promise，<code>catch</code> 处理程序需要返回一个被拒绝的 Promise。你可以通过使用 <code>Promise.reject</code> 来实现这一点。</p>
<pre><code class="language-javascript">function getUsers() {
  return fetch('https://example.com/api/users')
    .then(result =&gt; result.json())
    .catch(error =&gt; {
      console.error('Error loading users:', error);
      return Promise.reject(error);
    });
}
</code></pre>
<p>在处理错误后返回一个被拒绝的 Promise</p>
<p>现在你仍然会得到 "Error loading users（加载用户错误）" 的消息，但返回的 Promise 也会因错误而被拒绝。</p>
<h3 id="promise">Promise 的嵌套</h3>
<p>避免嵌套 Promise，尝试使用扁平化的 Promise 链式调用。</p>
<p>不要这样：</p>
<pre><code class="language-javascript">readFile(sourceFile)
  .then(data =&gt; {
    processData(data)
      .then(result =&gt; {
        writeFile(result, outputFile)
          .then(() =&gt; console.log('done');
      });
  });
</code></pre>
<p>应该这样：</p>
<pre><code class="language-javascript">readFile(sourceFile)
  .then(data =&gt; processData(data))
  .then(result =&gt; writeFile(result, outputFile))
  .then(() =&gt; console.log('done'));
</code></pre>
<h2 id="conclusion">总结</h2>
<p>以下是使用 Promise 的关键要点：</p>
<ul>
<li>Promise 可以是 pending（待定）、fulfilled（已兑现）或 rejected（已拒绝）</li>
<li>如果一个 Promise 被兑现（fulfilled）或被拒绝（rejected），它就是被解决了(settled),</li>
<li>使用 <code>then</code> 来获取 Promise 的兑现值（以及进行下一步的操作）</li>
<li>使用 <code>catch</code> 来处理错误</li>
<li>使用 <code>finally</code> 来执行在成功或错误情况下都需要的清理逻辑</li>
<li>链接 Promise 以按顺序执行异步任务</li>
<li>使用 <code>Promise.all</code> 来获得一个 Promise，当所有给定的 Promise 都被兑现时它就会被兑现（fulfilled），或当其中一个 Promise 被拒绝时它被拒绝（rejected）</li>
<li>使用 <code>Promise.allSettled</code> 来获得一个 Promise，当所有给定的 Promise 都被兑现（fulfilled）或被拒绝（rejected）时它被解决</li>
<li>使用 <code>Promise.race</code> 来获得一个 Promise，当给定的 Promise 中第一个被兑现或被拒绝时它也被兑现或被拒绝</li>
<li>使用 <code>Promise.any</code> 来获得一个 Promise，当给定的 Promise 中第一个被兑现时它被兑现</li>
<li>使用 <code>await</code> 关键字来等待一个 Promise 的兑现值</li>
<li>使用 try-catch 模块来处理使用 <code>await</code> 关键字时产生的错误</li>
<li>使用 <code>await</code> 的函数必须使用 <code>async</code> 关键字</li>
</ul>
<p>感谢阅读这篇关于 Promise 的深入探讨。希望你学到了新知识！</p>
<!--kg-card-end: markdown--><p>感谢 <a href="https://www.freecodecamp.org/chinese/news/author/mingxun/">Mingxun Liu</a> 校对这篇译文。</p> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ JavaScript 删除字符串中的字符 ]]>
                </title>
                <description>
                    <![CDATA[ 操作字符串是编程的一项基本技能。 用 JavaScript 编写代码时可能会遇到的一个常见任务是从字符串中修剪字符。修剪包括删除字符串开头和/或末尾的特定字符。这些字符可以是前后空格、制表符、换行符或末尾逗号。 你可能还想删除程序中不需要的特定字符，并用其他字符代替。 在本文中，你将了解到一些在 JavaScript 中修剪和删除字符串中的字符的方法。 如何使用 JavaScript 中的 trim() 方法删除空格 你可以使用内置的 trim() 方法删除字符串开头和末尾的空格。 下面是 trim() 方法的一般语法： string.trim() 直接在要修剪的字符串上调用 trim() 方法。该方法会删除字符串开头和末尾的空格，如空格、制表符或换行符。 请注意，trim() 方法不会修改原始字符串。相反，它会返回一个经过修剪的新字符串，并去掉开头和末尾的字符。 下面是一个例子： let greeting = "    Hello World    "; let trimmedGreeting = greeting.trim(); console.log(t ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/javascript-remove-char-from-string/</link>
                <guid isPermaLink="false">664ab00887b80103d37baeea</guid>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Miya Liu ]]>
                </dc:creator>
                <pubDate>Mon, 20 May 2024 03:18:34 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2024/05/1d75957a397f479d41bc73b407025508.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>原文：</strong> <a href="https://www.freecodecamp.org/news/javascript-remove-char-from-string/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">JS Remove Char from String – How to Trim a Character from a String in JavaScript</a>
      </p><p>操作字符串是编程的一项基本技能。</p><p>用 JavaScript 编写代码时可能会遇到的一个常见任务是从字符串中修剪字符。修剪包括删除字符串开头和/或末尾的特定字符。这些字符可以是前后空格、制表符、换行符或末尾逗号。</p><p>你可能还想删除程序中不需要的特定字符，并用其他字符代替。</p><p>在本文中，你将了解到一些在 JavaScript 中修剪和删除字符串中的字符的方法。</p><h2 id="-javascript-trim-">如何使用 JavaScript 中的 trim() 方法删除空格</h2><p>你可以使用内置的 <code>trim()</code> 方法删除字符串开头和末尾的空格。</p><p>下面是 <code>trim()</code> 方法的一般语法：</p><pre><code class="language-javascript">string.trim()
</code></pre><p>直接在要修剪的字符串上调用 <code>trim()</code> 方法。该方法会删除字符串开头和末尾的空格，如空格、制表符或换行符。</p><p>请注意，<code>trim()</code> 方法不会修改原始字符串。相反，它会返回一个经过修剪的新字符串，并去掉开头和末尾的字符。</p><p>下面是一个例子：</p><pre><code class="language-javascript">let greeting = "    Hello World    ";

let trimmedGreeting = greeting.trim();

console.log(trimmedGreeting); // Output: "Hello World"
</code></pre><p>在上面的示例中，我声明了一个名为 <code>greeting</code> 的变量，并赋值一个字符串值 <code>Hello World</code>。字符串的开头和末尾都有空格。</p><p>为了删除开头和末尾的空格，我在 <code>greeting</code> 变量上调用了 <code>trim()</code> 方法，并将结果存储在一个新变量 <code>trimmedGreeting</code> 中。</p><p>当我使用 <code>console.log()</code> 将新字符串打印到控制台时，开头的空格和末尾的空格已被删除。</p><p>而 <code>greeting</code> 中的原始字符串仍然包含开头和末尾的空格。</p><h3 id="-javascript-trimstart-">如何使用 JavaScript 中的 trimStart() 方法删除开头的空格</h3><p>要删除 JavaScript 中字符串开头的空格，可以使用 <code>trimStart()</code> 方法。</p><p>让我们看看下面的例子：</p><pre><code class="language-javascript">let greeting = "    Hello World    ";

let trimmedGreeting = greeting.trimStart();

console.log(trimmedGreeting); // Output: "Hello World    "
</code></pre><p>在上面的示例中，<code>greeting</code> 变量的字符串值是 <code>Hello World</code>。</p><p>我在 <code>greeting</code> 上调用了 <code>trimStart()</code> 方法，并将结果保存在 <code>trimmedGreeting</code> 中。该方法只删除字符串开头的空格。字符串末尾的空格将被保留。</p><h3 id="-javascript-trimend-">如何使用 JavaScript 中的 trimEnd() 方法删除末尾空格</h3><p>要在 JavaScript 中删除字符串末尾的空格，可以使用 <code>trimEnd()</code> 方法。</p><p>让我们看看下面的例子：</p><pre><code class="language-javascript">let greeting = "    Hello World    ";

let trimmedGreeting = greeting.trimEnd();

console.log(trimmedGreeting); // Output: "    Hello World"
</code></pre><p>存储在 <code>greeting</code> 中的字符串开头和末尾都有空格。</p><p>我在 <code>greeting</code> 上调用了 <code>trimEnd()</code> 方法，并将结果保存在 <code>trimmedGreeting</code> 中。该方法只删除字符串末尾的空格。字符串开头的空格将被保留。</p><h2 id="-javascript-replace-">如何使用 JavaScript 中的 replace() 方法删除字符</h2><p>要从 JavaScript 字符串中删除特定字符，可以使用 <code>replace()</code> 方法。</p><p>以下是 <code>replace()</code> 方法的一般语法：</p><pre><code class="language-javascript">string.replace(pattern, replacement);
</code></pre><p>你需要在想要修改的字符串上调用 <code>replace()</code> 方法。该方法接受两个参数：<code>pattern</code> 和 <code>replacement</code>。</p><p><code>pattern</code> 参数指定要查找并替换的字符串模式。这可以是一个特定的字符、子串或正则表达式模式，你想在字符串中找到并替换它们。</p><p><code>replacement</code> 参数是要把 <code>pattern</code> 替换成的新字符或字符串。</p><p>请注意，<code>replace()</code> 方法不会修改原始字符串。</p><p>让我们来看一个示例：</p><pre><code class="language-javascript">let sentence = "I love dogs";

let modifiedSentence  = sentence.replace("dogs", "cats");

console.log(modifiedSentence); // Output: "I love cats"
</code></pre><p>我首先声明了一个名为 <code>sentence</code> 的变量，并赋值给字符串 <code>I love dogs</code>。</p><p>然后，我在 <code>sentence</code> 上调用 <code>replace()</code> 方法，因为我想删除子串 <code>dogs</code>，代之以 <code>cats</code>。然后，我将结果存储在新变量 <code>modifiedSentence</code> 中。</p><p>最后，我将存储在 <code>modifiedSentence</code> 变量中的字符串记录到控制台。<code>"I love cats"</code> 字符串被打印到控制台。</p><p><code>replace()</code> 方法找到了子串 <code>dogs</code>，并将其替换为 <code>cats</code>，而没有改变任何其他内容。</p><h2 id="-javascript-replace--1">如何使用 JavaScript 中的 replace() 方法删除一个字符的多个实例</h2><p>在上一节中，我们举例说明了如何使用 <code>replace()</code> 方法将一个单词替换为另一个单词。</p><p>但是，如果要替换的单词出现了多次，该怎么办呢？</p><pre><code class="language-javascript">let sentence = "I love dogs because dogs are cute";

let modifiedSentence  = sentence.replace("dogs", "cats");

console.log(modifiedSentence); // Output: "I love cats because dogs are cute"
</code></pre><p>在上面的例子中，<code>sentence</code> 变量中有两次出现了 <code>dogs</code> 这个词，我想用 <code>cats</code> 来替换它们。但是，<code>replace()</code> 方法默认只替换第一次出现的 <code>dogs</code>。</p><p>使用 <code>replace()</code> 方法删除一个单词的多个实例则有些不同。你可以使用正则表达式来实现这一目的。</p><p>让我们使用正则表达式重写代码：</p><pre><code class="language-javascript">let sentence = "I love dogs because dogs are cute";

let modifiedSentence  = sentence.replace(/dogs/g, "cats");

console.log(modifiedSentence); // Output: "I love cats because cats are cute"
</code></pre><p>在上面的示例中，我将所有出现的字符串 <code>dogs</code> 替换为字符串 <code>cats</code>。</p><p>我没有将字符串作为第一个参数传递给 <code>replace()</code>，而是传递了使用 <code>g</code> 标志的正则表达式 <code>/dogs/g</code>。这个标志代表 <code>global</code>，它匹配所有出现的单词 <code>dogs</code>，而不仅仅是第一个。</p><h2 id="-">小结</h2><p>在本文中，我们学习了在 JavaScript 中修剪字符串和替换字符串中的字符的基础知识。</p><p>具体来说，你学会了如何使用 <code>trim()</code> 方法修剪开头和末尾的空格，并了解了 <code>trimStart()</code> 和 <code>trimEnd()</code> 方法分别用于只删除开头或结尾的空格。</p><p>最后，你还学会了如何使用 <code>replace()</code> 方法删除特定字符并用其他字符替换。</p><p>感谢你阅读本文，祝你编码愉快！</p> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ JavaScript 刷新页面 ]]>
                </title>
                <description>
                    <![CDATA[ JavaScript 是一种多用途编程语言，允许开发人员创建动态和交互式 Web 应用程序。Web 开发中的一项常见任务是刷新或重新加载网页，以更新网页内容或触发某些操作。 在本文中，我们将探讨用 JavaScript 刷新页面的不同方法，并了解每种方法的优缺点。 为什么要在 JavaScript 中刷新页面 刷新网页在各种情况下都很有用，例如：  * 内容更新：如果网页上的内容是动态的且经常变化，则可能需要刷新页面以显示最新的数据或信息。这种情况常见于新闻网站、股票市场跟踪器、天气应用程序等。  * 提交表单：在网页上提交表单后，可能需要刷新页面以显示成功信息或重置表单以进行新的提交。  * 状态重置：在某些情况下，你可能想重置网页的状态或清除某些数据以重新开始。刷新页面可以实现这一目的。 现在，让我们来探索用 JavaScript 刷新页面的不同方法。 方法 1：如何使用 location.reload() 刷新页面 在 JavaScript 中刷新页面的最简单方法是使用 location.reload() 方法。该方法会从服务器重新加载当前网页，丢弃当前内容并加载最新内 ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/javascript-refresh-page-how-to-reload-a-page-in-js/</link>
                <guid isPermaLink="false">66447d5c87b80103d37bae15</guid>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Miya Liu ]]>
                </dc:creator>
                <pubDate>Wed, 15 May 2024 05:14:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2024/05/cover-template--8-.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>原文：</strong> <a href="https://www.freecodecamp.org/news/javascript-refresh-page-how-to-reload-a-page-in-js/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">JavaScript Refresh Page – How to Reload a Page in JS</a>
      </p><p>JavaScript 是一种多用途编程语言，允许开发人员创建动态和交互式 Web 应用程序。Web 开发中的一项常见任务是刷新或重新加载网页，以更新网页内容或触发某些操作。</p><p>在本文中，我们将探讨用 JavaScript 刷新页面的不同方法，并了解每种方法的优缺点。</p><h2 id="-javascript-">为什么要在 JavaScript 中刷新页面</h2><p>刷新网页在各种情况下都很有用，例如：</p><ul><li><strong>内容更新：</strong>如果网页上的内容是动态的且经常变化，则可能需要刷新页面以显示最新的数据或信息。这种情况常见于新闻网站、股票市场跟踪器、天气应用程序等。</li><li><strong>提交表单：</strong>在网页上提交表单后，可能需要刷新页面以显示成功信息或重置表单以进行新的提交。</li><li><strong>状态重置：</strong>在某些情况下，你可能想重置网页的状态或清除某些数据以重新开始。刷新页面可以实现这一目的。</li></ul><p>现在，让我们来探索用 JavaScript 刷新页面的不同方法。</p><h2 id="-1-location-reload-">方法 1：如何使用 location.reload() 刷新页面</h2><p>在 JavaScript 中刷新页面的最简单方法是使用 <code>location.reload()</code> 方法。该方法会从服务器重新加载当前网页，丢弃当前内容并加载最新内容。</p><pre><code class="language-js">// 刷新页面
location.reload();
</code></pre><h3 id="-location-reload-">使用 location.reload() 的优点</h3><ul><li>简单明了，易于使用。</li><li>它能从服务器重新加载整个页面，确保你获得最新的内容。</li></ul><h3 id="-location-reload--1">使用 location.reload() 的缺点</h3><ul><li>它会丢弃页面的当前内容，可能导致用户输入或数据丢失。</li><li>重新加载页面时可能会产生闪烁效果，影响用户体验。</li></ul><h2 id="-2-location-replace-">方法 2：如何使用 location.replace() 刷新页面</h2><p>在 JavaScript 中刷新页面的另一种方法是使用 <code>location.replace()</code> 方法。该方法会用新的 URL 替换网页的当前 URL，从而有效地用新内容重新加载网页。</p><p>在控制台中尝试此方法时，会发现它显示的是当前的 URL：</p><pre><code class="language-js">console.log(location.href)
</code></pre><p>这意味着，当你使用 <code>location.replace()</code> 方法将网页的当前 URL 替换为新的 URL（相同的 URL）时，你的网页将刷新。</p><pre><code class="language-js">// 将 URL 替换为目标页面的 URL，刷新页面
location.replace(location.href);
</code></pre><h3 id="-location-replace-">使用 location.replace() 的优点</h3><ul><li>这是一种用新内容重新加载页面的快速方法。</li><li>它保留了页面的当前内容，只替换了 URL，避免了用户输入或数据的丢失。</li></ul><h3 id="-location-replace--1">使用 location.replace() 的缺点</h3><ul><li>它会替换页面的整个 URL，这可能会导致当前浏览历史记录丢失。</li><li>在某些情况下可能不起作用，例如当网页是在新窗口或标签页中打开时。</li></ul><h2 id="-3-location-reload-true-">方法 3：如何使用 location.reload(true) 重载网页</h2><p><code>location.reload()</code> 方法还接受一个布尔参数 <code>forceGet</code>，当参数设置为 <code>true</code> 时，会强制从服务器重新加载网页，绕过缓存。</p><p>当你想确保从服务器获取最新内容时，即使页面已被缓存，这个参数也很有用。</p><pre><code class="language-js">// 刷新页面并绕过缓存
location.reload(true);
</code></pre><h3 id="-location-reload-true-">使用 location.reload(true) 的优点</h3><ul><li>即使页面已缓存，也能确保从服务器获取最新内容。</li></ul><h3 id="-location-reload-true--1">使用 location.reload(true) 的缺点</h3><ul><li>会丢弃页面的当前内容，导致用户输入或数据丢失。</li><li>重新加载页面时可能会产生闪烁效果，影响用户体验。</li></ul><h2 id="-4-location-href-">方法 4：如何使用 location.href 刷新页面</h2><p>在 JavaScript 中刷新页面的另一种方法是使用 <code>location.href</code> 属性将网页的 URL 设置为自己的 URL。这实际上是用新的 URL 重新加载页面，从而触发页面刷新。</p><pre><code class="language-js">// 将 URL 替换为目标页面的 URL，刷新页面
location.href = location.href;
</code></pre><h3 id="-location-href-">使用 location.href 的优点</h3><ul><li>这是一种简单有效的刷新页面方法。</li><li>它保留了页面的当前内容，只更新 URL，避免了用户输入或数据的丢失。</li></ul><h3 id="-location-href--1">使用 location.href 的缺点</h3><ul><li>它会替换页面的整个 URL，这可能会导致当前浏览历史记录丢失。</li><li>在某些情况下可能不起作用，例如当网页是在新窗口或标签页中打开时。</li></ul><h2 id="-5-location-reload-">方法 5：如何使用带有延迟的 location.reload() 来刷新页面</h2><p>如果想在刷新页面前添加延迟，可以将 <code>setTimeout()</code> 函数与 <code>location.reload()</code> 方法结合使用。</p><p>这样就可以在重新加载页面前指定一个以毫秒为单位的时间间隔，从而控制刷新的时间。</p><pre><code class="language-js">// 延迟 3 秒后刷新页面
setTimeout(function(){
    location.reload();
}, 3000); // 3000 milliseconds = 3 seconds
</code></pre><h3 id="-location-reload--2">使用带延迟的 location.reload() 的优点</h3><ul><li>它允许你通过添加延迟来控制页面刷新的时间。</li><li>在需要在特定事件或操作后刷新页面的情况下，它提供了灵活性。</li></ul><h3 id="-location-reload--3">使用带延迟的 location.reload() 的缺点</h3><ul><li>可能会导致页面刷新延迟，从而影响用户体验。</li><li>在网络连接不稳定或速度较慢的情况下，可能无法达到预期效果。</li></ul><h2 id="-"><strong>总结</strong></h2><p>在本文中，你了解了在 JavaScript 中刷新页面的不同方法。每种方法都有其优缺点，因此你可以更容易地为自己的 Web 开发项目选择最佳方法。</p><p>在使用这些方法刷新页面时，重要的是要考虑对用户体验、数据丢失和浏览历史记录的影响。</p><p>希望这篇文章能帮助你理解如何用 JavaScript 重新加载网页，并根据具体的使用情况选择合适的方法。</p><p>祝你编码愉快！</p><p>踏上学习之旅，<a href="https://joelolawanle.com/contents">浏览 200 多篇有关 Web 开发的专业文章</a>。查看<a href="https://joelolawanle.com/posts">我的博客</a>，获取更多精彩内容。</p> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 如何在 JavaScript 中检查对象是否为空 ]]>
                </title>
                <description>
                    <![CDATA[ 对象是编程中最常用的数据类型之一。对象是以键值对形式存储的相关数据的集合，例如： let userDetails = {   name: "John Doe",   username: "jonnydoe",   age: 14, } 在处理对象时，你可能需要在执行函数之前检查对象是否为空。 在 JavaScript 中，有多种方法可以检查对象是否为空。在本文中，你将了解到检查对象是否为空的各种方法、可附加的选项以及原因。 注意：当一个对象没有键值对时，它就被认为是空的。 如果你赶时间，下面是一个基本示例： const myEmptyObj = {}; // 与新浏览器配合使用效果最佳 Object.keys(myEmptyObj).length === 0 && myEmptyObj.constructor === Object // 适用于所有浏览器 _.isEmpty(myEmptyObj) 这两种方法都将返回 true。现在让我们来了解一下 JavaScript 中用于检查对象是否为空的这些方法和更多选项。 如何使用 Object.keys() 检查对象 ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/check-if-an-object-is-empty-in-javascript/</link>
                <guid isPermaLink="false">663c942575e3010487da55ac</guid>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Miya Liu ]]>
                </dc:creator>
                <pubDate>Thu, 09 May 2024 04:12:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2024/05/cover-template--21-.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>原文：</strong> <a href="https://www.freecodecamp.org/news/check-if-an-object-is-empty-in-javascript/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">How to Check if an Object is Empty in JavaScript – JS Java isEmpty Equivalent</a>
      </p><p>对象是编程中最常用的数据类型之一。对象是以键值对形式存储的相关数据的集合，例如：</p><pre><code class="language-js">let userDetails = {
  name: "John Doe",
  username: "jonnydoe",
  age: 14,
}
</code></pre><p>在处理对象时，你可能需要在执行函数之前检查对象是否为空。</p><p>在 JavaScript 中，有多种方法可以检查对象是否为空。在本文中，你将了解到检查对象是否为空的各种方法、可附加的选项以及原因。</p><p><strong>注意：</strong>当一个对象没有键值对时，它就被认为是空的。</p><p>如果你赶时间，下面是一个基本示例：</p><pre><code class="language-js">const myEmptyObj = {};

// 与新浏览器配合使用效果最佳
Object.keys(myEmptyObj).length === 0 &amp;&amp; myEmptyObj.constructor === Object

// 适用于所有浏览器
_.isEmpty(myEmptyObj)
</code></pre><p>这两种方法都将返回 <code>true</code>。现在让我们来了解一下 JavaScript 中用于检查对象是否为空的这些方法和更多选项。</p><h2 id="-object-keys-">如何使用 Object.keys() 检查对象是否为空</h2><p><code>Object.keys()</code> 方法是 ECMAScript6（ES6）中引入的静态对象方法，所有现代浏览器都支持该方法。该方法返回一个包含对象键的数组，例如：</p><pre><code class="language-js">let userDetails = {
  name: "John Doe",
  username: "jonnydoe",
  age: 14
};

console.log(Object.keys(userDetails)); // ["name","username","age"]
</code></pre><p>这样，你就可以应用 <code>.length</code> 属性了。如果返回零（0），则对象为空。</p><pre><code class="language-js">let userDetails = {
  name: "John Doe",
  username: "jonnydoe",
  age: 14
};

let myEmptyObj = {};

console.log(Object.keys(userDetails).length); // 3
console.log(Object.keys(myEmptyObj).length); // 0
</code></pre><p>现在，你可以使用此方法通过 if 语句或创建一个函数来检查对象是否为空。</p><pre><code class="language-js">const isObjectEmpty = (objectName) =&gt; {
  return Object.keys(objectName).length === 0
}
</code></pre><p>这将返回 <code>true</code> 或 <code>false</code>。如果对象为空，则返回 <code>true</code>，否则返回 <code>false</code>。</p><pre><code class="language-js">let userDetails = {
  name: "John Doe",
  username: "jonnydoe",
  age: 14
};

let myEmptyObj = {};

const isObjectEmpty = (objectName) =&gt; {
  return Object.keys(objectName).length === 0
}

console.log(isObjectEmpty(userDetails)); // false
console.log(isObjectEmpty(myEmptyObj)); // true
</code></pre><p><strong>注意：</strong>在检查对象是否为空或任何数据类型时，仅检查长度并不是最佳选择。最好是先确认数据类型是否正确。</p><p>为此，可以使用构造函数检查：</p><pre><code class="language-js">const isObjectEmpty = (objectName) =&gt; {
  return Object.keys(objectName).length === 0 &amp;&amp; objectName.constructor === Object;
}
</code></pre><p>这样，你就可以进行更彻底的检查。</p><p>到目前为止，一切正常。但是你可能还想避免在变量是 <code>undefined</code> 或传递的值为 <code>null</code> 而不是 <code>{}</code> 时抛出 <code>TypeError</code>。为了解决这个问题，你可以增加一个额外的检查：</p><pre><code class="language-js">const isObjectEmpty = (objectName) =&gt; {
  return (
    objectName &amp;&amp;
    Object.keys(objectName).length === 0 &amp;&amp;
    objectName.constructor === Object
  );
};
</code></pre><p>在上面的代码中，增加了一个额外的检查。这意味着，如果不是空对象，它将返回 <code>null</code> 或 <code>undefined</code>，如下例所示：</p><pre><code class="language-js">let userDetails = {
  name: "John Doe",
  username: "jonnydoe",
  age: 14
};

let myEmptyObj = {};
let nullObj = null;
let undefinedObj;

const isObjectEmpty = (objectName) =&gt; {
  return (
    objectName &amp;&amp;
    Object.keys(objectName).length === 0 &amp;&amp;
    objectName.constructor === Object
  );
};

console.log(isObjectEmpty(userDetails)); // false
console.log(isObjectEmpty(myEmptyObj)); // true
console.log(isObjectEmpty(undefinedObj)); // undefined
console.log(isObjectEmpty(nullObj)); // null
</code></pre><p><strong>注：</strong>这也适用于其他对象静态方法，这意味着你可以使用 <code>Object.entries()</code> 或 <code>Object.values()</code> 代替 <code>Object.keys()</code>。</p><p>如何使用 for...in 循环检查对象是否为空</p><p>你可以使用的另一种方法是 ES6 <code>for…in</code> 循环。你可以将此循环与 <code>hasOwnProperty()</code> 方法一起使用。</p><pre><code class="language-js">const isObjectEmpty = (objectName) =&gt; {
  for (let prop in objectName) {
    if (objectName.hasOwnProperty(prop)) {
      return false;
    }
  }
  return true;
};
</code></pre><p>上述方法将循环遍历对象的每个属性。如果找到一个迭代，则说明对象不是空的。此外，<code>hasOwnProperty()</code> 将返回一个布尔值，表明对象是否将指定属性作为其属性。</p><pre><code class="language-js">let userDetails = {
  name: "John Doe",
  username: "jonnydoe",
  age: 14
};

let myEmptyObj = {};

const isObjectEmpty = (objectName) =&gt; {
  for (let prop in objectName) {
    if (objectName.hasOwnProperty(prop)) {
      return false;
    }
  }
  return true;
};

console.log(isObjectEmpty(userDetails)); // false
console.log(isObjectEmpty(myEmptyObj)); // true
</code></pre><h2 id="-json-stringify-">如何使用 JSON.stringify() 检查对象是否为空</h2><p>你还可以使用 <code>JSON.stingify()</code> 方法，该方法用于将 JavaScript 值转换为 JSON 字符串。这意味着它会将对象值转换为对象的字符串，例如：</p><pre><code class="language-js">let userDetails = {
  name: "John Doe",
  username: "jonnydoe",
  age: 14
};

console.log(JSON.stringify(userDetails)); 

Output:
"{'name':'John Doe','username':'jonnydoe','age':14}"
</code></pre><p>这意味着当对象为空时，它将返回 <code>"{}"</code>。你可以利用它来检查对象是否为空。</p><pre><code class="language-js">const isObjectEmpty = (objectName) =&gt; {
  return JSON.stringify(objectName) === "{}";
};
</code></pre><p>如果对象为空，则返回 <code>true</code>，否则返回 <code>false</code>：</p><pre><code class="language-js">let userDetails = {
  name: "John Doe",
  username: "jonnydoe",
  age: 14
};

let myEmptyObj = {};

const isObjectEmpty = (objectName) =&gt; {
  return JSON.stringify(objectName) === "{}";
};

console.log(isObjectEmpty(userDetails)); // false
console.log(isObjectEmpty(myEmptyObj)); // true
</code></pre><h2 id="-lodash-">如何使用 Lodash 检查对象是否为空</h2><p>最后，我在这里介绍的某些方法可能适用于旧版本的浏览器，而另一些方法则可能不起作用。如果你想找到一种既适用于旧版本浏览器又适用于现代浏览器的解决方案，可以使用 <a href="https://lodash.com/">Lodash</a>。</p><p>Lodash 是一个现代 JavaScript 工具库，可以用非常基本的语法实现许多 JavaScript 功能。</p><p>例如，如果要检查对象是否为空，只需使用 “isEmpty” 方法即可。</p><pre><code class="language-js">_.isEmpty(objectName);
</code></pre><p>在项目中安装 Lodash 非常简单。你只需使用此命令即可：</p><pre><code class="language-js">$ npm install lodash
</code></pre><p>现在，你可以初始化下划线方法来使用该方法。</p><pre><code class="language-js">const _ = require('lodash');

let userDetails = {
  name: "John Doe",
  username: "jonnydoe",
  age: 14
};

let myEmptyObj = {};

const isObjectEmpty = (objectName) =&gt; {
  return _.isEmpty(objectName);
};

console.log(isObjectEmpty(userDetails)); // false
console.log(isObjectEmpty(myEmptyObj)); // true
</code></pre><h2 id="-">就是这些 💪</h2><p>我喜欢探索检查对象是否为空的各种方法。请随意使用适合你的项目或任务的最佳方法。</p><p>祝你编码愉快！</p><p>踏上学习之旅，<a href="https://joelolawanle.com/contents">浏览 200 多篇有关 Web 开发的专业文章</a>。查看<a href="https://joelolawanle.com/posts">我的博客</a>，获取更多精彩内容。</p> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ JavaScript DOM 操作手册 ]]>
                </title>
                <description>
                    <![CDATA[ DOM 操作是在学习 JavaScript 中最令人兴奋的主题之一。这是因为 JavaScript 的主要用途之一就是使网页具有交互性，而 DOM 在这里起到了重要作用。 DOM 是一个非常强大的工具，允许你与网页上的元素进行交互和操作。本手册将帮助你理解并自信地使用 DOM。 你将从 DOM 是什么以及 DOM 可以用来做什么开始学习，然后我们将深入学习如何选择、修改和为 DOM 元素设置样式，你也将学习如何创建一个新元素并添加到你的网页上。 手册也包含了像是如何遍历 DOM、DOM 事件是什么，以及一些项目实践的想法等主题。 让我们开始吧！ 目录  * DOM 是什么 * DOM 可以用来做什么          * 如何选择 DOM 元素 * getElementById     * ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/the-javascript-dom-manipulation-handbook/</link>
                <guid isPermaLink="false">66278b8ee2f5d704bae5420a</guid>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Mingxun Liu ]]>
                </dc:creator>
                <pubDate>Tue, 23 Apr 2024 10:19:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2024/04/The-JavaScript-DOM-Manipulation-Handbook-Cover-1.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>原文：</strong> <a href="https://www.freecodecamp.org/news/the-javascript-dom-manipulation-handbook/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">The JavaScript DOM Manipulation Handbook</a>
      </p><!--kg-card-begin: markdown--><p>DOM 操作是在学习 JavaScript 中最令人兴奋的主题之一。这是因为 JavaScript 的主要用途之一就是使网页具有交互性，而 DOM 在这里起到了重要作用。</p>
<p>DOM 是一个非常强大的工具，允许你与网页上的元素进行交互和操作。本手册将帮助你理解并自信地使用 DOM。</p>
<p>你将从 DOM 是什么以及 DOM 可以用来做什么开始学习，然后我们将深入学习如何选择、修改和为 DOM 元素设置样式，你也将学习如何创建一个新元素并添加到你的网页上。</p>
<p>手册也包含了像是如何遍历 DOM、DOM 事件是什么，以及一些项目实践的想法等主题。</p>
<p>让我们开始吧！</p>
<h2 id="">目录</h2>
<ul>
<li><a href="#what-is-dom">DOM 是什么</a>
<ul>
<li><a href="#use-of-dom">DOM 可以用来做什么</a></li>
</ul>
</li>
<li><a href="#how-to-select-dom">如何选择 DOM 元素</a>
<ul>
<li><a href="#getElementById">getElementById</a></li>
<li><a href="#getElementsByClassName">getElementsByClassName</a></li>
<li><a href="#getElementsByTagName">getElementsByTagName</a></li>
<li><a href="#querySelector">querySelector</a></li>
<li><a href="#querySelectorAll">querySelectorAll</a></li>
</ul>
</li>
<li><a href="#changee-dom-content">如何更改 DOM 元素的内容</a>
<ul>
<li><a href="#innerhtml"><code>innerHTML</code> 属性</a></li>
<li><a href="#innerhtml-risk">使用 <code>innerHTML</code> 的安全风险</a></li>
<li><a href="#innertext-and-textcontent"><code>innerText</code> 和 <code>textContent</code> 属性</a></li>
</ul>
</li>
<li><a href="#handle-dom">如何处理 DOM 元素的属性</a>
<ul>
<li><a href="#getAttribute"><code>getAttribute()</code> 方法</a></li>
<li><a href="#setAttribute"><code>setAttribute()</code> 方法</a></li>
<li><a href="#removeAttribute"><code>removeAttribute()</code> 方法</a></li>
<li><a href="#hasAttribute"><code>hasAttribute()</code> 方法</a></li>
</ul>
</li>
<li><a href="#change-dom-style">如何更改 DOM 的样式</a>
<ul>
<li><a href="#use-style">使用 <code>.style</code> 属性设置样式</a></li>
<li><a href="#use-class">使用 <code>class</code> 设置样式</a></li>
</ul>
</li>
<li><a href="#loop-through-dom">如何遍历 DOM</a>
<ul>
<li><a href="#node-and-element-difference">节点和元素之间的不同</a></li>
<li><a href="#parentNode-or-parentElement">使用 <code>parentNode</code> 还是 <code>parentElement</code> 选择父级</a></li>
<li><a href="#childNodes-or-children">使用 <code>childNodes</code> 还是 <code>children</code> 选择子级</a></li>
<li><a href="#choose-first-or-last-element-node">选择第一个或是最后一个元素/节点</a></li>
<li><a href="#choose-sibling-node">在 DOM 中选择兄弟节点</a></li>
</ul>
</li>
<li><a href="#dom-event-and-event-listener">DOM 事件和事件监听器</a>
<ul>
<li><a href="#event-listener-and-handler">事件监听器与事件处理函数</a></li>
<li><a href="#register-events">JavaScript 中三种注册事件的方法</a></li>
<li><a href="#practice">实践挑战</a></li>
<li><a href="#solution">实践挑战的解决方案</a></li>
<li><a href="#event-object">事件对象</a></li>
<li><a href="#event-type">事件类型</a></li>
</ul>
</li>
<li><a href="#event-flow">JavaScript 的事件流</a>
<ul>
<li><a href="#event-bubbling">事件冒泡</a></li>
<li><a href="#event-capturing">事件捕获</a></li>
<li><a href="#event-stopPropagation-method">Event 的 <code>stopPropagation()</code> 方法</a></li>
</ul>
</li>
<li><a href="#JS-DOM-manipulation-projects-ideas">JavaScript DOM 项目</a></li>
<li><a href="#conclusion">总结</a></li>
</ul>
<h2 id="what-is-dom">DOM 是什么</h2>
<p>DOM 全称是文档对象模型（Document Object Model），但是这是什么意思呢？我们分开来看看。</p>
<p><strong>文档</strong>指的是你在浏览器上所看到的网页。具体来说，HTML 文档处理页面内容的结构，包括组成页面的文本、图片、链接和其他的元素。</p>
<p><strong>对象</strong>指的是像 img, h1, p 这样的元素被视为对象。每一个对象都有属性（类似 id、 class、style）和方法，利用这些属性和方法你就可以操作这些元素。</p>
<p><strong>模型</strong>指的是它是 HTML 文档的一种表示或副本，以分层树的形式呈现。这颗树包括了所有元素，并且有它们之间的父子关系。</p>
<p>浏览器会确保 DOM 与 HTML 文档同步，使它们总是保持一致。因此，如果 HTML 中的某些内容发生变化，DOM 也会相应变化，反之亦然。</p>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2024/01/JavaScript--2-.png" alt="HTML DOM 树" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>HTML DOM 树</figcaption>
</figure>
<p>在树的顶部是 Document 对象，它只有一个子元素 —— <code>html</code> 元素。<code>html</code> 元素也被称为根元素，它有 <code>head</code> 和 <code>body</code> 两个子元素，而每个子元素也都有它们自己的子元素。</p>
<p>元素之间的父子关系使你能够遍历、移动和选择它们。稍后会详细介绍。</p>
<h3 id="use-of-dom">DOM 可以用来做什么</h3>
<p>DOM 操作允许开发者与网页的结构、样式、内容交互。下面是一些你可以通过 DOM 用来做的事情：</p>
<ul>
<li>更改或移除 DOM 中存在的元素</li>
<li>创建并添加新元素到网页</li>
<li>更改一些元素的样式</li>
<li>给元素添加事件监听器让它们可交互</li>
</ul>
<h2 id="how-to-select-dom">如何选择 DOM 元素</h2>
<p>要对 DOM 元素执行操作，首先必须选择或访问相应的元素。在这一节，你将学到一些常见的方法去选择 DOM 元素。</p>
<p>让我们使用下面通讯录片段来展示各种 DOM 选择器方法如何工作。</p>
<pre><code class="language-HTML">  &lt;h1 id="page-title"&gt;Phonebook&lt;/h1&gt;
  
  &lt;p class="family"&gt;Marie&lt;/p&gt;
  &lt;p class="family"&gt;Jose&lt;/p&gt;
  &lt;p class="work"&gt;Anne&lt;/p&gt;
  &lt;p class="work"&gt;Joan&lt;/p&gt;
</code></pre>
<p>一个标题元素和四个段落元素的简单代码片段</p>
<p>这段代码包括了一个 id 为 <code>page-title</code> 的 <code>h1</code> 标题和四个 <code>p</code> 段落，前两个段落都具有 <code>family</code> 类，而后两个具有 <code>work</code> 类。</p>
<h3 id="getElementById">1. getElementById</h3>
<p>你可以使用这个方法去选择带有 id 属性的元素，id 是独一无二的标识符。例如，当一个 <code>h1</code> 元素有值为 <code>page-title</code> 的 id，页面上的其他元素不应该有相同值作为 id。</p>
<p>这意味着每当你使用 <code>getElementById()</code> 方法，你将只会从 DOM 中选择一个元素。</p>
<p>我们来看例子：</p>
<p>这个 <code>h1</code> 有值为 <code>page-title</code> 的 id，以下是使用 <code>getElementById()</code> 方法选择它的办法：</p>
<pre><code class="language-javascript">const titleElement = document.getElementById("page-title")
console.log(titleElement)
</code></pre>
<p>这里例子选择了 <code>h1</code> 元素并赋值给了变量 <code>titleElement</code>。</p>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2023/12/Screenshot-2023-12-02-at-9.01.01-AM.png" alt="使用 getElementById() 方法访问元素的结果" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>使用 getElementById() 方法访问元素的结果</figcaption>
</figure>
<p>如果 DOM 中没有给定 id 的元素，则 <code>getElementById()</code> 将返回 <code>null</code>。</p>
<h3 id="getElementsByClassName">2. getElementsByClassName</h3>
<p>你可以使用这个方法选择超过一个对象。这个方法获取 class 属性的值作为参数在 DOM 中选择给定 class 的所有元素。与 id 不一样，你可以在不同的 HTML 元素上给一个相同的 class。</p>
<p>看这个例子：</p>
<pre><code class="language-javascript">const famContacts = document.getElementsByClassName("family")
console.log(famContacts)
</code></pre>
<p>这返回了给定 class 的所有元素的 HTML 集合，控制台上如下所示：</p>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2023/12/Screenshot-2023-12-01-at-10.35.51-AM.png" alt="getElementsByClassName() 方法返回一个 HTML 集合" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>getElementsByClassName() 方法返回一个 HTML 集合</figcaption>
</figure>
<p>注意：HTML 集合看起来像是数组，但实际上不是。你可以使用括号符号访问元素，就像访问数组一样，但是不能使用像是 <code>map</code>、<code>filter</code> 或是 <code>forEach</code> 等数组方法。</p>
<pre><code class="language-javascript">console.log(famContacts[0]) 
</code></pre>
<p>这样会获得 HTML 集合中的第一个元素，也就是名字为 Marie 的段落。</p>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2023/12/Screenshot-2023-12-02-at-9.03.35-AM-1.png" alt="使用索引访问 HTML 集合中的元素" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>使用索引访问 HTML 集合中的元素</figcaption>
</figure>
<p>但是如果你想通过循环遍历 <code>famContacts</code> HTML 集合中的所有元素呢？你首先需要将 HTML 集合转换成数组，然后你就可以任意使用数组方法了。</p>
<p>一个简单的办法从 HTML 集合创建数组就是使用展开语法，像是这样：</p>
<pre><code class="language-javascript">let famContactsArray = [...famContacts]

famContactsArray.forEach(element =&gt; console.log(element))
</code></pre>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2023/12/Screenshot-2023-12-02-at-9.06.48-AM.png" alt="打印 HTML 集合中所有元素" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>打印 HTML 集合中所有元素</figcaption>
</figure>
<p>使用 <code>forEach</code> 方法，你可以访问 <code>famContactsArray</code> 中每一个子项。如果你没有从 HTML 集合创建一个数组，而试着将数组方法直接用于它浏览器将会抛出一个错误。</p>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2023/12/Screenshot-2023-12-01-at-11.57.27-AM.png" alt="当你将数组方法直接用于 HTML 集合的错误信息" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>当你将数组方法直接用于 HTML 集合的错误信息</figcaption>
</figure>
3. getElementsByTagName
<p>这个方法将通过标签名来选择元素。例如：<code>getElementByTagName('p')</code> 将选择页面中所有 <code>p</code> 标签。</p>
<p>像是 <code>getElementsByClassName()</code>，这个方法也返回一个被选择元素的 HTML 集合。</p>
<p>看这个例子:</p>
<pre><code class="language-javascript">const allContacts = document.getElementsByTagName('p')
console.log(allContacts)
</code></pre>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2023/12/Screenshot-2023-12-02-at-8.39.36-AM.png" alt="一个包含所有 p 标签的 HTML 集合" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>一个包含所有 p 标签的 HTML 集合</figcaption>
</figure>
<p>你可以从 HTML 集合创建一个数组，然后使用任意数组方法。</p>
<pre><code class="language-javascript">let allContactsArray = [...allContacts]

allContactsArray.map(element =&gt; console.log(element))
</code></pre>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2023/12/Screenshot-2023-12-02-at-9.08.26-AM.png" alt="在 allContactsArray 使用 map() 方法" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>在 allContactsArray 使用 map() 方法</figcaption>
</figure>
<blockquote>
<p>译者注：由于 <code>map()</code> 创建一个新数组，在没有使用返回的数组的情况下调用它是不恰当的；应该使用 <code>forEach</code> 或 <code>for...of</code> 作为代替。详见 <a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/map#%E6%8F%8F%E8%BF%B0"><code>Array.prototype.map()</code></a>。</p>
</blockquote>
<h3 id="querySelector">4. querySelector</h3>
<p>你可以使用这个方法在 DOM 中选择任意的 HTML 元素，它仅返回一个元素：第一个匹配选择器的元素。</p>
<p><code>querySelector()</code> 用法类似 CSS 选择器。</p>
<p>举个例子，当你想要选择一个具有 id 的元素时，你会怎么做？用 <code>#id</code>。那当你想要选择具有 class 的元素呢？你会用 <code>.class</code>。</p>
<p>看这个例子:</p>
<pre><code class="language-javascript">const firstWorkContact = document.querySelector('.work')
console.log(firstWorkContact)
</code></pre>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2023/12/Screenshot-2023-12-02-at-11.38.12-AM.png" alt="使用 querySelector() 的例子" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>使用 querySelector() 的例子</figcaption>
</figure>
<p>上边这个例子只获取到了一个具有 <code>work</code> 类的元素，其他的被忽略了。</p>
<p>让我们再看一个像是 CSS 选择器一样使用 <code>querySelector()</code> 的例子。下方 <code>div</code> 元素包含了 4 个 <code>button</code>。</p>
<pre><code class="language-HTML">&lt;div&gt;
    &lt;button&gt;First button&lt;/button&gt;
    &lt;button&gt;Second button&lt;/button&gt;
    &lt;button&gt;Third button&lt;/button&gt;
    &lt;button&gt;Fourth button&lt;/button&gt;
&lt;/div&gt;
</code></pre>
<p>假设你想要选择第三个按钮，你可以像下面那样使用 <code>querySelector()</code>。代码中使用了 CSS <code>nth-child</code> 选择器去获取 <code>div</code> 中的第三个 <code>button</code>。</p>
<pre><code class="language-javascript">const thirdBtn = document.querySelector('div button:nth-child(3)')
console.log(thirdBtn)
</code></pre>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2023/12/Screenshot-2023-12-02-at-2.42.48-PM.png" alt="使用 querySelector() 获取第三个按钮的结果" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>使用 querySelector() 获取第三个按钮的结果</figcaption>
</figure>
<p>但如果你想选择所有四个按钮而不只是第一个呢？你应该使用 <code>querySelectorAll()</code> 代替。</p>
<h3 id="querySelectorAll">5. querySelectorAll</h3>
<p>类似 <code>querySelector()</code>，<code>querySelectorAll</code> 也是使用 CSS 选择器去选择 HTML 元素。不同的是它返回匹配选择器的所有元素，而不仅仅是第一个。</p>
<p>让我们使用 <code>querySelectorAll()</code> 选择上一个示例中所有的按钮。</p>
<pre><code class="language-javascript">const allBtns = document.querySelectorAll('button')
console.log(allBtns)
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/12/Screenshot-2023-12-02-at-3.04.18-PM.png" alt="Screenshot-2023-12-02-at-3.04.18-PM" width="600" height="400" loading="lazy"></p>
<p><code>querySelectorAll()</code> 返回一个被选择元素组成的 NodeList。</p>
<p>注意：<code>querySelectorAll()</code> 返回一个 <code>NodeList</code>。<code>NodeList</code> 与 HTML 集合有点不同，你不需要转换成数组就可以使用 <code>forEach()</code> 方法。</p>
<pre><code class="language-javascript">allBtns.forEach(btn =&gt; console.log(btn))
</code></pre>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2023/12/Screenshot-2023-12-02-at-3.00.19-PM.png" alt="在 NodeList 使用 forEach() 方法" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>在 NodeList 使用 forEach() 方法</figcaption>
</figure>
<p>但是你仍然不能在 NodeList 使用 <code>map</code>、<code>filter</code> 等这类数组方法，如果你需要的话应该创建一个数组。</p>
<p>你可以阅读这篇<a href="https://www.freecodecamp.org/news/dom-manipulation-htmlcollection-vs-nodelist/"> freeCodeCamp 关于 HTML 集合和 NodeList 之间区别的文章</a>来了解更多。</p>
<h2 id="changee-dom-content">如何更改 DOM 元素的内容</h2>
<p>目前为止，你已经学到了几种不同的方法去选择 DOM 元素，但这仅仅是开始。现在，让我们来看看如何操作 DOM 去更改网页的内容。</p>
<p>你需要做的第一件事就是选择元素，你可以用上一小节学到的任一方法。</p>
<p>在选择元素后，你可以使用这几种方法去添加或更新内容。</p>
<h3 id="innerhtml">`innerHTML` 属性</h3>
<p>这是一种可以让你读取、更新元素的内容或结构的方法。让我们看看应该如何使用 <code>innerHTML</code> 方法。</p>
<p>下面是一个包含三个 <code>p</code> 标签的代码片段，每个 <code>p</code> 标签都有一个 id。</p>
<pre><code class="language-HTML">  &lt;p id="topic"&gt;JS array methods&lt;/p&gt;
  &lt;p id="first-method"&gt;map&lt;/p&gt;
  &lt;p id="second-method"&gt;filter&lt;/p&gt;
</code></pre>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2023/12/Screenshot-2023-12-03-at-8.17.55-AM.png" alt="三个 p 标签的代码片段" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>三个 p 标签的代码片段</figcaption>
</figure>
<p>你可以使用 <code>innerHTML</code> 获取任一段落的内容。举个例子，我们来获取第一个段落的内容。</p>
<pre><code class="language-javascript">const topicElement = document.querySelector('#topic')
console.log(topicElement.innerHTML)
</code></pre>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2023/12/Screenshot-2023-12-03-at-8.10.36-AM.png" alt="topicElement 的 innerHTML 的输出" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>topicElement 的 innerHTML 的输出</figcaption>
</figure>
<p>现在，假设你想将 topic 内容从“JS array methods”更改为“JavaScript array methods”。你可以通过将新文本赋值给元素的 innerHTML 来实现。</p>
<pre><code class="language-javascript">const topicElement = document.querySelector('#topic')
topicElement.innerHTML = "JavaScript array methods"
</code></pre>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2023/12/Screenshot-2023-12-03-at-8.16.59-AM-1.png" alt="topic 内容从 “JS Array methods” 更新到 “JavaScript array methods”" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>topic 内容从 “JS Array methods” 更新到 “JavaScript array methods”</figcaption>
</figure>
<p>使用 <code>innerHTML</code>，你可以更改的不只是内容，你也可以更改元素的 HTML 结构。例如，如果你想让 “JavaScript” 加粗，你可以这样做：</p>
<pre><code class="language-javascript">topicElement.innerHTML = "&lt;b&gt;JavaScript&lt;/b&gt; array methods"
</code></pre>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2023/12/Screenshot-2023-12-03-at-8.27.45-AM.png" alt="使用 innerHTML 将 “JavaScript” 加粗了”" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>使用 innerHTML 将 “JavaScript” 加粗了</figcaption>
</figure>
<h3 id="innerhtml-risk">使用 `innerHTML` 的安全风险</h3>
<p>使用 <code>innerHTML</code> 会带来潜在的安全风险，例如：<a href="https://www.freecodecamp.org/news/cross-site-scripting-what-is-xss/">XSS（Cross-site scripting，跨站脚本）攻击</a>。</p>
<p>如果插入的内容来自用户输入或任何不受信任的来源，请确保在使用 <code>innerHTML</code> 前做好校验或清洗，以防止 XSS 攻击。你可以使用类似 <a href="https://www.npmjs.com/package/dompurify">DOMPurify</a> 的库来实现这一点。</p>
<p>此外，如果你处理纯文本内容，可以考虑使用 <code>innerText</code> 和 <code>textContent</code>。</p>
<h3 id="innertext-and-textcontent">`innerText` 和 `textContent` 属性</h3>
<p><code>innerText</code> 和 <code>textContent</code> 忽略 HTML 标签，会把它们当作字符串的一部分。你可以使用这两种方法去读取或更新 DOM 元素的文本。</p>
<p>这两种方法的关键区别在于如何处理文本。使用 <code>innerText</code> 会返回显示在屏幕上的文本，而使用 <code>textContent</code> 则返回标记中的文本。来看下面的例子。</p>
<pre><code class="language-HTML">&lt;p&gt;Key =&lt;span style="display: none;"&gt;     ABC123&lt;span&gt;&lt;/p&gt;
</code></pre>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2023/12/Screenshot-2023-12-03-at-10.03.41-AM.png" alt="一个 p 标签中有一些文本和一个隐藏的 span 标签" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>一个 p 标签中有一些文本和一个隐藏的 span 标签</figcaption>
</figure>
<p>这个例子中包括了一个 <code>p</code> 标签，<code>p</code> 标签中 <code>span</code> 标签包含了 Key 的值。因为 <code>span</code> 内联样式被设置为 “none”，所以 Key 值不会显示在屏幕上。</p>
<p>现在，让我们选择这个 <code>p</code> 标签，然后打印 <code>innerText</code>、<code>textContent</code> 这两个值，看看有什么不同。</p>
<pre><code class="language-javascript">const paragraph = document.querySelector('p');

console.log("innerText: ", paragraph.innerText)
console.log("textContent: ", paragraph.textContent)
</code></pre>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2023/12/Screenshot-2023-12-03-at-10.06.11-AM.png" alt="innerText 和 textContent 的打印结果" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>innerText 和 textContent 的打印结果</figcaption>
</figure>
<p>请注意 <code>innerText</code> 返回的文本是在屏幕上显示的样子（不包含使用 CSS 隐藏的 Key 值），<code>textContent</code> 返回的文本包括了隐藏的元素和空格。</p>
<p>让我们再看另一个添加文本到元素的例子。下面的代码包括两个 <code>p</code> 标签，每个 <code>p</code> 标签都有一个 <code>b</code> 标签和一个空的 <code>span</code> 标签，以及它们之间有一个 <code>hr</code> 标签。</p>
<pre><code class="language-HTML">  &lt;p&gt;
    &lt;b&gt;innerText Example&lt;/b&gt;
    &lt;span id="inner-text"&gt;&lt;/span&gt;
  &lt;/p&gt;
  
  &lt;hr&gt;	
 
  &lt;p&gt;
    &lt;b&gt;textContent Example&lt;/b&gt;
    &lt;span id="textContent"&gt;&lt;/span&gt;
  &lt;/p&gt;
</code></pre>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2023/12/Screenshot-2023-12-03-at-10.48.11-AM.png" alt="演示 innerText 和 textContent 属性的示例" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>演示 innerText 和 textContent 属性的示例</figcaption>
</figure>
<p>现在，让我们选择两个 <code>span</code> 元素，并在其中添加相同的文本。这将帮助你更好地理解 <code>innerText</code> 和 <code>textContent</code> 之间的区别。</p>
<pre><code class="language-javascript">const example1 = document.querySelector('#inner-text');
const example2 = document.querySelector('#text-content');

let address = `
  ABC Street,
  Spintex Road,
  Accra,
  Ghana.
`;

example1.innerText = address;
example2.textContent = address;
</code></pre>
<p>这段代码给两个示例提供了相同的变量 <code>address</code>，第一个使用了 <code>innerText</code>，第二个使用了 <code>textContent</code>。请看下面的结果：</p>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2023/12/Screenshot-2023-12-03-at-10.46.46-AM.png" alt="使用 innerText 和 textContent 更新内容的结果" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>使用 innerText 和 textContent 更新内容的结果</figcaption>
</figure>
<p>请注意 <code>innerText</code> 换行了，而 <code>textContent</code> 却没有。</p>
<p>两个方法的另一个关键不同是它们在内部循环的行为，当在一个循环中处理批量操作或频繁更新时 <code>innerText</code> 比 <code>textContent</code> 会慢一些。</p>
<p>看看<a href="https://www.freecodecamp.org/news/innerhtml-vs-innertext-vs-textcontent/">这篇 freeCodeCamp 文章</a>可以了解更多 <code>innerHTML</code>、<code>innerText</code> 和 <code>textContent</code> 之间的不同。</p>
<h2 id="handle-dom">如何处理 DOM 元素的属性</h2>
<p><a href="https://www.freecodecamp.org/news/html-attributes-explained/">HTML 属性</a> 提供了有关 HTML 元素的有用信息。这些属性总是包含在元素的开始标签内，属性由一个 name 和 value 组成（但也有只出现 name 的例外情况）。</p>
<p>浏览器根据 HTML 结构生成 DOM 时，会将这些属性转化为 DOM 对象的动态属性。</p>
<p>看这个例子：</p>
<p>这个 HTML 中有一个按钮，按钮有一些属性：</p>
<pre><code class="language-HTML">&lt;button id="myBtn" type="submit"&gt;Click Here&lt;/button&gt;
</code></pre>
<p>对于这个例子，浏览器会创建一个 <code>HTMLButtonElement</code> 对象到 DOM 中，并且这个对象的属性也互相匹配。</p>
<ul>
<li><code>HTMLButtonElement.id</code> 的值是 <code>myBtn</code></li>
<li><code>HTMLButtonElement.type</code> 的值是 <code>submit</code></li>
</ul>
<p>要使用 JavaScript 与这些属性进行交互和操作，可以使用诸如 <code>getAttribute()</code> 和 <code>setAttribute()</code> 之类的方法直接访问这些属性。</p>
<h3 id="getAttribute">`getAttribute()` 方法</h3>
<p>顾名思义，你可以使用这个方法去获取元素上已经存在的属性。</p>
<p>它接受一个参数（属性名）返回属性值，如果你给的属性值在这个元素中不存在，这个方法会返回 <code>null</code>。</p>
<p>看这个例子：</p>
<pre><code class="language-HTML">&lt;img src="image.jpg" alt="An example image"&gt;
</code></pre>
<pre><code class="language-javascript">const imageElement = document.querySelector('img')
console.log(imageElement.getAttribute('src'))
</code></pre>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2023/12/Screenshot-2023-12-09-at-9.00.25-AM.png" alt="getAttribute() 用来获取 src 属性值" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>getAttribute() 用来获取 src 属性值</figcaption>
</figure>
<p>在上面的例子中，使用 <code>getAttribute()</code> 方法，你可以获取 <code>img</code> 标签的 <code>src</code> 属性值。</p>
<h3 id="setAttribute">`setAttribute()` 方法</h3>
<p>这个方法用来设置、更改元素的属性。它接受两个参数，第一个参数是你想要更改的属性名，第二个参数是你想要设定的新值。</p>
<p>看这个例子：</p>
<pre><code class="language-HTML">&lt;img src="image.jpg" alt="An example image"&gt;
</code></pre>
<pre><code class="language-javascript">const imageElement = document.querySelector('img')

console.log("BEFORE:", imageElement.getAttribute('src'))
imageElement.setAttribute('src', 'new-image.jpg')
console.log("AFTER:", imageElement.getAttribute('src'))
</code></pre>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2023/12/Screenshot-2023-12-09-at-9.27.14-AM.png" alt="使用 setAttribute() 更新 src 属性值" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>使用 setAttribute() 更新 src 属性值</figcaption>
</figure>
<p>这个例子先打印了 <code>src</code> 属性值，然后使用 <code>setAttribute()</code> 从 <code>image.jpg</code> 到 <code>new-image.jpg</code> 更改了值。</p>
<p>当你给 <code>setAttribute()</code> 传递了一个在这个元素中不存在的属性作为参数时，它将创建一个新属性。例如，你可以添加一个 height 属性给 <code>img</code> 标签：</p>
<pre><code class="language-javascript">const imageElement = document.querySelector('img')

console.log("BEFORE:", imageElement.getAttribute('height'))
imageElement.setAttribute('height', '200')
console.log("AFTER:", imageElement.getAttribute('height'))
</code></pre>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2023/12/Screenshot-2023-12-09-at-9.32.53-AM.png" alt="给 img 添加 height 属性的例子" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>给 img 添加 height 属性的例子</figcaption>
</figure>
<p>第一个打印语句返回 <code>null</code> 是因为 height 属性不存在，但是在我们使用 <code>setAttribute()</code> 设置过后，第二个打印语句返回了 height 的正确值。</p>
<h3 id="removeAttribute">`removeAttribute()` 方法</h3>
<p>在前面的小节中，你学到了如何使用 <code>setAttribute()</code> 添加一个新属性。如果你想移除一个已存在的属性呢？</p>
<p>你可以使用 <code>removeAttribute()</code> 方法，传递一个你想要移除属性名作为参数。</p>
<p>看这个例子：</p>
<pre><code class="language-HTML">  &lt;img src="image.jpg" alt="An example image" height="200"&gt;
</code></pre>
<p>让我们使用 <code>removeAttribute()</code> 方法移除 <code>height</code> 属性从 <code>img</code> 标签上。</p>
<pre><code class="language-javascript">const imageElement = document.querySelector('img')

console.log("BEFORE:", imageElement.getAttribute('height'))
imageElement.removeAttribute('height', '200')
console.log("AFTER:", imageElement.getAttribute('height'))
</code></pre>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2023/12/Screenshot-2023-12-09-at-10.09.35-AM.png" alt="使用 removeAttribute() 的示例" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>使用 removeAttribute() 的示例</figcaption>
</figure>
<p>在调用 <code>removeAttribute()</code> 前，我们第一次打印了 height 的属性值，是 200，但在调用 <code>removeAttribute()</code> 之后，第二次打印结果是 <code>null</code>，可以确认我们从 <code>img</code> 中移除了 height 属性。</p>
<h3 id="hasAttribute">`hasAttribute()` 方法</h3>
<p>处理 DOM 属性的的另一个方法是 <code>hasAttribute()</code>，你可以使用这个方法检查元素是否有指定的属性。</p>
<p>如果指定属性存在， 则 <code>hasAttribute()</code> 方法返回 <code>true</code>，否则返回 <code>false</code>。</p>
<p>看这个例子：</p>
<pre><code class="language-html">&lt;img src="image.jpg" alt="An example image" height="200"&gt;
</code></pre>
<p>让我们使用 <code>hasAttribute()</code> 去检查 <code>img</code> 上是否存在 <code>height</code> 和 <code>width</code> 属性。</p>
<pre><code class="language-javascript">const imageElement = document.querySelector('img')

console.log("HEIGHT", imageElement.hasAttribute('height'))
console.log("WIDTH", imageElement.hasAttribute('width'))
</code></pre>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2023/12/Screenshot-2023-12-09-at-10.20.53-AM.png" alt="使用 hasAttribute() 检查属性是否存在的示例" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>使用 hasAttribute() 检查属性是否存在的示例</figcaption>
</figure>
<p>因为它存在这个属性，所以 height 返回了 <code>true</code>，而检查 width 时返回了 <code>false</code>，因为它不存在。</p>
<h2 id="change-dom-style">如何更改 DOM 的样式</h2>
<p>在 JavaScript 中，有两种主要方法处理 DOM 元素的样式，你可以使用 <code>.style</code> 属性或者使用 <code>class</code>。每种方法都有自己的优点以及最适合的情况。</p>
<h3 id="use-style">使用 `.style` 属性设置样式</h3>
<p>如果你想对某个元素做指定更改的话，你可以使用 <code>.style</code> 属性。<code>.style</code> 属性允许你采用<a href="https://www.freecodecamp.org/news/inline-style-in-html/">内联</a>的形式直接为元素设置样式，这意味着他会覆盖你在 CSS 中的样式。</p>
<p>使用 <code>.style</code> 属性，你可以访问全部的 CSS 属性，看下面的演示：</p>
<pre><code class="language-html">  &lt;h1&gt;Styling elements with JavaScript&lt;/h1&gt;
</code></pre>
<pre><code class="language-javascript">const header = document.querySelector('h1')
console.log(header.style)
</code></pre>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2023/12/ezgif.com-video-to-gif--8-.gif" alt="打印到控制台上的 h1 元素的样式声明" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>打印到控制台上的 h1 元素的样式声明</figcaption>
</figure>
<p><code>console.log()</code> 打印了该元素的 CSS 样式声明以及所有的 CSS 属性。</p>
<p>现在我们看一个如何使用 <code>.style</code> 属性的例子。</p>
<pre><code class="language-html">  &lt;h1&gt;I love JavaScript&lt;/h1&gt;
</code></pre>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2023/12/Screenshot-2023-12-11-at-7.56.41-AM.png" alt="一个 h1 标题" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>一个 h1 标题</figcaption>
</figure>
<p>这里有一个 <code>h1</code> 标题，现在让我们使用 <code>.style</code> 属性给它增加一点样式。</p>
<pre><code class="language-javascript">const paragraph = document.querySelector('h1')

paragraph.style.color = 'white'
paragraph.style.backgroundColor = 'green'
</code></pre>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2023/12/Screenshot-2023-12-11-at-7.59.15-AM.png" alt="使用 .style 属性给 h1 元素增加背景色" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>使用 .style 属性给 h1 元素增加背景色</figcaption>
</figure>
<p>注意：在 JavaScript 中，如果 CSS 属性名包含两个或更多的单词，你不能使用“-”连字符。例如，在 CSS 中你可以写 <code>background-color</code>，但是在 JavaScript 代码中，你必须使用小驼峰命名，所以 <code>background-color</code> 变成了 <code>backgroundColor</code>。</p>
<p>你也可以通过设定属性值为空字符串删除一个元素的样式。</p>
<pre><code class="language-javascript">element.style.propertyName = ""
</code></pre>
<h3 id="use-class">使用 class 设置样式</h3>
<p>通过 class，你可以一次创建样式，并将其应用于不同的元素。这有助于提高代码的可维护性。</p>
<h4 id="classname"><code>className</code> 属性</h4>
<p><code>className</code> 属性展示了 DOM 元素的 class 属性。并且你可以使用它去获取或设置 class 的属性值。</p>
<p>看这个例子：</p>
<pre><code class="language-html">&lt;p class="food rice-dish"&gt;Jollof rice&lt;/p&gt;
</code></pre>
<pre><code class="language-javascript">const jollofParagraph = document.querySelector('p')

console.log(jollofParagraph.className)

jollofParagraph.className = 'favorite'

console.log(jollofParagraph.className)
</code></pre>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2023/12/Screenshot-2023-12-11-at-9.13.37-AM.png" alt="使用 className 属性更改 class 值的例子" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>使用 className 属性更改 class 值的例子</figcaption>
</figure>
<p><code>className</code> 可以读取或替换最近的 class，在上面的例子中，第一次打印是 class 的初始值，在更新 <code>className</code> 属性后，第二次打印则是新值。</p>
<p>这里还有一个更灵活的属性，举个例子，如果你想添加一个其他 class 而不是使用新的 class 替换老的 class 呢？这就是 <code>classList</code> 属性的作用所在。</p>
<h4 id="classlist"><code>classList</code> 属性</h4>
<p>使用 <code>classList</code> 属性，你可以添加或者移除 class。你可以切换 class、使用新值替换 class，甚至可以检查 class 中是否包含某个值。</p>
<p>看这个例子：</p>
<pre><code class="language-html">&lt;p class="food"&gt;Jollof rice&lt;/p&gt;
</code></pre>
<pre><code class="language-javascript">const jollofParagraph = document.querySelector('p')
console.log(jollofParagraph.classList)
</code></pre>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2023/12/Screenshot-2023-12-11-at-9.43.30-AM.png" alt="classList 中只有一个值" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>classList 中只有一个值</figcaption>
</figure>
<h4 id="classlistaddclass">使用 <code>classList.add()</code> 添加 class</h4>
<pre><code class="language-javascript">jollofParagraph.classList.add('fav', 'tasty')

console.log(jollofParagraph.classList)
</code></pre>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2023/12/Screenshot-2023-12-11-at-9.46.14-AM.png" alt="使用 classList.add() 添加新 class 的例子" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>使用 classList.add() 添加新 class 的例子</figcaption>
</figure>
<p>这段代码添加了 <code>fav</code> 和 <code>tasty</code> 两个新 class 到 class 列表。</p>
<h4 id="classlistremoveclass">使用 <code>classList.remove()</code> 移除 class</h4>
<pre><code class="language-javascript">jollofParagraph.classList.remove('tasty')

console.log(jollofParagraph.classList)
</code></pre>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2023/12/Screenshot-2023-12-11-at-9.50.26-AM.png" alt="使用 classList.remove() 移除 class 的例子" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>使用 classList.remove() 移除 class 的例子</figcaption>
</figure>
<p>这段代码从 class 列表中移除了 <code>tasty</code> class。</p>
<h4 id="classlistreplaceclass">使用 <code>classList.replace()</code> 替换 class</h4>
<pre><code class="language-javascript">jollofParagraph.classList.replace('fav', 'favorite')

console.log(jollofParagraph.classList)
</code></pre>
<p>这段代码使用 <code>favorite</code> 替换了 <code>fav</code></p>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2023/12/Screenshot-2023-12-11-at-9.53.30-AM.png" alt="使用 classList.replace() 替换 class 的例子" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>使用 classList.replace() 替换 class 的例子</figcaption>
</figure>
<h4 id="classlistcontainsclass">使用 <code>classList.contains()</code> 检查 class 是否存在</h4>
<pre><code class="language-javascript">const isFavorite = jollofParagraph.classList.contains('favorite')
const isSoup = jollofParagraph.classList.contains('soup')

console.log("Contains favorite: ", isFavorite)
console.log("Contains soup: ", isSoup)
</code></pre>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2023/12/Screenshot-2023-12-11-at-10.09.53-AM.png" alt="使用 classList.contains() 检查 class 是否存在的例子" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>使用 classList.contains() 检查 class 是否存在的例子</figcaption>
</figure>
<p>这段代码会检查传递给它的 class 是否包含在 class 列表中。</p>
<p>如果包含在 class 列表中（例如 <code>favorite</code>），则返回 <code>true</code>；如果不包含在 class 列表中（例如 <code>soup</code>），则返回 <code>false</code>。</p>
<h4 id="classlisttoggleclass">使用 <code>classList.toggle()</code> 切换 class</h4>
<p>当你使用 <code>toggle()</code> 时，它会首先检查该 class 是否存在。如果存在，它将删除该 class 。如果不存在，则会添加。</p>
<pre><code class="language-javascript">jollofParagraph.classList.toggle('favorite')
console.log(jollofParagraph.classList)

jollofParagraph.classList.toggle('favorite')
console.log(jollofParagraph.classList)

jollofParagraph.classList.toggle('favorite')
console.log(jollofParagraph.classList)
</code></pre>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2023/12/Screenshot-2023-12-11-at-10.19.18-AM.png" alt="使用 classList.toggle() 切换 class 的例子" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>使用 classList.toggle() 切换 class 的例子</figcaption>
</figure>
<p>第一次切换，<code>favorite</code> 存在 class 列表中，所以移除。</p>
<p>第二次切换，<code>favorite</code> 不存在 class 列表中，所以添加。</p>
<p>第三次切换，现在 <code>favorite</code> 存在 class 列表中了，所以从 class 列表中移除。</p>
<p><code>toggle()</code> 会根据 class 的存在与否，不断从 class 列表中添加或删除该值。</p>
<h2 id="loop-through-dom">如何遍历 DOM</h2>
<p>遍历 DOM 意味着在 HTML 文档中的不同元素或节点间移动，包括选择和访问父级、子级或者是兄弟元素（或是节点）。你可以这样做去操作文档结构或是获取信息。</p>
<p>但在我们进入这一小节前，你需要理解节点和元素之间的不同。</p>
<h3 id="node-and-element-difference">节点和元素之间的不同</h3>
<p>节点是 DOM 的构件，它们代表着 HTML 结构中的不同组件。</p>
<p>元素是一种特定的节点，但并非所有节点都是元素。代码中一些像是元素属性、文本内容或者是注释都是节点，但它们不是元素。</p>
<p>元素是一种特定类型的节点，它定义了文档内容的结构，可以把元素当作你在用的 HTML 标签，例如 <code>&lt;div&gt;</code>、<code>&lt;p&gt;</code> 和 <code>&lt;ul&gt;</code>。每个元素都可以由属性、文本内容和其他嵌套元素组成。</p>
<h3 id="parentNode-or-parentElement">使用 `parentNode` 还是 `parentElement` 选择父级</h3>
<p>当要选择 DOM 元素的父级时，你可以使用 <code>parentNode</code> 或者 <code>parentElement</code>，它们都可以获取到你给的元素的父级。</p>
<p>从实用角度看，元素或节点的父级总是一个元素。所以，无论你使用哪一个，你总能获得选择元素的正确父级。</p>
<p>让我们看一个选择元素父级的例子：</p>
<pre><code class="language-html">  &lt;div class="container"&gt;
    &lt;p class="full-text"&gt;
        &lt;i id="italics"&gt;Some italicized text&lt;/i&gt;
    &lt;/p&gt;
  &lt;/div&gt;
</code></pre>
<pre><code class="language-javascript">const italicizedText = document.getElementById('italics')

console.log(italicizedText.parentNode)
console.log(italicizedText.parentNode.parentNode)
</code></pre>
<p>首先，选择一个元素，然后，调用 <code>parentNode</code> 这个属性去获取父级。你也可以像是第二个打印语句一样链式调用 <code>parentNode</code> 属性去获取父级的父级。</p>
<p>下面的截图展示了两个打印语句的输出。</p>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2023/12/Screenshot-2023-12-12-at-9.44.45-AM.png" alt="选择元素父级的示例" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>选择元素父级的示例</figcaption>
</figure>
<h3 id="childNodes-or-children">使用 `childNodes` 还是 `children` 选择子级</h3>
<p>你可以使用 <code>.childNodes</code> 和 <code>.children</code> 属性去选择元素的内容，但它们有些不同。</p>
<p><strong><code>childNodes</code>：</strong> 返回一个被选择元素的所有子节点的 NodeList。它包含元素和像是文本、注释或是其他非元素节点。</p>
<p><strong><code>.children</code>：</strong> 返回一个被选择元素的只包含子元素的 HTML 集合。它不包含像是文本、注释这样的非元素节点。</p>
<p>让我们通过一个例子看看它们的不同：</p>
<pre><code class="language-html">  &lt;div id="container"&gt;
    A text node
    &lt;p&gt;Some paragraph&lt;/p&gt;
    &lt;!-- This is a comment --&gt;
    &lt;span&gt;Span Element&lt;/span&gt;
  &lt;/div&gt;
</code></pre>
<p>上面的代码只有两个元素，<code>p</code> 和 <code>span</code>，但是有其他节点 —— 文本节点、注释。</p>
<pre><code class="language-javascript">const container = document.getElementById('container');

const containerChildNodes = container.childNodes;
const containerChildren = container.children;

console.log(containerChildNodes);
console.log(containerChildren);
</code></pre>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2023/12/Screenshot-2023-12-12-at-10.29.23-AM.png" alt="使用 childNodes 属性的示例" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>使用 childNodes 属性的示例</figcaption>
</figure>
<p><code>childNodes</code> 将返回所有的子节点（包括元素和非元素），它还将元素之间的空格作为文本节点。</p>
<p>这用起来可能会让人感到困惑。因此，除非有充分的理由，否则应坚持使用 <code>.children</code> 属性。</p>
<p><code>children</code> 将只会返回子元素（<code>p</code> 和 <code>span</code>）。</p>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2023/12/Screenshot-2023-12-12-at-10.34.08-AM.png" alt="使用 children 属性的示例" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>使用 children 属性的示例</figcaption>
</figure>
<h3 id="choose-first-or-last-element-node">选择第一个或是最后一个元素/节点</h3>
<p>如果你只需要选择第一个或是最后一个元素，你可以使用这四个属性。</p>
<ul>
<li><code>firstChild</code>: 只选择父元素的第一个子节点。</li>
<li><code>lastChild</code>: 只选择父元素的最后一个子节点。</li>
<li><code>firstElementChild</code>: 选择父元素的第一个子元素。</li>
<li><code>lastElementChild</code>: 选择父元素的最后一个子元素。</li>
</ul>
<p>让我们用上一小节中相同的例子，看看它们分别使如何工作的：</p>
<pre><code class="language-html">  &lt;div id="container"&gt;
    A text node
    &lt;p&gt;Some paragraph&lt;/p&gt;
    &lt;!-- This is a comment --&gt;
    &lt;span&gt;Span Element&lt;/span&gt;
  &lt;/div&gt;
</code></pre>
<pre><code class="language-javascript">const container = document.getElementById('container');

console.log("FIRST CHILD:", container.firstChild)
console.log("LAST CHILD:", container.lastChild)
console.log("FIRST ELEMENT: ", container.firstElementChild)
console.log("LAST ELEMENT:", container.lastElementChild)
</code></pre>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2023/12/Screenshot-2023-12-13-at-7.43.25-AM.png" alt="选择第一个或是最后一个元素/节点的示例" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>选择第一个或是最后一个元素/节点的示例</figcaption>
</figure>
<p>注意 <code>firstChild</code> 返回的是第一个文本节点，但 <code>firstElementChild</code> 返回的是第一个元素 <code>p</code>。这意味着它忽略了 <code>p</code> 标签前的文本节点。</p>
<p>另外，请注意 <code>lastChild</code> 返回一个文本节点，尽管从标签上来看 <code>span</code> 之后似乎什么都没有。这是因为 <code>lastChild</code> 属性将 <code>span</code> 的结束标签和 <code>div</code> 元素的结束标签之间的换行符/空格视为一个节点。</p>
<p>这就是为什么通常来说使用 <code>firstElementChild</code> 和 <code>lastElementChild</code>会更安全。</p>
<h3 id="choose-sibling-node">在 DOM 中选择兄弟节点</h3>
<p>你已经学到了如何选择元素的父级或子级，你也可以使用以下属性选择元素的兄弟节点。</p>
<ul>
<li><code>nextSibling</code>: 选择相同父元素的下一个节点。</li>
<li><code>nextElementSibling</code>: 选择下一个元素忽略任何非元素节点。</li>
<li><code>previousSibling</code>: 选择相同父元素的上一个节点。</li>
<li><code>previousElementSibling</code>: 选择上一个元素忽略任何非元素节点。</li>
</ul>
<p>看这个例子：</p>
<pre><code class="language-html">  &lt;div&gt;
    &lt;p id="one"&gt;First paragraph&lt;/p&gt;
    text node
    &lt;p id="two"&gt;Second paragraph&lt;/p&gt;
    another text node
    &lt;p id="three"&gt;Third paragraph&lt;/p&gt;
    &lt;p id="four"&gt;Fourth paragraph&lt;/p&gt;
  &lt;/div&gt;
</code></pre>
<pre><code class="language-javascript">const paragraphTwo = document.getElementById('two')

console.log("nextSibling: ", paragraphTwo.nextSibling)
console.log("nextElementSibling: ", paragraphTwo.nextElementSibling)
console.log("previousSibling: ", paragraphTwo.previousSibling)
console.log("previousElementSibling: ", paragraphTwo.previousElementSibling)
</code></pre>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2023/12/Screenshot-2023-12-13-at-7.57.18-AM.png" alt="选择兄弟节点的示例" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>选择兄弟节点的示例</figcaption>
</figure>
<p><code>nextSibling</code> 和 <code>previousSibling</code> 会选择文本节点，因为它们会考虑父节点内的所有节点。然而，<code>nextElementSibling</code> 和 <code>previousElementSibling</code> 可以只选择 <code>p</code> 标签，因为它们忽略文本这样的非元素节点。</p>
<h2 id="dom-event-and-event-listener">DOM 事件和事件监听器</h2>
<p>DOM 事件是浏览器中发生的动作。有了这些事件，你就可以使网站具有互动性。</p>
<p>一些 DOM 事件是用户发起的，像是点击、移动鼠标或是用键盘打字。另一些是浏览器发起的，像是页面加载完成。</p>
<h3 id="event-listener-and-handler">事件监听器与事件处理函数</h3>
<p>事件监听器是一个让你知道事件什么时候发生的方法，它允许你监听注意 DOM 事件，这样当事件发生时，你可以做点什么。</p>
<p>事件处理函数是对这个事件的响应，当事件发生时这个函数将会运行。</p>
<p>举个例子，你可以给按钮附加一个事件监听器，当用户点击的时候你就可以知道了。然后，你可以写一个事件处理函数，在点击事件发生时在屏幕上打印一些内容。</p>
<p>在这个案例中，当点击发生时事件监听器会通知你的应用，然后触发响应；这个响应（事件发生时调用的函数）就是事件处理函数。</p>
<h3 id="register-events">JavaScript 中三种注册事件的方法</h3>
<p>你可以使用 JavaScript 通过下面三种不同方法监听并响应 DOM 事件。</p>
<ul>
<li><strong>使用内联的事件处理函数：</strong> 就是你添加一个事件监听器作为 HTML 元素的属性。在 JavaScript 早期，这是使用事件唯一的方法。看下面这个例子：</li>
</ul>
<pre><code class="language-javascript">// Example of using an inline event handler

&lt;button onclick="alert('Hello')"&gt;Click me!&lt;/button&gt;
</code></pre>
<ul>
<li><strong>使用 onEvent 处理函数：</strong> 当元素只有一个事件处理函数时你可以这样使用。当你使用这个方法添加超过一个事件处理函数时，只有最后一个函数会运行，因为它会覆盖之前其他的。</li>
</ul>
<pre><code class="language-html">&lt;!-- An example of using an on-event handler --&gt;

&lt;button&gt;Click me!&lt;/button&gt;

&lt;script&gt;
  const myButton = document.querySelector('button')
	
  myButton.onclick = function() {
    console.log("Run first handler")
  }
	
  myButton.onclick = function() {
    console.log("Run second handler")
  }
&lt;/script&gt;
</code></pre>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2023/12/Screenshot-2023-12-14-at-7.41.49-AM.png" alt="只有第二个事件处理函数被执行了" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>只有第二个事件处理函数被执行了</figcaption>
</figure>
<p>正如在控制台所看到的结果，浏览器只运行了第二个事件处理函数的代码。</p>
<ul>
<li><strong>使用 <code>addEventListener()</code> 方法：</strong> 这个方法允许你附加超过一个事件处理函数到一个元素上。并且它将按照它们被添加的顺序执行。</li>
</ul>
<p>一般来说，你应该坚持使用 <code>addEventListener()</code>，除非你有一个令人信服的理由。</p>
<p><code>addEventListener()</code> 接受两个参数，第一个参数是你想监听的事件名称，第二个参数是当事件发生时你想要运行的事件处理函数。</p>
<pre><code class="language-html">&lt;!-- An example of using the addEventListener method --&gt;

&lt;button&gt;Click me!&lt;/button&gt;

&lt;script&gt;
  const myButton = document.querySelector('button')
	
  myButton.addEventListener('click', function() {
    console.log("Run first handler")
  })
	
  myButton.addEventListener('click', function() {
    console.log("Run second handler")
  })
&lt;/script&gt;
</code></pre>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2023/12/Screenshot-2023-12-14-at-7.51.22-AM.png" alt="addEventListener() 执行了两个处理函数" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>addEventListener() 执行了两个处理函数</figcaption>
</figure>
<h3 id="practice">实践挑战</h3>
<p>再继续学习之前这里有一个挑战。在看解题方法之前，先试着自己解决它。</p>
<p>请看下面的 HTML 和 CSS 代码。</p>
<p>这个挑战包括了两个元素，一个 <code>div#gift-box</code> 和一个 <code>button#click-btn</code>，礼物盒子有一个 <code>hide</code> class。</p>
<p>你的任务是写 JavaScript 去监听按钮的点击事件，当用户点击按钮时显示隐藏的盒子。</p>
<pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html lang="en"&gt;
  &lt;head&gt;&lt;/head&gt;
  &lt;body&gt;
    
      &lt;div id="gift-box" class="hide"&gt;🎁&lt;/div&gt;
      &lt;button id="click-btn"&gt;Show the box&lt;/button&gt;
      
  &lt;/body&gt;
&lt;/html&gt;
</code></pre>
<pre><code class="language-css">.hide {
  display: none;
}

#gift-box {
  font-size: 5em;
}
</code></pre>
<p><a href="https://stackblitz.com/edit/js-cywa91?file=index.html,style.css,index.js"><strong>在 StackBlitz 解决这个挑战</strong></a></p>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2023/12/ezgif.com-video-to-gif-converted.gif" alt="挑战解决方案的动图演示" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>挑战解决方案的动图演示</figcaption>
</figure>
<h3 id="solution">实践挑战的解决方案</h3>
<p>如果你能解决这个难题，那恭喜你。如果你没有解决也没事，下面提供了解决方案和解释。</p>
<pre><code class="language-javascript">const giftBoxElement = document.getElementById('gift-box')
const buttonElement = document.getElementById('click-btn')

buttonElement.addEventListener('click', function() {
  giftBoxElement.classList.remove('hide')
})
</code></pre>
<p>为了解决这个挑战，首先你需要选择 <code>#gift-box</code> 和 <code>#click-btn</code> 两个元素。</p>
<p>然后，你需要给按钮添加一个事件监听器，像是之前提到的，<code>addEventListener()</code> 接受两个参数。</p>
<p>在这个案例中，第一个参数是 <code>'click'</code>，第二个参数是一个函数。</p>
<p>目标是显示这个盒子，盒子使用 <code>hide</code> class 在 CSS 中设置了 <code>display</code> 为 <code>none</code>。显示盒子的一种方法是使用 JavaScript 从 classList 中移除 <code>hide</code>。</p>
<h3 id="event-object">事件对象</h3>
<p>这是一个当事件发生时浏览器传递给事件处理函数作为参数的 JavaScript 对象。对象包含了一些有用的属性和方法：</p>
<ul>
<li><code>type</code>：发生的事件类型（例如：点击、鼠标悬浮、按下按键等等）</li>
<li><code>target</code>：触发这个事件的元素</li>
<li><code>clientX</code> 和 <code>clientY</code>：事件发生时，鼠标指针的水平和垂直的坐标</li>
<li><code>preventDefault()</code>：阻止与事件相关的默认动作，例如阻止表单的默认提交事件</li>
<li><code>stopPropagation()</code>：阻止事件通过 DOM 传播，后面会详细说明</li>
</ul>
<p>你可以查看所有的属性和方法在 <a href="https://developer.mozilla.org/en-US/docs/Web/API/Event">MDN 文档</a>。</p>
<h3 id="event-type">事件类型</h3>
<p>浏览器允许你监听的 DOM 事件种类很多，下面列举一些常见的。</p>
<p><strong>鼠标事件：</strong></p>
<ul>
<li><code>click</code>：当元素被点击</li>
<li><code>dbclick</code>：当元素被双击</li>
<li><code>mouseover</code>：当鼠标指针移入元素</li>
<li><code>mouseleave</code>：当鼠标指针离开元素</li>
<li><code>mousedown</code>：当鼠标在元素上按下</li>
<li><code>mouseup</code>：当鼠标在元素上松开</li>
</ul>
<p><strong>键盘事件：</strong></p>
<ul>
<li><code>keydown</code>：当键盘按键被按下</li>
<li><code>keyup</code>：当键盘按键被松开</li>
<li><code>keypress</code>：当按键被按下并显示事件的按键时，注意这个事件不是所有按键都可以触发，尤其是不可打印字符按键</li>
</ul>
<p><strong>表单事件：</strong></p>
<ul>
<li><code>submit</code>：当表单被提交</li>
<li><code>input</code>：当输入框字段更改</li>
<li><code>change</code>：当表单元素值被更改并失焦</li>
</ul>
<p><strong>窗口事件：</strong></p>
<ul>
<li><code>load</code>：当浏览器完成页面加载</li>
<li><code>unload</code>：当用户离开页面</li>
<li><code>resize</code>：当浏览器窗口被调整大小</li>
<li><code>scroll</code>：当用户滚动浏览文档</li>
</ul>
<p>你可以在这查看<a href="https://www.w3schools.com/jsref/dom_obj_event.asp">详细的 DOM 事件表</a>。</p>
<h2 id="event-flow">JavaScript 的事件流</h2>
<p>当 JavaScript 事件发生时，事件会在 DOM 中从发生事件的目标传播到最外层的元素，反之亦然。</p>
<p>例如，假设你点击了页面上的一个按钮。在点击按钮的同时，你也点击了它的父元素以及按钮在 DOM 层次结构中的任何元素。</p>
<h3 id="event-bubbling">事件冒泡</h3>
<p>这是指事件首先在发生的目标（或是某个元素）被注册，然后向外注册到父元素，最后注册到最外层的元素。</p>
<p>看这个例子：</p>
<pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html&gt;
    &lt;head&gt;
        &lt;title&gt;Event bubbling DEMO&lt;/title&gt;
    &lt;/head&gt;
    &lt;body&gt;
        &lt;div id="outer"&gt;
            &lt;div id="inner"&gt;
              &lt;button id='btn'&gt;Click Here&lt;/button&gt;
            &lt;/div&gt;
        &lt;/div&gt;
    &lt;/body&gt;
&lt;/html&gt;
</code></pre>
<p>例子中包含了一个 <code>#btn</code> 按钮，随着事件冒泡，当按钮上发生事件（如点击）时，事件会按以下顺序发生。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/12/4.png" alt="4" width="600" height="400" loading="lazy"></p>
<p>DOM 中的事件冒泡：从 <code>button</code> 到 <code>div#inner</code> 到 <code>div#outer</code> 到 <code>body</code> 到 <code>html</code> 到 <code>document</code>。</p>
<p>事件从目标元素开始向上冒泡，回到最外层的祖先元素。</p>
<h3 id="event-capturing">事件捕获</h3>
<p>事件捕获与事件冒泡相反，事件从最外层祖先元素开始向下沿着 DOM 树到目标元素。</p>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2023/12/JavaScript--2-.png" alt="DOM 中的事件捕获" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>DOM 中的事件捕获</figcaption>
</figure>
<p>在事件捕获阶段，附加到元素上的事件侦听器是按照从最顶层的祖先到目标元素的层次结构顺序执行的。</p>
<p>如果你想知道为什么这很重要，让我们使用和上面相同的 HTML 片段，看一个实际的例子：</p>
<pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html&gt;
    &lt;head&gt;
        &lt;title&gt;Event bubbling DEMO&lt;/title&gt;
    &lt;/head&gt;
    &lt;body&gt;
        &lt;div id="outer"&gt;
            &lt;div id="inner"&gt;
              &lt;button id='btn'&gt;Click Here&lt;/button&gt;
            &lt;/div&gt;
        &lt;/div&gt;
    &lt;/body&gt;
&lt;/html&gt;
</code></pre>
<p>让我们分别添加一个事件监听器到<code>button</code>、<code>#inner</code>、<code>#outer</code> 上：</p>
<pre><code class="language-javascript">const button = document.getElementById('btn')
const innerDiv = document.getElementById('inner')
const outerDiv = document.getElementById('outer')

button.addEventListener('click', function() {
  console.log('Click on button')
})

innerDiv.addEventListener('click', function() {
  console.log('Click on inner Div')
})

outerDiv.addEventListener('click', function() {
  console.log('Click on outer Div')
})
</code></pre>
<p>默认情况下，浏览器使用事件冒泡的机制，因此，无需为事件监听器添加任何其他参数。这是事件处理函数响应按钮点击时的运行顺序：</p>
<ol>
<li><code>button</code></li>
<li><code>#innerDiv</code></li>
<li><code>#outerDiv</code></li>
</ol>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2023/12/Screenshot-2023-12-15-at-11.54.07-AM.png" alt="在冒泡阶段，事件从目标元素到最外层处理" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>在冒泡阶段，事件从目标元素到最外层处理</figcaption>
</figure>
<p>使用事件捕获模型，你可以通过给事件监听器传递第三个参数为 <code>true</code>。</p>
<pre><code class="language-javascript">const button = document.getElementById('btn')
const innerDiv = document.getElementById('inner')
const outerDiv = document.getElementById('outer')

button.addEventListener('click', function() {
  console.log('Click on button')
}, true)

innerDiv.addEventListener('click', function() {
  console.log('Click on inner Div')
}, true)

outerDiv.addEventListener('click', function() {
  console.log('Click on outer Div')
}, true)
</code></pre>
<p>现在运行，事件处理函数的执行顺序将会是相反的方向，像是这样：</p>
<ol>
<li><code>#outerDiv</code></li>
<li><code>#innerDiv</code></li>
<li><code>button</code></li>
</ol>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2023/12/Screenshot-2023-12-15-at-11.58.38-AM.png" alt="在捕获阶段，事件从最外层到目标元素处理" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>在捕获阶段，事件从最外层到目标元素处理</figcaption>
</figure>
<h3 id="event-stopPropagation-method">`stopPropagation()` 停止传播事件</h3>
<p>你已经学习了事件冒泡如何在元素上注册事件，并一直注册到最外层的祖先元素，也看到了事件捕获是怎么反过来的。</p>
<p>但是如果你不想注册事件到所有祖先呢？这就是 <code>stopPropagation()</code> 的作用所在，你可以使用这个方法在整个 DOM 中阻止事件的传播。</p>
<p>让我们看看如何在之前的例子中使用 <code>stopPropagation()</code>：</p>
<pre><code class="language-javascript">button.addEventListener('click', function(event) {
  event.stopPropagation()
  console.log('Click on button')
})

innerDiv.addEventListener('click', function() {
  console.log('Click on inner Div')
})

outerDiv.addEventListener('click', function() {
  console.log('Click on outer Div')
})
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/12/Screenshot-2023-12-15-at-2.48.37-PM.png" alt="Screenshot-2023-12-15-at-2.48.37-PM" width="600" height="400" loading="lazy"></p>
<p><code>stopPropagation()</code> 只允许第一个事件监听器的执行。</p>
<p>现在，只有一个事件处理函数被触发。因为 <code>stopPropagation()</code> 在按钮的事件处理函数中，所以在 <code>innerDiv</code> 和 <code>outerDiv</code> 的事件处理函数并没有被触发。</p>
<p>此外，注意 event 对象，你需要在事件处理函数中作为参数传递它。</p>
<h2 id="JS-DOM-manipulation-projects-ideas">JavaScript DOM 项目</h2>
<p>构建项目是提高对编码概念理解的绝佳方式，所以，卷起袖子，准备工作吧！</p>
<p>这有五个项目想法去帮助你练习巩固你的技巧。</p>
<h3 id="">切换开关</h3>
<p>设计一个开关，当它被点击时切换它的状态，更新 DOM （例如颜色）反映开关的当前状态。</p>
<h3 id="">随机颜色选择器</h3>
<p>创建一个简单的应用，用户可以点击一个按钮生成一个随机颜色。在屏幕上包括一个填充被选择颜色的图形，同时显示颜色代码。</p>
<h3 id="">倒计时</h3>
<p>构建一个从指定时间开始的计时器，在屏幕上实时更新显示剩余时间。</p>
<h3 id="">单词计数器</h3>
<p>开发一个应用，它提供一个文本输入框和文本域给用户用来输入。当用户输入时，在屏幕上实时显示单词个数。</p>
<h3 id="todo">交互式的 Todo 列表</h3>
<p>创建一个应用，允许用户添加、删除、编辑任务。你可以随你开心添加一些想要的高级特性，例如标记任务完成、过滤任务或是排序等。</p>
<h2 id="conclusion">总结</h2>
<p>如果你已经走到了这里，那你现在应该对操作 JavaScript DOM 有着不错的理解。随着实践，你将有足够的信心来处理需要了解这些 DOM 操作概念的高级项目。</p>
<p>一个良好的操作原版 JS DOM 的基础将在使用 React，Angular，Vue，Svelte 这些 JavaScript 库时派上用场。</p>
<p>感谢你阅读本文，祝你编码愉快！想获取更多有深度的教程，欢迎随时订阅<a href="https://www.youtube.com/@DevAfterHours">我的 YouTube 频道</a>。</p>
<!--kg-card-end: markdown--> ]]>
                </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">https://www.freecodecamp.org/news/regex-in-javascript/</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[ 如何用 JavaScript 解决停车场挑战 ]]>
                </title>
                <description>
                    <![CDATA[ 你听说过停车场挑战吗？ 如果没有，那让我来简单解释一下吧。 停车场挑战要求编写一个管理停车场的 class。 在本教程中，我们将用 JavaScript 来实现这一挑战。为了让停车场更有趣一点，我们将创建一个小小的 React App，用以可视化我们的代码。 让我们开始吧。🎉 挑战要求 为实现挑战，你需要使用 JavaScript 实现一个类。 该类应包含以模拟停车场工作的变量和方法。 下面是详细要求：  * 我们需要创建具有指定大小（停车位数量）的停车场  * 我们认为所有车辆都是相同的，所以我们不需要对车辆进行区分  * 我们的类提供了一种在停车场停放新车的方法  * 我们的类提供了一种移除已停放车辆的方法  * 我们的类提供了一种获取停车场大小（车位总数）的方法 停车场挑战解决方案 首先，让我们看一下停车场类的逻辑。 停车场自身的逻辑很简单，所以可能对于你们大多数人来说没有任何难度————特别当你已具备一些 OOP 和基于类的编程经验时。 使用class实现停车场挑战 我将会先展示实现代码，再对于代码实现进行简短的解释。 class ParkingLot {  ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/parking-lot-challenge-solved-in-javascript/</link>
                <guid isPermaLink="false">65fb93ce6f02f80413b538e9</guid>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp.org ]]>
                </dc:creator>
                <pubDate>Wed, 20 Mar 2024 02:13:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2024/03/parking-loot.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>原文：</strong> <a href="https://www.freecodecamp.org/news/parking-lot-challenge-solved-in-javascript/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">How to Solve the Parking Lot Challenge in JavaScript</a>
      </p><!--kg-card-begin: markdown--><p>你听说过停车场挑战吗？ 如果没有，那让我来简单解释一下吧。</p>
<p>停车场挑战要求编写一个管理停车场的 class。</p>
<p>在本教程中，我们将用 JavaScript 来实现这一挑战。为了让停车场更有趣一点，我们将创建一个小小的 React App，用以可视化我们的代码。</p>
<p>让我们开始吧。🎉</p>
<h1 id="">挑战要求</h1>
<p>为实现挑战，你需要使用 JavaScript 实现一个类。 该类应包含以模拟停车场工作的变量和方法。 下面是详细要求：</p>
<ul>
<li>我们需要创建具有指定大小（停车位数量）的停车场</li>
<li>我们认为所有车辆都是相同的，所以我们不需要对车辆进行区分</li>
<li>我们的类提供了一种在停车场停放新车的方法</li>
<li>我们的类提供了一种移除已停放车辆的方法</li>
<li>我们的类提供了一种获取停车场大小（车位总数）的方法</li>
</ul>
<h1 id="">停车场挑战解决方案</h1>
<p>首先，让我们看一下停车场类的逻辑。</p>
<p>停车场自身的逻辑很简单，所以可能对于你们大多数人来说没有任何难度————特别当你已具备一些 OOP 和基于类的编程经验时。</p>
<h2 id="class">使用class实现停车场挑战</h2>
<p>我将会先展示实现代码，再对于代码实现进行简短的解释。</p>
<pre><code class="language-javascript">class ParkingLot {
  slots = [];

  constructor(parkingSize) {
    this.slots = new Array(parkingSize).fill(null);
  }

  park(carId) {
    console.log(`Parking car: ${carId}`);
    if (this.slots.every((slot) =&gt; slot !== null)) {
      return false;
    }

    for (let i = 0; i &lt;= this.slots.length; i++) {
      const slot = this.slots[i];

      if (slot === null) {
        this.slots[i] = carId;
        return true;
      }
    }
  }

  remove(carId) {
    console.log(`Leaving car: ${carId}`);
    if (this.slots.every((slot) =&gt; slot !== carId)) {
      return false;
    }

    for (let i = 0; i &lt;= this.slots.length; i++) {
      const slot = this.slots[i];

      if (slot === carId) {
        this.slots[i] = null;
        return true;
      }
    }
  }

  getSlots() {
    console.log(`Parking slots: ${this.slots}`);
    return this.slots;
  }

  getSize() {
    console.log(`Parking size is: ${this.slots.length}`);
    return this.slots.length;
  }

  getAvailable() {
    const availableSlots = this.slots.filter((s) =&gt; s === null).length;
    console.log(`Available parking slots: ${availableSlots}`);
    return availableSlots;
  }

  isFull() {
    return this.getAvailable() === 0;
  }
}

export default ParkingLot;
</code></pre>
<p>让我们从头开始：首先，我们的停车场class有一个<code>slots</code>属性，这个属性是一个数组，用于存储有关停车位的信息（不考虑停车位是否已被占用）。</p>
<p>在<code>slots</code>属性之后，我们创建了<code>constructor</code>方法，<code>constructor</code>将在每次创建停车场类的实例时都会执行。在<code>constructor</code>中，我们使用<code>parkingSize</code>的输入数值参数来创建一个长度等于该数字的空数组。</p>
<p>严格来说，这个数组并不是空数组，因为我们用为 <em>null</em> 的值对它进行了初始化。这意味着在构造函数执行结束后，我们将会得到一个填充了 <em>null</em> 值的数组，而数组的具体大小则取决于我们传入构造方法的值。</p>
<p>举个例子，如果我们执行下面的代码：</p>
<pre><code class="language-javascript">const parking = new ParkingLot(5);
</code></pre>
<p>我们将会得到以下结果：</p>
<pre><code class="language-javascript">[null, null, null, null, null] // lenght = 5

instead of [] // 空数组，长度为 0
</code></pre>
<p>看完了停车场类的构造函数，我们接下来再来看看剩下的方法。</p>
<p><code>park()</code>—— 是实现停车功能的方法。该方法遍历了 <code>slots</code> 数组，检查是否有空闲位置（即数组内仍然等于 null 的位置），并在这些空闲位置中添加汽车。</p>
<p><code>carId</code>代表汽车，我们以它为标识符，表示在某个地点有一辆车。请注意，如果 <code>slots</code> 中没有空闲位置，则停车失败，此方法返回 false，如果停车成功，则返回 true。</p>
<p><code>getSlots()</code> - 是一个辅助方法，用于返回存储停车位的数组。</p>
<p><code>remove()</code> – 这个方法实现了从停车场移除汽车的功能，它也可以重置 <code>slots</code> 数组。</p>
<p>💡到目前为止，可能你已经注意到了，几乎在每种情况下，当我们需要操作 <code>slots</code> 数组时，我们都需要遍历整个数组，以便访问数组内的元素。</p>
<p>不同的编程语言提供了不同的数据结构和方式，但它们的主要思想也总是相同的：当你需要对数据做一些操作时，你需要以某种方式遍历它们。</p>
<p>为了将汽车从停车场中移走，我们使用前文中的标识符。我们会在 <code>slots</code> 数组中寻找带有标识符的车辆，在找到对应车辆后。将它“移出”停车场。我们只需将该 <code>slots</code> 数组再次设置为 <em>null</em> 即可执行移除操作。</p>
<p>现在你大概就会明白，为何我们使用 <em>null</em> 来初始化停车场数组。</p>
<p>该方法也会根据是否成功移除车辆返回布尔值。</p>
<p>当创建一些 UI 界面以展示变化，我们需要使用这些方法的返回值。比如，将汽车添加到停车场时（<code>park</code> 方法），也会有相同的返回值机制。</p>
<p><code>getSize()</code> – 我们用来检查停车场大小的另一个辅助方法。</p>
<p><code>getAvailable()</code> - 这个方法可以显示我们目前有多少空闲的停车位。</p>
<p><code>isFull()</code> – 这个方法告诉我们停车场是否已满，即没有更多可用的车位。</p>
<h1 id="reactapp">如何构建 React App</h1>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2022/06/image-92.png" alt="停车场应用程序 - 主页面" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>停车场应用程序 - 主页面</figcaption>
</figure>
<p>有趣的部分快开始啦🕺。</p>
<p>我们将创建一个可交互的 app，将我们已实现的代码停车场可视化。</p>
<p>我们的 app 将提供基本的 UI 控件，允许（假想的）操作员使用停车场软件。为了使视觉效果更令人赏心悦目，我们将尝试为 app 的基本功能设置动画。</p>
<p>一起来看看，是如何实现的吧！📺</p>
<h2 id="demo">Demo</h2>
<p>点击<a href="https://parking-lot-chi.vercel.app/">在线 demo</a> 链接，提前尝鲜体验吧🥪。</p>
<h2 id="">项目源码</h2>
<p>这里是停车场 app 的<a href="https://github.com/mihailgaberov/parking-lot">代码仓库</a>。</p>
<p>让我来简要介绍一下技术选项的 <em>what</em> 和 <em>why</em>。</p>
<p>这个 app 是使用 <a href="https://vitejs.dev/">vite</a> 构建的，因为我最近一直在体验它，并且对于它的速度和性能都十分满意。</p>
<p>尽管 <strong>vite</strong> 还处于开发阶段，但如果我要开始开发一个新项目，并且可以选择构建工具的话，我会选择<strong>vite</strong>。</p>
<p>这并不是说我反对它的老大哥 <a href="https://create-react-app.dev/">CRA</a>。 相反，我已经用 creact react app 搭建了很多 app，并且我仍在我的一些项目中使用它。 这是因为 <strong>vite</strong> 更快，更符合我当前的诉求。</p>
<p>💡请记住，对于项目的技术选型应该取决于特定项目的特定需求。毕竟，软件开发不存在银弹，这始终是一个需求和优先级的问题。</p>
<h2 id="reactapp">React App 项目结构</h2>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2022/06/image-93.png" alt="App 项目结构" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>App 项目结构</figcaption>
</figure>
<p>停车场 app 的代码结构十分简洁。可以看到，在根目录，我们有两个文件夹 - <em>assets</em> 和 <em>src</em>。  <em>assets</em> 文件夹包含了 app 使用的静态资源（在本例中，它是一张汽车图像）。 <em>src</em> 则包含了 app 的源码文件。</p>
<p>让我们更深入地看看 <em>src</em> 文件夹吧。</p>
<p>在 <em>src</em> 内部，有下列文件夹：</p>
<ul>
<li>
<p><a href="https://github.com/mihailgaberov/parking-lot/tree/main/src/components">components</a> - 包含 app 中使用的所有 React 组件源码</p>
</li>
<li>
<p><a href="https://github.com/mihailgaberov/parking-lot/tree/main/src/lib">lib</a> - 包含停车场类，负责 app 的主要逻辑</p>
</li>
<li>
<p><a href="https://github.com/mihailgaberov/parking-lot/tree/main/src/pages">pages</a> 包含两个子目录，分别用于 app 的两个页面 – Landing 页面和 Main 页面</p>
</li>
<li>
<p><a href="https://github.com/mihailgaberov/parking-lot/tree/main/src/utils">utils</a> 包含一个辅助方法，用于生成将停车位表示为 <em>busy</em> 时使用的虚构汽车牌照</p>
</li>
<li>
<p>还有几个文件，其中大部分都与应用程序的入口程序有关。favicon 文件除外，它的作用你应该有所接触，如果之前没有接触过，那么你可以在浏览器的选项卡上看到它😉</p>
</li>
</ul>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2022/06/image-94.png" alt="带有 favicon 的浏览器选项卡" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>带有 favicon 的浏览器选项卡</figcaption>
</figure>
<h2 id="app">App 页面</h2>
<p>如前所述，app 的主页面（main pages，也称为 mian screens）称为<a href="https://github.com/mihailgaberov/parking-lot/tree/main/src/pages/Landing">Landing页面</a>和<a href="https://github.com/mihailgaberov/parking-lot/tree/main/src/pages/Main">Main页面</a>。 这些页面由 React 组件构成，它们是欢迎页面所有内容的骨架。在欢迎页中，你可以选择选择最初的登录位置，并且选择停车场的车位数量。</p>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2022/06/image-95.png" alt="欢迎页" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>欢迎页</figcaption>
</figure>
<p>再点击欢迎页的 submit 按钮后，你会跳转到主页面。在主页面，你可以作为操作者对停车场进行管理。</p>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2022/06/image-96.png" alt="主页" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>主页</figcaption>
</figure>
<h2 id="app">App 的功能</h2>
<p>停车场 app 提供了最基础的停车场管理功能。当用户选择了他们想要的车位数量（最多可选择 20 个车位）后，用户将回到主页面，并可以看到所有的空闲停车位。</p>
<p>用户可以通过 PARK! 按钮停放车辆，对应车位将在车辆停泊后显示为繁忙状态并且展示对应车辆的登记号码。操作者可以通过点击繁忙状态的车位来取消车位的停放车辆，即移除停放车辆。</p>
<h3 id="">💡红色汽车移动的简单动画只是为了视觉效果，对停车场的运作方式没有任何实际影响。</h3>
<p>我使用 <a href="https://github.com/css-modules/css-modules">CSS modules</a> 为 app 设置样式。为了让移动设备的体验更好，我还优化了移动端的样式。</p>
<p>行动起来，快<a href="https://parking-lot-chi.vercel.app/">试一试</a>吧🧪</p>
<h1 id="">总结</h1>
<p>在写作这篇文章时，我一开始只打算描述停车场类本身——仅仅出于科普目的，展示如何通过 JavaScript 实现一个类。</p>
<p>但后来我觉得，仅仅实现一个类有点无聊🥱。 因为我想创造一些更有趣💃🏻，更游戏化的东西🕹️。</p>
<p>所以我开发出了这款类似迷你游戏的应用程序🎮。</p>
<p>在创建它时，我 5 岁的女儿 🧒🏻 一看到它就想玩玩看，并且玩得特别开心！</p>
<p>或许这对于成年人来说过于幼稚，但是对于小朋友却刚刚好😀。</p>
<p>我希望这个游戏一般的实现方案可以吸引你的注意力，以便你可以更好得记忆它背后的知识📖。</p>
<p>感谢阅读！🙏</p>
<!--kg-card-end: markdown--> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ JavaScript 中的 DOM 操作——面向初学者的全面指南 ]]>
                </title>
                <description>
                    <![CDATA[ 作为一种 Web 开发语言，JavaScript 赋予了开发者创建动态和交互式网页的能力，其中一个实现这种交互性的 JavaScript 关键特性是文档对象模型（DOM）操作。 DOM 操作允许开发者修改网页的结构、样式和内容。在本文中，我们将探讨 JavaScript 中 DOM 操作的基础知识，将复杂的概念分解成易于理解的代码片段。 什么是 DOM 文档对象模型（DOM）是一种用于 Web 文档的编程接口。它以对象树的形式表示文档的结构，其中每个对象对应于文档的一部分，例如元素、属性和文本。JavaScript 可以操作这个树状结构，允许开发者动态地改变网页的内容和外观。 如何访问 DOM 元素 为了操作 DOM，我们需要访问它的元素，这一步通常使用代表整个 HTML 文档的 document 对象来实现。让我们看一个简单的例子： // 使用一个元素的 ID 访问这个元素 const headerElement = document.getElementById('header'); // 使用类名访问元素 const paragraphs = document.getE ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/dom-manipulation-in-javascript/</link>
                <guid isPermaLink="false">65f10c90823801041102de9e</guid>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ DOM ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Tsukistar ]]>
                </dc:creator>
                <pubDate>Wed, 13 Mar 2024 02:17:02 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2024/03/Beige-Aesthetic-Neutral-Thesis-Defense-Presentation-1.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>原文：</strong> <a href="https://www.freecodecamp.org/news/dom-manipulation-in-javascript/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">DOM Manipulation in JavaScript – A Comprehensive Guide for Beginners</a>
      </p><!--kg-card-begin: markdown--><p>作为一种 Web 开发语言，JavaScript 赋予了开发者创建动态和交互式网页的能力，其中一个实现这种交互性的 JavaScript 关键特性是文档对象模型（DOM）操作。</p>
<p>DOM 操作允许开发者修改网页的结构、样式和内容。在本文中，我们将探讨 JavaScript 中 DOM 操作的基础知识，将复杂的概念分解成易于理解的代码片段。</p>
<h2 id="dom">什么是 DOM</h2>
<p>文档对象模型（DOM）是一种用于 Web 文档的编程接口。它以对象树的形式表示文档的结构，其中每个对象对应于文档的一部分，例如元素、属性和文本。JavaScript 可以操作这个树状结构，允许开发者动态地改变网页的内容和外观。</p>
<h3 id="dom">如何访问 DOM 元素</h3>
<p>为了操作 DOM，我们需要访问它的元素，这一步通常使用代表整个 HTML 文档的 document 对象来实现。让我们看一个简单的例子：</p>
<pre><code class="language-javascript">// 使用一个元素的 ID 访问这个元素
const headerElement = document.getElementById('header');

// 使用类名访问元素
const paragraphs = document.getElementsByClassName('paragraph');

// 使用标签名访问元素
const images = document.getElementsByTagName('img');
</code></pre>
<p>在上面的代码片段中，我们使用 <code>getElementById</code> , <code>getElementsByClassName</code> , 和 <code>getElementsByTagName</code> 三个函数来检索特定的元素。这三个函数的返回值可以存储在变量中，以供开发者进行进一步的操作。</p>
<h3 id="">如何修改元素内容</h3>
<p>一旦我们已经访问了一个元素，我们就可以使用这个元素的<code>innerHTML</code>属性来修改它的内容：</p>
<pre><code class="language-javascript">// 修改一个元素的内容
headerElement.innerHTML = 'New Header Text';
</code></pre>
<p>在上面的例子中，我们将 <code>headerElement</code> 的内容更改为 <code>New Header Text</code> 。这是一种简单而行之有效的方法，可以更新元素中的文本内容。</p>
<h2 id="">事件和事件处理</h2>
<p>事件是在浏览器中发生的操作或事务，例如用户点击按钮或调整窗口大小。JavaScript 允许我们处理这些事件并执行相应的代码作为对事件的响应。事件处理是创建交互式网页的关键方面。</p>
<h3 id="">如何添加事件监听器</h3>
<p>为了响应事件，我们可以使用事件监听器，它们是用于“监听”特定元素上的特定事件的函数。让我们以一个按钮点击事件为例：</p>
<pre><code class="language-javascript">// 访问一个按钮元素
const myButton = document.getElementById('myButton');

// 添加点击事件监听器
myButton.addEventListener('click', function() {
    alert('Button Clicked!');
});
</code></pre>
<p>在这个例子中，当 ID 为 <code>myButton</code> 的按钮被点击时，浏览器将弹出一个内容为 <code>Button Clicked!</code> 的提示框。事件监听器提供了一种根据用户交互执行自定义代码的方式。</p>
<h2 id="">如何修改样式</h2>
<p>DOM 操作还包括修改元素的样式，使我们能够创建视觉上吸引人的、动态的网页。</p>
<h3 id="">如何动态地修改样式</h3>
<p>我们可以使用元素的 <code>style</code> 属性来改变其外观。让我们以点击按钮时改变段落颜色的例子来说明：</p>
<pre><code class="language-javascript">// 访问一个段落元素
const myParagraph = document.getElementById('myParagraph');

// 访问一个按钮元素
const colorButton = document.getElementById('colorButton');

// 为按钮添加一个点击事件
colorButton.addEventListener('click', function() {
    // 修改段落的颜色样式
    myParagraph.style.color = 'blue';
});
</code></pre>
<p>在这个例子中，当 ID 为 <code>colorButton</code> 的按钮被点击时，ID 为 <code>myParagraph</code> 的段落的文本颜色将被更改为蓝色。</p>
<h2 id="">如何创建和修改元素</h2>
<p>除了修改现有元素之外，JavaScript 还允许我们创建新元素并将它们添加到 DOM 中。</p>
<h3 id="">如何创建新的元素</h3>
<p>使用 <code>createElement</code> 方法来创建一个新的 HTML 元素。让我们创建一个新的段落元素，并将其追加（添加）到文档的 body 中：</p>
<pre><code class="language-javascript">// 新建一个段落元素
const newParagraph = document.createElement('p');

// 为新段落设置文本内容
newParagraph.textContent = 'This is a new paragraph.';

// 将新创建的段落追加到文档的 body 中
document.body.appendChild(newParagraph);
</code></pre>
<p>在这个例子中，我们创建了一个新的 <code>p</code>（段落）元素，设置了它的文本内容，然后将其追加到文档的 body 中。</p>
<h3 id="">如何修改属性</h3>
<p>我们还可以修改现有元素的属性。让我们思考如何动态更改图像的来源：</p>
<pre><code class="language-javascript">// 访问一个图像元素
const myImage = document.getElementById('myImage');

// 修改图像的来源属性
myImage.src = 'new-image.jpg';
</code></pre>
<p>这个例子中, 我们访问了 ID 为 <code>myImage</code> 的图像元素，并将其 <code>src</code> 属性更改为 <code>new-image.jpg</code>，动态更新显示的图像。</p>
<h3 id="">如何更新表单的输入值</h3>
<p>让我们思考这样一个情景：你希望根据用户的交互来更新文本输入框的值：</p>
<pre><code class="language-javascript">// 访问一个文本输入框元素
const myInput = document.getElementById('myInput');

// 添加一个输入事件监听器
myInput.addEventListener('input', function() {
    // 使用输入值更新一个段落的内容
    document.getElementById('inputValue').textContent = myInput.value;
});
</code></pre>
<p>在这个例子中，当用户在 ID 为 <code>myInput</code> 的文本输入框中输入内容时，ID 为 <code>inputValue</code> 的段落将根据输入值动态更新段落内容。</p>
<h3 id="">如何切换可见性</h3>
<p>你可以通过使用 <code>display</code> 样式属性来切换元素的可见性。让我们创建一个按钮用于切换段落的可见性：</p>
<pre><code class="language-javascript">// 访问一个按钮元素
const toggleButton = document.getElementById('toggleButton');

// 访问一个段落元素
const toggleParagraph = document.getElementById('toggleParagraph');

// 添加一个点击事件监听器
toggleButton.addEventListener('click', function() {
    // 切换段落的可见性
    toggleParagraph.style.display = toggleParagraph.style.display === 'none' ? 'block' : 'none';
});
</code></pre>
<p>在这里，ID 为 <code>toggleParagraph</code> 的段落最初是可见的，单击 ID 为 <code>toggleButton</code> 的按钮会切换其可见性。</p>
<h2 id="dom">DOM 操作中的常见陷阱</h2>
<p>虽然 DOM 操作是创建动态网页的强大工具，但初学者经常会遇到一些常见陷阱，这些陷阱可能导致意外行为或错误。让我们探讨一些这样的陷阱，并提供如何避免它们的建议。</p>
<h3 id="dom">在 DOM 渲染完成前操作它们</h3>
<p>有时我们可能会在 DOM 尚未完全加载之前尝试操作它，这可能导致 JavaScript 尝试访问尚未呈现的元素。为了避免这种情况，最重要的是在执行任何 JavaScript 代码之前都需要等待 DOM 完全加载：</p>
<pre><code class="language-javascript">document.addEventListener('DOMContentLoaded', function() {
    // DOM 操作代码放在这里
});
</code></pre>
<p>通过将执行 DOM 操作的代码包裹在 <code>DOMContentLoaded</code> 事件监听器内部，你就可以确保它仅在 DOM 准备就绪时运行。</p>
<h3 id="">没有确认一个元素是否存在</h3>
<p>在尝试使用诸如 <code>getElementById</code> 这样的方法访问元素时，我们可能会假设元素存在并继续进行操作。但是，如果元素不存在于页面上，则可能会导致错误。</p>
<p>在操作元素之前，我们始终应该先检查该元素是否存在：</p>
<pre><code class="language-javascript">const myElement = document.getElementById('myElement');

if (myElement) {
    // 在这里对这个元素进行操作
} else {
    console.error('Element not found!');
}
</code></pre>
<p>这个简单的检查可以防止在操作不确定是否存在的元素时出现的错误。</p>
<h3 id="">忘记阻止默认行为</h3>
<p>在处理事件时，忘记阻止默认行为可能会导致意外的页面行为。例如，如果在没有阻止默认行为的情况下提交表单，页面可能会重新加载，导致数据丢失：</p>
<pre><code class="language-javascript">const myForm = document.getElementById('myForm');

myForm.addEventListener('submit', function(event) {
    // 阻止默认的表单提交
    event.preventDefault();

    // 你的表单处理代码放在这里
});
</code></pre>
<p>通过调用<code>event.preventDefault()</code>，你可以阻止与表单提交事件关联的默认行为，从而完全控制事件的处理方式。</p>
<h3 id="dom">执行效率低下的 DOM 查询</h3>
<p>在循环中执行效率低下的 DOM 查询会降低性能。每次查询都涉及遍历 DOM，不必要的查询会减慢网页的加载速度。</p>
<p>与其重复查询 DOM，不如缓存元素的引用：</p>
<pre><code class="language-javascript">// 在循环中执行的效率低下的查询
for (let i = 0; i &lt; 10; i++) {
    const myElement = document.getElementById('myElement');
    // 操作 `myElement`
}

// 循环之外的高效的查询
const myElement = document.getElementById('myElement');
for (let i = 0; i &lt; 10; i++) {
    // 操作 `myElement`
}
</code></pre>
<p>通过一次查询 DOM 并重复使用该 DOM 的引用，可以优化你的代码。</p>
<h3 id="">无法处理跨浏览器的兼容性</h3>
<p>不同的浏览器可能会略有不同地解释 JavaScript 和 DOM 操作。如果没有考虑跨浏览器兼容性，可能会导致代码的行为不一致。</p>
<p>使用特性检测，并考虑使用像 jQuery 或现代框架这样的库来处理跨浏览器的不一致性：</p>
<pre><code class="language-javascript">// 使用特性检测检查浏览器对 `addEventListener` 方法的支持：
if (document.addEventListener) {
    // 使用 addEventListener
} else {
    // 回退到另一种受支持的方法
}
</code></pre>
<p>通过在使用一些功能特性之前检查它们，你可以确保您的代码在各种浏览器上正常工作。</p>
<h2 id="dom">如何在框架中使用 DOM 操作</h2>
<p>虽然 JavaScript 允许直接操作 DOM，但现代 Web 开发通常涉及使用诸如 React 或 Vue.js 等框架。这些框架提供了一种更加结构化的方式来构建和管理用户界面。</p>
<h3 id="react">React 示例</h3>
<pre><code class="language-javascript">// React 组件渲染一个按钮并处理其点击事件
class MyButton extends React.Component {
    handleClick() {
        alert('React Button Clicked!');
    }

    render() {
        return (
            &lt;button onClick={() =&gt; this.handleClick()}&gt;Click me&lt;/button&gt;
        );
    }
}

// 将 React 组件渲染到 DOM 中
ReactDOM.render(&lt;MyButton /&gt;, document.getElementById('reactRoot'));
</code></pre>
<p>这个 React 示例创建了一个组件来处理按钮点击事件，它演示了一种更加声明式的 UI 开发方法。</p>
<h3 id="vuejs">Vue.js 示例</h3>
<pre><code class="language-javascript">// 具有一个数据属性和一个方法的 Vue.js 实例
new Vue({
    el: '#vueRoot',
    data: {
        message: 'Vue.js Message'
    },
    methods: {
        showMessage: function () {
            alert(this.message);
        }
    }
});
</code></pre>
<p>这个例子创建了一个 Vue.js 实例来管理数据和方法，展示了 Vue.js 的响应性和基于组件的结构。</p>
<h2 id="">总结</h2>
<p>在这个全面的指南中，我们深入探讨了 JavaScript 中的 DOM 操作。从访问元素到处理事件，从修改样式到创建新元素，我们以简单直接的方式涵盖了 DOM 操作的基本概念。</p>
<p>请记住，DOM 操作是创建动态和交互式网页的核心。通过掌握这些基本技术，你将能够构建引人入胜且用户友好的 Web 应用程序。随着你继续进行你的 JavaScript 学习之旅，更多的练习与实践将加深你对这些概念的理解，为你成为一名成功的 Web 开发者铺平道路。</p>
<!--kg-card-end: markdown--> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 理解 DOM 事件和 JavaScript 事件监听器 ]]>
                </title>
                <description>
                    <![CDATA[ 浏览器中的 JavaScript 代码使用事件驱动编程模式。这意味着当浏览器中发生特定的 DOM 事件时，将执行一段代码作为对该操作的响应。 在本文中，我将帮助你理解如何使用 JavaScript 监听和响应 DOM 事件。 如果你需要复习一下 DOM，我已经写了一篇文章来解释什么是 DOM 以及 JavaScript 如何与之交互 [https://www.freecodecamp.org/news/introduction-to-the-dom/]。 什么是 DOM 事件，它们为什么有用 DOM 事件是浏览器暴露的信号，你可以利用这些信号运行一段 JavaScript 代码。 这些 DOM 事件会在用户与我们创建的应用程序进行交互时发生，例如点击按钮或在输入框中输入字母。 作为 web 开发人员，你可以指示 JavaScript 监听特定事件，并对该事件做出响应。 例如：  * 点击按钮时，更改段落文本。  * 提交表单时，使用 Fetch API 发送 POST 请求。 如何监听 DOM 事件 要监听事件，需要使用 addEventListener() 方法将事件 ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/dom-events-and-javascript-event-listeners/</link>
                <guid isPermaLink="false">65ae29b3c8b4c103e5521044</guid>
                
                    <category>
                        <![CDATA[ DOM ]]>
                    </category>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Miya Liu ]]>
                </dc:creator>
                <pubDate>Mon, 22 Jan 2024 04:19:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2024/01/DOM-events-feature-image.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>原文：</strong> <a href="https://www.freecodecamp.org/news/dom-events-and-javascript-event-listeners/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">Understanding DOM Events and JavaScript Event Listeners</a>
      </p><p>浏览器中的 JavaScript 代码使用事件驱动编程模式。这意味着当浏览器中发生特定的 DOM 事件时，将执行一段代码作为对该操作的响应。</p><p>在本文中，我将帮助你理解如何使用 JavaScript 监听和响应 DOM 事件。</p><p>如果你需要复习一下 DOM，我已经写了一篇文章来解释<a href="https://www.freecodecamp.org/news/introduction-to-the-dom/">什么是 DOM 以及 JavaScript 如何与之交互</a>。</p><h2 id="-dom-">什么是 DOM 事件，它们为什么有用</h2><p>DOM 事件是浏览器暴露的信号，你可以利用这些信号运行一段 JavaScript 代码。</p><p>这些 DOM 事件会在用户与我们创建的应用程序进行交互时发生，例如点击按钮或在输入框中输入字母。</p><p>作为 web 开发人员，你可以指示 JavaScript 监听特定事件，并对该事件做出响应。</p><p>例如：</p><ul><li>点击按钮时，更改段落文本。</li><li>提交表单时，使用 Fetch API 发送 POST 请求。</li></ul><h2 id="-dom--1">如何监听 DOM 事件</h2><p>要监听事件，需要使用 <code>addEventListener()</code> 方法将事件监听器附加到元素上。</p><p><code>addEventListener()</code> 方法接受两个参数：</p><ul><li>要监听的事件 <code>type</code></li><li>事件触发时要运行的函数</li></ul><pre><code class="language-js">Element.addEventListener(type, function);</code></pre><p>回到示例，假设你想在点击按钮元素时更改段落文本，你可以这样做：</p><pre><code class="language-html">&lt;body&gt;
  &lt;p id="myParagraph"&gt;This is an example paragraph&lt;/p&gt;
  &lt;button id="changeText"&gt;Change Text&lt;/button&gt;
  &lt;script&gt;
    const button = document.querySelector('#changeText');

    function newText(event) {
      const p = document.querySelector('#myParagraph');
      p.innerText = 'The text has been changed';
    }

    button.addEventListener('click', newText);
  &lt;/script&gt;
&lt;/body&gt;</code></pre><p>要在 HTML 文档中插入 JavaScript 代码，我们需要使用 <code>script</code> 标签，如上图所示。</p><p>使用 <code>document.querySelector()</code> 方法选择按钮元素，然后在该元素上调用 <code>addEventListener()</code> 方法，为按钮附加一个事件监听器。</p><p>首先，指定要监听的事件类型 <code>type</code>，在本例中为 <code>click</code> 事件。然后，指定该事件发生时要运行的函数。</p><p>在上面的代码中，当 <code>click</code> 事件被触发时，<code>newText</code> 函数将被执行。</p><p>事件监听器还将发送一个 <code>event</code> 对象，其中包含触发事件的相关信息。这就是为什么在上面的 <code>newText</code> 函数中有一个 <code>event</code> 参数。</p><p>你可以将事件记录到控制台，查看其详细信息：</p><pre><code class="language-js">function newText(event) {
  console.log(event);
}</code></pre><p>如果你再次点击按钮，将得到以下输出结果：</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2024/01/event-log-example-1.png" class="kg-image" alt="Event object log" width="600" height="400" loading="lazy"></figure><p>根据事件触发时要做的事情，你可能需要使用 <code>event</code> 对象中包含的信息。</p><p>在这里，我们要做的只是更改段落文本，因此不需要 <code>event</code> 对象。稍后，我们将在处理键盘事件时看到使用 <code>event</code> 对象的示例。</p><p>在浏览器中可以监听的事件有很多。以下是开发 web 应用时可能需要的一些最常见事件：</p><!--kg-card-begin: markdown--><table>
<thead>
<tr>
<th>事件</th>
<th>触发事件的情况</th>
</tr>
</thead>
<tbody>
<tr>
<td>click</td>
<td>当你按下并释放鼠标的主按钮时，用于追踪按钮和可点击的元素</td>
</tr>
<tr>
<td>mousemove</td>
<td>当你移动鼠标光标时</td>
</tr>
<tr>
<td>mouseover</td>
<td>当你将鼠标光标移动到某个元素上方时，类似于 CSS 的 hover 状态</td>
</tr>
<tr>
<td>mouseout</td>
<td>当你的鼠标光标移动出元素的边界时</td>
</tr>
<tr>
<td>dblclick</td>
<td>当你点击两次时</td>
</tr>
<tr>
<td>DOMContentLoaded</td>
<td>当 DOM 内容完全加载时</td>
</tr>
<tr>
<td>keydown</td>
<td>当你在键盘上按下一个键时</td>
</tr>
<tr>
<td>keyup</td>
<td>当你在键盘上释放一个键时</td>
</tr>
<tr>
<td>submit</td>
<td>当一个表单被提交时</td>
</tr>
</tbody>
</table>
<!--kg-card-end: markdown--><p>如果你想阅读 DOM 事件类型的完整列表，可以访问<a href="https://en.wikipedia.org/wiki/DOM_event">此页面</a>。</p><p>DOM 事件分为多个类别。在此，我们将只介绍两个在项目中最常用的事件：键盘事件和鼠标事件。</p><h2 id="-">键盘事件</h2><p>对于键盘，你可以跟踪按下和松开键时分别运行的 <code>keydown</code> 和 <code>keyup</code> 事件。</p><p>举例说明，请在控制台运行以下代码：</p><pre><code class="language-js">document.addEventListener('keydown', event =&gt; {
  console.log(`A key is pressed: ${event.key}`);
});

document.addEventListener('keyup', event =&gt; {
  console.log(`A key is released: ${event.key}`);
});</code></pre><p>运行上述代码后，慢慢按下键盘上的一个键，然后慢慢松开。</p><p>你应该会看到如下日志输出：</p><figure class="kg-card kg-image-card kg-width-wide"><img src="https://www.freecodecamp.org/news/content/images/2024/01/keydown-event-1.png" class="kg-image" alt="Keyboard events log" width="600" height="400" loading="lazy"></figure><p>请注意 “keydown” 日志是如何在你按下按键时立即显示的，而 “keyup” 日志只有在你松开按键时才会显示。</p><p>键盘事件通常会附加到 <code>document</code> 对象而不是特定元素上，因为整个网站都应能监听到该事件。</p><h2 id="--1">鼠标事件</h2><p>除了键盘事件，DOM 还提供了一种跟踪鼠标事件的方法。</p><p>最常见的鼠标事件有：</p><ul><li><code>mousedown</code> - 鼠标按钮被按下</li><li><code>mouseup</code> - 鼠标按钮被释放</li><li><code>click</code> - 点击事件</li><li><code>dblclick</code> - 双击事件</li><li><code>mousemove</code> - 当鼠标移动到元素上时</li><li><code>contextmenu</code> - 打开上下文菜单时，例如单击鼠标右键时</li></ul><p>同样，你可以直接在 <code>document</code> 对象中添加事件监听器来测试这些事件：</p><pre><code class="language-js">document.addEventListener('mousedown', event =&gt; {
  console.log(`The mouse is pressed`);
});

document.addEventListener('mouseup', event =&gt; {
  console.log(`The mouse is released`);
});</code></pre><p>运行上面的代码，然后点击浏览器中的任意位置。你应该会看到 <code>mousedown</code> 和 <code>mouseup</code> 事件分别被记录下来。</p><h2 id="--2">如何移除事件监听器</h2><p>要移除附加到元素上的事件监听器，需要调用 <code>removeEventListener()</code> 方法，并传递事件的 <code>type</code> 和你传递给 <code>addEventListener()</code> 方法的 <code>function</code>，如下所示：</p><pre><code class="language-js">button.removeEventListener('click', newText);</code></pre><p>上述代码可以从 <code>button</code> 元素中移除 “click” 事件监听器。请注意，你需要在元素上调用 <code>removeEventListener()</code> 方法，同时将函数 <code>newText</code> 传递给该方法。</p><p>要正确地移除事件监听器，需要为事件附加函数引用。如果向 <code>addEventListener()</code> 方法传递一个无名函数，就无法移除该事件：</p><pre><code class="language-js">button.addEventListener('click', function (event) {
  alert('Button save is clicked');
});</code></pre><p>如果没有上面例子中的函数名，就无法移除事件监听器。</p><h2 id="-html-">如何使用 HTML 属性监听事件</h2><p>除了使用 <code>addEventListener()</code> 方法外，你还可以通过在 HTML 元素中添加 <code>on[eventname]</code> 属性来监听事件。</p><p>例如，假设你想监听按钮点击，你可以为按钮添加 <code>onclick</code> 属性，如下所示：</p><pre><code class="language-html">&lt;body&gt;
  &lt;button onclick="handleClick()"&gt;Click Me!&lt;/button&gt;
  &lt;script&gt;

    function handleClick(event) {
      alert('The button is clicked!');
    }
  &lt;/script&gt;
&lt;/body&gt;</code></pre><p>在上面的按钮元素中，我们添加了 <code>onclick</code> 属性，并将 <code>handleClick()</code> 函数传递给它。</p><p>当我们点击按钮时，`handleClick()` 函数将被执行。</p><p>你也可以使用 JavaScript 添加 <code>onclick</code> 属性，如下所示：</p><pre><code class="language-html">&lt;body&gt;
  &lt;button id="myBtn"&gt;Click Me!&lt;/button&gt;
  &lt;script&gt;
    const myBtn = document.querySelector('#myBtn');
    myBtn.onclick = handleClick;

    function handleClick(event) {
      alert('The button is clicked!');
    }
  &lt;/script&gt;
&lt;/body&gt;</code></pre><p>在这里，我们使用 JavaScript 将 <code>handleClick</code> 函数的引用赋值给 <code>onclick</code> 属性。</p><p>要删除 <code>onclick</code> 属性，可以将该属性赋值为空：</p><pre><code class="language-js">const myBtn = document.querySelector('#myBtn');
myBtn.onclick = null;</code></pre><h2 id="--3">你应该使用哪一种</h2><p>如你所见，有两种方法可以监听 DOM 事件：<code>addEventListener()</code> 方法和 <code>on[eventname]</code> HTML 属性。你应该使用哪一种呢？</p><p>答案是，当你需要更多扩展性时，可以使用 <code>addEventListener()</code> 方法，而当你希望事情简单化时，可以使用 <code>on[eventname]</code> 方法。</p><p>在开发 web 应用程序时，<code>.html</code> 文件只应作为页面的结构，而 <code>.js</code> 文件则应定义 web 应用程序的任何行为。</p><p>为了使应用程序更易于维护和扩展，JavaScript 应能访问 HTML 元素，但 HTML 元素不能执行 JavaScript 函数。这就是推荐使用 <code>addEventListener()</code> 方法的原因。</p><p>但是，<code>addEventListener()</code> 并不是没有代价的，这使得代码的阅读相当繁琐。</p><p>使用 <code>on[eventname]</code> 属性时，你只需在 HTML 元素中指定函数名称：</p><pre><code class="language-html">&lt;body&gt;
  &lt;button onclick="handleClick()"&gt;Click Me!&lt;/button&gt;
  &lt;script&gt;

    function handleClick(event) {
      alert('The button is clicked!');
    }
  &lt;/script&gt;
&lt;/body&gt;</code></pre><p>但使用 <code>addEventListener()</code> 方法时，需要查询所需的元素，调用该方法，然后指定要运行的事件和回调函数：</p><pre><code class="language-html">&lt;body&gt;
  &lt;button id="myBtn"&gt;Click Me!&lt;/button&gt;
  &lt;script&gt;
    const myBtn = document.querySelector('#myBtn');
    myBtn.addEventListener('click', handleClick);

    function handleClick(event) {
      alert('The button is clicked!');
    }
  &lt;/script&gt;
&lt;/body&gt;</code></pre><p>如上所示，在使用 <code>on[eventname]</code> 属性时，可以少写一些代码。</p><p>虽然这看起来无关紧要，但当你在大型应用程序中使用大量 HTML 和 JS 文件时，这可以是一件重要的事情。</p><p>此外，<code>addEventListener()</code> 方法还允许你为同一元素附加多个监听器，如下所示：</p><pre><code class="language-html">&lt;body&gt;
  &lt;button id="myBtn"&gt;Click Me!&lt;/button&gt;
  &lt;script&gt;
    const myBtn = document.querySelector('#myBtn');

    myBtn.addEventListener('click', handleClick);

    myBtn.addEventListener('click', handleClickTwo);

    function handleClick() {
      console.log('Run from handleClick function');
    }

    function handleClickTwo() {
      console.log('Run from handleClickTwo function');
    }
  &lt;/script&gt;
&lt;/body&gt;</code></pre><p>点击上面的按钮时，JavaScript 将同时执行两个事件监听器。</p><p>而使用 <code>onclick</code> 属性则无法做到这一点，因为每次只能指定一个函数作为引用：</p><pre><code class="language-html">&lt;body&gt;
  &lt;button id="myBtn"&gt;Click Me!&lt;/button&gt;
  &lt;script&gt;
    const myBtn = document.querySelector('#myBtn');

    myBtn.onclick = handleClick;
      
    // 当你把一个新的函数赋给 onclick，
    // 旧的函数被覆盖

    myBtn.onclick = handleClickTwo;

    function handleClick() {
      console.log('Run from handleClick function');
    }

    function handleClickTwo() {
      console.log('Run from handleClickTwo function');
    }
  &lt;/script&gt;
&lt;/body&gt;</code></pre><p>但我从未遇到过需要两次监听同一事件的情况，因此这一优势可能根本没有用处。</p><h2 id="--4">结语</h2><p>通过浏览器暴露的 DOM 事件，你可以以适当的方式响应用户操作。</p><p>这种使用事件监听器来完成特定任务的模式被称为事件驱动编程，在使用 JavaScript 开发 web 应用程序时会经常用到这种模式。</p><p>有两种方法可以监听事件：使用 <code>addEventListener()</code> JavaScript 方法和 <code>on[eventname]</code> HTML 属性。这两种方法各有利弊，因此你最好都能熟悉。</p><p>如果你喜欢这篇文章，并希望将自己的 JavaScript 技能提高到一个新的水平，我建议你点击<a href="https://codewithnathan.com/beginning-modern-javascript">这里</a>查看我的新书<em>《Beginning Modern JavaScript》</em>。</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2024/01/beginning-js-cover.png" class="kg-image" alt="beginning-js-cover" width="600" height="400" loading="lazy"></figure><p>本书旨在让初学者轻松学习 JavaScript，让所有希望学习 JavaScript 的人都能轻松掌握。它以循序渐进的指南帮助你了解如何使用 JavaScript 创建动态 web 应用程序。</p><p>我在此承诺：你将真正感觉到自己了解自己在用 JavaScript 做什么。</p><p>下次见！</p> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 2024 年你的第一门编程语言应该学什么？[已解决] ]]>
                </title>
                <description>
                    <![CDATA[ 大多数人的编程之旅都是从搜索引擎开始的。 例如搜索“XXX 语言教程”。 但要怎么决定从哪门语言开始呢？ “硅谷的人总是开 Java 的玩笑，我想应该先学 Java。” 或者： "Haskell！现在 Haskell 这么火！" 又或者： "一定要学 Go！你看它的吉祥物多么可爱！" 或者当我们不知所措的时候，只能搜： "入门学什么编程语言比较好？" 人们顾虑的问题通常就那么几个，下面这张信息图很好地回答了这些疑惑： Image credit [http://carlcheo.com/startcoding]选择第一门编程语言的过程十分有趣，这就好像是个人的信仰选择问题一样。 也许因为你像个孩子一样喜欢玩橡皮泥，稀里糊涂都打算开始学 Ruby。我还是要提醒你，不要草率做决定。 想要真正掌握你的第一门编程语言，你需要投入成千上百小时来学习实践。 所以你要考虑清楚以下这几个方面：  * 这门语言的岗位需求大不大  * 这门语言有没有发展前景  * 语言的学习难度  * 在学习过程中你能运用它开发什么样的练手项目（把你的成果分享给周围的朋友才能激励你坚持学习下去 ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/what-programming-language-should-i-learn-first/</link>
                <guid isPermaLink="false">5d31400dfbfdee429dc5ef54</guid>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ 余博伦 ]]>
                </dc:creator>
                <pubDate>Tue, 09 Jan 2024 04:13:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2019/07/1_1XEF9NuNQy0rSu4kVdbb6A.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>原文：</strong> <a href="https://www.freecodecamp.org/news/what-programming-language-should-i-learn-first-19a33b0a467d/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">What Programming Language Should I Learn First in 2024? [Solved]</a>
      </p><p>大多数人的编程之旅都是从搜索引擎开始的。</p><p>例如搜索“XXX 语言教程”。</p><p>但要怎么决定从哪门语言开始呢？</p><p>“硅谷的人总是开 Java 的玩笑，我想应该先学 Java。”</p><p>或者：</p><p>"Haskell！现在 Haskell 这么火！"</p><p>又或者：</p><p>"一定要学 Go！你看它的吉祥物多么可爱！"</p><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/news/content/images/2019/08/image-15.png" class="kg-image" alt="image-15" width="720" height="720" loading="lazy"></figure><p>或者当我们不知所措的时候，只能搜：</p><p>"入门学什么编程语言比较好？"</p><p>人们顾虑的问题通常就那么几个，下面这张信息图很好地回答了这些疑惑：</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://pic2.zhimg.com/80/v2-d5279a67971e0d64ff2fa29eb0501789_hd.png" class="kg-image" alt="v2-d5279a67971e0d64ff2fa29eb0501789_hd" width="600" height="400" loading="lazy"><figcaption><a href="http://carlcheo.com/startcoding" rel="noopener">Image credit</a></figcaption></figure><p>选择第一门编程语言的过程十分有趣，这就好像是个人的信仰选择问题一样。</p><p>也许因为你像个孩子一样喜欢玩橡皮泥，稀里糊涂都打算开始学 Ruby。我还是要提醒你，不要草率做决定。</p><p>想要真正掌握你的第一门编程语言，你需要投入成千上百小时来学习实践。</p><p>所以你要考虑清楚以下这几个方面：</p><ul><li>这门语言的岗位需求大不大</li><li>这门语言有没有发展前景</li><li>语言的学习难度</li><li>在学习过程中你能运用它开发什么样的练手项目（把你的成果分享给周围的朋友才能激励你坚持学习下去）</li></ul><p>每年都会有新的编程语言出现，以及随着而来的学术论文亦或者是搞笑段子：</p><figure class="kg-card kg-image-card"><img src="https://pic1.zhimg.com/80/v2-6a8dc1c8a42520a03d612928988d7c04_hd.jpg" class="kg-image" alt="v2-6a8dc1c8a42520a03d612928988d7c04_hd" width="720" height="2518" loading="lazy"></figure><p>第一门编程语言可选择的范围很广。下面这张图很清晰地显示了过去几年中几门语言学习的 Google 搜索热度变化：<br></p><figure class="kg-card kg-image-card"><img src="https://pic2.zhimg.com/80/v2-693368bd57c79ee3487e36fe016885fd_hd.png" class="kg-image" alt="v2-693368bd57c79ee3487e36fe016885fd_hd" width="600" height="400" loading="lazy"></figure><p>Java 有起有落，而 Python 逐渐成为了最受欢迎的入门语言。除此之外，还有最近几年逐步火起来的 JavaScript。</p><p>在我展开讨论之前，先明确几点：</p><ul><li>我并不会在这里争论哪门语言要比哪门优越</li><li>我认同一名合格的程序员最终应该掌握不止一门编程语言</li><li>我想说明的是，对于刚入门的初学者来说，还是应该先专注于一门语言的学习。而这门语言，我想你可能早就在标题中发现了——就是 JavaScript</li></ul><p>我们先来聊聊现在学校里的编程教学是什么情况。</p><h2 id="-">计算机基础教学</h2><figure class="kg-card kg-image-card"><img src="https://pic2.zhimg.com/80/v2-94f1beeccb3bfc6cfec2c81c1acfe46d_hd.png" class="kg-image" alt="v2-94f1beeccb3bfc6cfec2c81c1acfe46d_hd" width="600" height="400" loading="lazy"></figure><p>一般在大学教授编程课程的都是计算机系，而计算机专业一般数学和电子方面的内容又比较多。当然你可能早就听说过：</p><blockquote>计算机科学的教育无法使任何人成为专业的程序员，就好像学习怎么使用刷子和颜料无法让任何人成为画家一样—— Eric S. Raymond（《大教堂与集市》作者）</blockquote><p>即使在 2020 年的今天，许多大学还像教计算机科学一样教编程，像教数学一样教计算机科学。</p><p>所以许多课程中编排的入门语言是 C 这样底层抽象的语言，或者是专门针对数学的 MATLAB.</p><p>一些专业权威的机构也推出了他们的编程语言年度排行榜，例如 <a href="https://link.zhihu.com/?target=http%3A//www.tiobe.com/tiobe-index/" rel="nofollow noreferrer">TIOBE Index</a>，又或者是下面这个来自 <a href="https://link.zhihu.com/?target=http%3A//spectrum.ieee.org/computing/software/the-2016-top-programming-languages" rel="nofollow noreferrer">IEEE</a>的榜单：</p><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/news/content/images/2019/08/image-16.png" class="kg-image" alt="image-16" width="636" height="381" loading="lazy"></figure><p>大多数这类榜单 10 年来都没有什么变化。</p><p>但事实上不是这样的，包括在学术界。</p><p>早在 2014 年，Python 就超过了 Java，成为了最受美国各大 CS（Computer Science）机构欢迎的教学语言。</p><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/news/content/images/2019/08/image-17.png" class="kg-image" alt="image-17" width="700" height="450" loading="lazy"></figure><p>而在实际上的生产工作中，也发生了和权威机构发布的完全不一样的变化（数据来自 <em>87,354</em> 名开发者参与的 Stack Overflow 2019 年度调查）：</p><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/news/content/images/2020/02/image-36.png" class="kg-image" alt="image-36" width="619" height="805" loading="lazy"></figure><p>多一半的开发者都在使用 JavaScript。它最早只在前端开发应用，现在也逐步在后端流行开来。甚至开始延伸到游戏开发和物联网领域。</p><p>工作岗位上对 JavaScript 的需求也仅次于 Java（数据来自 <a href="https://link.zhihu.com/?target=http%3A//www.indeed.com/jobtrends/q-java-q-python-q-javascript-q-ruby-q-php-q-android-q-ios.html" rel="nofollow noreferrer">Indeed.com</a>）：</p><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/news/content/images/2019/08/image-19.png" class="kg-image" alt="image-19" width="720" height="440" loading="lazy"></figure><p>所以我们也围绕 JavaScript 技术栈建立了我们的<a href="https://link.zhihu.com/?target=https%3A//www.freecodecamp.com/" rel="nofollow noreferrer">开源社区</a>。在过去的五年里，有超过 40,000 名学员通过在 <strong>freeCodeCamp </strong>学习找到了编程相关的工作。</p><p>不过 JavaScript 真的也适合你么？它值得你为之献出第一次么？我们来继续分析：</p><h3 id="--1">事实一：岗位需求</h3><p>如果你只是为了好奇好玩而学编程的话，可以忽略这一点。但如果你和大多数人一样，想通过掌握编程技能找到工作的话，这点对你就非常重要了。</p><p>像我之前提到的，JavaScript 是除 Java 之外需求量最大的编程语言。</p><p>另外还有一点：JavaScript 已经发展了 20 多年，而直到最近，一些诸如 Netflix, Walmart 和 PayPal 一类的大公司才开始全栈应用 JS 构建他们的应用。</p><p>所以就造成了市场上 JavaScript 开发者供不应求的状况（数据来自 <a href="https://link.zhihu.com/?target=http%3A//www.indeed.com/jobtrends/q-java-q-python-q-javascript-q-ruby-q-php-q-android-q-ios.html" rel="nofollow noreferrer">Indeed.com</a>）。</p><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/news/content/images/2019/08/image-20.png" class="kg-image" alt="image-20" width="720" height="451" loading="lazy"></figure><p>从图上来看，每个 Java 岗位大概有 2.7 人在竞争，IOS 和 PHP 也平均都有 2 人以上竞争。</p><p>而通过计算，每个 JavaScript 岗位只有 0.7，这就成了属于 JS 开发者的卖家市场。</p><p><em>译者注：为了更具说服力，特此补充国内的编程语言岗位情况（数据来源 </em><a href="https://blog.csdn.net/juwikuang/article/details/100550704">2019 年 9 月中国编程语言排行榜</a><em>）：</em></p><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/news/content/images/2020/02/image-35.png" class="kg-image" alt="image-35" width="785" height="673" loading="lazy"></figure><h2 id="--2">事实二：发展前景</h2><p>在 GitHub 上，平均每个 JavaScript 的项目收到的 pull requests 要比其他语言多两倍以上。其增长速度也远超别的流行语言。</p><figure class="kg-card kg-image-card"><img src="https://pic2.zhimg.com/80/v2-d90e80afd63f1087f50c70bf10a41665_hd.png" class="kg-image" alt="v2-d90e80afd63f1087f50c70bf10a41665_hd" width="600" height="400" loading="lazy"></figure><p>JavaScript 的整个生态体系也因为 Google, Microsoft, Facebook, Netflix 这类大公司的投资而受益匪浅。</p><p>例如 TypeScript(一种 JavaScript 的超集，提供更丰富特性，可编译为普通 JS)有着超过 100 名开源贡献者，而这里面大多数都是微软和谷歌的员工。</p><p>而且这类集团公司很少有使用 Java 的。因为 Java 的所有者 Oracle 经常会因为各种有关 Java 开发的原因起诉别的公司。</p><h2 id="--3">事实三：学习难度</h2><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/news/content/images/2019/08/image-21.png" class="kg-image" alt="image-21" width="720" height="303" loading="lazy"></figure><p>大多数的程序员应该都会认同，高级的脚本语言通常比较好学。JavaScript 当然属于此类，也包括 Ruby 和 Python.</p><p>即便很多大学课程教授的都是 Java 或 C++，这类语言也真的很难上手。</p><h2 id="--4">事实四：你能实践的练手项目</h2><p>这是 JavaScript 最大的优势。JS 可以运行在任何有浏览器的设备上。你几乎可以用 JS 开发任何应用，而且很方便就能分享发布到任何地方。</p><p>正是由于 JavaScript 无处不在，Stack Overflow 的联合创始人 Jeff Atwood 才说出他的那句经典定律：</p><blockquote>任何可以用 JavaScript 写成的应用，最终都会使用 JavaScript 写成。</blockquote><p>现实也在不断的佐证，<a href="https://link.zhihu.com/?target=https%3A//www.reddit.com/r/atwoodslaw/" rel="nofollow noreferrer">证据在此</a>。</p><p>Java 也曾号称是跨平台语言。可也许你了解到，Oracle 官方在今年早些时候终止了<a href="https://link.zhihu.com/?target=http%3A//motherboard.vice.com/read/a-brief-history-of-the-java-applet" rel="nofollow noreferrer">Java Applets</a> 的支持。</p><p>Python 也经常会遇到类似的问题：</p><blockquote>我怎么才能把我写的这个游戏发给我的朋友？或者能够不安装什么应用就在手机上运行么？ — James Hague in <a href="https://link.zhihu.com/?target=http%3A//prog21.dadgum.com/203.html" rel="nofollow noreferrer">Retiring Python as a Teaching Language</a></blockquote><p>相比之下，JS 要方便很多。下面的这些应用都来自我们开源社区的学员，你只需要在浏览器里打开链接就能开始玩啦：</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://chinese.freecodecamp.org/news/content/images/2019/08/image-22.png" class="kg-image" alt="image-22" width="720" height="683" loading="lazy"><figcaption><a href="https://link.zhihu.com/?target=http%3A//s.codepen.io/adambeagle/debug/qOamaz" rel="nofollow noreferrer">1970s style Simon game</a></figcaption></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://pic2.zhimg.com/80/v2-a1da22984966e4030f5968fd3962ff6d_hd.png" class="kg-image" alt="v2-a1da22984966e4030f5968fd3962ff6d_hd" width="600" height="400" loading="lazy"><figcaption><a href="https://link.zhihu.com/?target=http%3A//s.codepen.io/safx/debug/Ewcym" rel="nofollow noreferrer">Conway’s Game of Life</a></figcaption></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://chinese.freecodecamp.org/news/content/images/2019/08/image-23.png" class="kg-image" alt="image-23" width="720" height="350" loading="lazy"><figcaption><a href="https://link.zhihu.com/?target=http%3A//s.codepen.io/duttakapil/debug/BKGjOa" rel="nofollow noreferrer">Star Wars-themed Wikipedia Search</a></figcaption></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://chinese.freecodecamp.org/news/content/images/2019/08/image-24.png" class="kg-image" alt="image-24" width="720" height="446" loading="lazy"><figcaption><a href="https://link.zhihu.com/?target=http%3A//s.codepen.io/Megabyteceer/debug/qbXJMQ" rel="nofollow noreferrer">A roguelike dungeon crawler game</a></figcaption></figure><h3 id="--5"><br>先专注学好一门语言，再去学别的。</h3><p>如果你总是在各个语言之间摇摆不定，<a href="https://link.zhihu.com/?target=https%3A//medium.freecodecamp.com/a-cautionary-tale-of-learning-to-code-my-own-eddb24d9d5a7" rel="nofollow noreferrer">你很难取得什么实质上的进步</a>。</p><p>为了避免在入门阶段止步不前，你需要扎实学好你的第一门语言。之后再学习别的语言你就会感觉到异常轻松。</p><p>等到那个阶段，你就可以逐步成长为一名掌握许多语言的全面的开发者啦：</p><ul><li>C 对于你理解计算机原理特别有帮助，例如内存管理之类的。在高性能计算中非常有用。</li><li>C++ 在游戏开发中运用广泛</li><li>Python 在科学研究和数据分析中应用广泛</li><li>如果你想在大型的科技公司中工作，Java 就非常重要。</li></ul><p>但还是先学会 JavaScript 再说吧:)</p><p>我知道评论里会有很多人要和我吵起来。虽然我知道这不可能，但我还是要试着说服你一下：</p><p><strong>Q1：JavaScript 运行速度不是很慢么？</strong></p><p>在很多实际运用场景中，JavaScript 和一些高性能语言运行一样快。</p><p>Node.js 要比 Python、Ruby、PHP 快上好几个数量级。</p><p>甚至已经接近了 C++, Java, Go 的性能。</p><p>下面是一个比较全面的编程语言“跑分评测”：</p><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/news/content/images/2019/08/image-25.png" class="kg-image" alt="image-25" width="720" height="422" loading="lazy"></figure><p><br><strong>Q2：JavaScript 连静态语言都不是！</strong></p><p>和 Python/Ruby 一样，JavaScript 属于动态语言，使用起来也很方便。举个例子，假如我想把变量 exampleArray 设为数组，我会先给它赋值，然后通过 length 函数来获取数组中元素的数量。</p><pre><code class="language-js">exampleArray = [1, 2]
-&gt; [1, 2]
exampleArray.length
-&gt; 2
</code></pre><p>但假如我不小心赋值给了它一个字符串，代码照样运行不会报错，可并不是我想要的结果：</p><pre><code class="language-text">exampleArray = "text"
-&gt; "text"
exampleArray.length
-&gt; 4
</code></pre><p>这一类错误在动态类型的语言中经常会遇到。大多数的开发者都会专门写一些检查和测试来避免此类错误的发生。</p><p>如果你非要学一门静态语言的话，我依旧推荐你学习 JavaScript，之后再开始学习 TypeScript。</p><blockquote>TypeScript 是有学习曲线的，但在掌握了 JavaScript 之后，你的学习会很顺利。— <a href="https://link.zhihu.com/?target=https%3A//medium.freecodecamp.com/when-should-i-use-typescript-311cb5fe801b" rel="nofollow noreferrer">Alex Ewerlöf on TypeScript</a></blockquote><p><strong>Q3：我想要学的是怎么开发移动 App</strong></p><p>那你也还应该先学 JavaScript 呀~</p><ol><li>JavaScript 有一些很棒的工具可以开发原生的移动应用，例如 <a href="https://link.zhihu.com/?target=http%3A//ngcordova.com/" rel="nofollow noreferrer">Angular Cordova</a>和 <a href="https://link.zhihu.com/?target=https%3A//facebook.github.io/react-native/" rel="nofollow noreferrer">React Native</a>.</li><li>为了让你的移动 App 真的像模像样，它至少得有个后端，你用一些合适的 Web 开发框架可以很轻松地实现，例如 Node.js+Express.js</li></ol><p>而且移动开发的春天也正是 JS 的流行带来的。</p><p>在 Stack Overflow的调查中，有近一半的开发者的岗位都是 Web 开发，移动开发岗只占了 8%。</p><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/news/content/images/2019/08/image-26.png" class="kg-image" alt="image-26" width="720" height="679" loading="lazy"></figure><p>“为某事专门下一个 App”的境况也已不再，大多数的智能手机用户<a href="https://link.zhihu.com/?target=http%3A//www.recode.net/2016/9/16/12933780/average-app-downloads-per-month-comscore" rel="nofollow noreferrer">开始趋于不下载新的 App 了</a>。</p><p>当然——人们还在用着 App。绝大多数都是大厂的微信、支付宝一类（原文：Mostly Facebook, Google Maps, and handful of others）。市场对移动开发的需求也越来越小。</p><p>移动开发的前景也难以预期。而且在许多方面，使用 JavaScript 来开发维护和部署移动应用要更加轻松简单。所以像 Facebook 和 Google 这样的公司在研发更好的 JavaScript 工具上投入的越来越多。</p><p>在 2016 年，几乎所有的开发都和 Web 开发有关。所有的事情都依附着互联网这个大平台。而且下一波将要崛起的设备——家庭智能、自动驾驶汽车等也都需要依附于网络。</p><p><strong>Q4：JavaScript 不是一个只花 10 天写着玩的语言么？</strong></p><p>JavaScript 有一段<a href="https://link.zhihu.com/?target=http%3A//www.w3school.com.cn/js/pro_js_history.asp" rel="nofollow noreferrer">简史</a>。</p><p>人们也经常会拿 JavaScript 开<a href="https://link.zhihu.com/?target=http%3A//blog.csdn.net/bitfan/article/details/10362461" rel="nofollow noreferrer">各种各样的玩笑</a>。</p><p>同样人们也喜欢吐槽 C++，可不管有多少人吐槽，C++ 依然很成功。</p><p><em>译者注：这就和 “PHP 是全世界最好的语言”是一个道理。</em></p><p>所以如果再有人在你面前说 JS 怎么怎么不好的话，请记住 C++ 之父的那句名言：</p><blockquote>世界上只有两种编程语言：一种是天天被人喷的，另一种是没人用的。— Bjarne Stroustrup</blockquote> ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
