<?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[ Humilitas - 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[ Humilitas - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/chinese/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Tue, 26 May 2026 20:24:15 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/chinese/news/author/humilitas/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ JavaScript 变量提升——let/const 定义的变量与 var 定义的变量有何不同 ]]>
                </title>
                <description>
                    <![CDATA[ 我曾经以为只有 var  定义的变量会提升（hoisting）。但我最近又了解到，let  或者 const  定义的变量也会提升。 本文会详细解释这个问题。 感兴趣的话，可以查看本文的视频版本 [https://www.youtube.com/watch?v=VbHaL_J8Ex0]。 var  定义的变量是如何提升的 以下代码展示了 var  定义的变量的提升情况： console.log(number) // undefined var number = 10 console.log(number) // 10 number  提升到了全局作用域的顶部，这使得它在变量声明代码之前的代码中也能够被访问，而不会报错。 不过需要注意的是，这里只有变量声明（var number）被提升了，变量的初始化（= 10）却没有被提升。所以在 number  声明之前访问它，得到的是  var 定义的变量的默认初始值，即 undefined。 变量声明和初始化的代码执行之后，访问 number  得到的就是它的初始值，即 10。 let/const 定义的变量是如何提升的  ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/javascript-let-and-const-hoisting/</link>
                <guid isPermaLink="false">63baaff981727e0763145e07</guid>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Humilitas ]]>
                </dc:creator>
                <pubDate>Wed, 04 Jan 2023 04:39:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2023/01/5.-let-const-hoisting.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>原文：</strong> <a href="https://www.freecodecamp.org/news/javascript-let-and-const-hoisting/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">Hoisting in JavaScript with let and const – and How it Differs from var</a>
      </p><!--kg-card-begin: markdown--><p>我曾经以为只有 <code>var</code> 定义的变量会提升（hoisting）。但我最近又了解到，<code>let</code> 或者 <code>const</code> 定义的变量也会提升。</p>
<p>本文会详细解释这个问题。</p>
<p>感兴趣的话，可以查看<a href="https://www.youtube.com/watch?v=VbHaL_J8Ex0">本文的视频版本</a>。</p>
<h2 id="var"><code>var</code> 定义的变量是如何提升的</h2>
<p>以下代码展示了 <code>var</code> 定义的变量的提升情况：</p>
<pre><code class="language-js">console.log(number)
// undefined

var number = 10

console.log(number)
// 10

</code></pre>
<p><code>number</code> 提升到了全局作用域的顶部，这使得它在变量声明代码之前的代码中也能够被访问，而不会报错。</p>
<p>不过需要注意的是，这里只有变量声明（<code>var number</code>）被提升了，变量的初始化（<code>= 10</code>）却没有被提升。所以在 <code>number</code> 声明之前访问它，得到的是 <strong>var 定义的变量的默认初始值</strong>，即 <code>undefined</code>。</p>
<p>变量声明和初始化的代码执行之后，访问 <code>number</code> 得到的就是它的初始值，即 <strong>10</strong>。</p>
<h2 id="letconst">let/const 定义的变量是如何提升的</h2>
<p>对 <code>let</code> 或 <code>const</code> 定义的变量做同样的测试：</p>
<pre><code class="language-js">console.log(number)

let number = 10
// 或 const number = 10

console.log(number)

</code></pre>
<p>发现会报错：<strong>ReferenceError: Cannot access 'number' before initialization</strong>。</p>
<p>由此可见，用 var 定义的变量可以在声明之前被访问而不报错，但是用 <code>let</code> 或 <code>const</code> 定义的变量却不行。</p>
<p>这正是令我以为只有 var 定义的变量才会提升的原因。</p>
<p>不过，如我所言，我最近发现 <code>let</code> 或者 <code>const</code> 定义的变量也会提升。接下来，我会解释这个情况。</p>
<p>看以下代码：</p>
<pre><code class="language-js">console.log(number2)

let number = 10

</code></pre>
<p>我尝试在控制台打印名为 <code>number2</code> 的变量，接着又初始化了一个名为 <code>number</code> 的变量。</p>
<p>执行这段代码，结果报错了：<strong>ReferenceError: number2 is not defined</strong>。</p>
<p>注意到这个报错信息和之前的报错信息有何不同了吗？之前的报错信息是 <strong>ReferenceError: Cannot access 'number' before initialization</strong>，而这次则是 <strong>ReferenceError: number2 is not defined</strong>。</p>
<p>两者是有区别的，前一个是说“无法在初始化之前访问”，后一个是说“未定义”。</p>
<p>后一个报错意味着 JavaScript 完全不认识 <code>number2</code> 这个变量，因为找不到它的定义——事实上我们确实没定义它，我们只定义了 <code>number</code>。</p>
<p>但是前一个报错说的并不是“未定义”，而是“无法在初始化之前访问”。回顾以下代码：</p>
<pre><code class="language-js">console.log(number)
// ReferenceError: Cannot access 'number' before initialization

let number = 10

console.log(number)

</code></pre>
<p>这意味着 JavaScript “认识” <code>number</code> 变量。怎么回事？这是因为 <code>number</code> 被提升到全局作用域的顶部了。</p>
<p>可是为什么又会报错呢？这就体现出 <code>var</code> 与 <code>let</code>/<code>const</code> 的提升行为之间的区别了。</p>
<p>由 <code>let</code> 或 <code>const</code> 定义的变量提升时<strong>不会</strong>默认初始化，所以在声明之前访问会报错：<strong>ReferenceError: Cannot access 'variable' before initialization</strong>。</p>
<p>然而由 <code>var</code> 定义的变量提升时<strong>会</strong>被初始化为默认值 <code>undefined</code>，所以在声明之前访问会得到 <code>undefined</code>。</p>
<h2 id="">暂时性死区</h2>
<p><code>let</code>/<code>const</code> 定义的变量被提升却无法正常访问，是因为存在<strong>暂时性死区（Temporal Dead Zone）</strong>。</p>
<p>再次回顾之前的代码：</p>
<pre><code class="language-js">console.log(number)

let number = 10

console.log(number)

</code></pre>
<p><code>number</code> 变量就处于暂时性死区中，JavaScript 知道它的存在（因为它的声明被提升了），却无法正常访问它（因为它没有被初始化）。</p>
<h2 id="">总结</h2>
<p>如果你和我一样，以为只有 <code>var</code> 定义的变量会提升而 <code>let</code>/<code>const</code> 定义的变量则不然，希望本文能澄清这个错误的想法。</p>
<p>正如我在文中所说的，<code>let</code> 和 <code>const</code> 定义的变量是会提升的，只是它们提升的时候不会进行默认初始化，使得它们无法被访问（因为这些变量在暂时性死区里）。</p>
<p>另一方面，<code>var</code> 定义的变量在提升时会默认初始化为 <code>undefined</code>。</p>
<p>希望本文对你有所帮助 :)</p>
<!--kg-card-end: markdown--> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ HTML 中的空格 ]]>
                </title>
                <description>
                    <![CDATA[ 原文：HTML Space – How to Add Spaces in HTML [https://www.freecodecamp.org/news/html-space-how-to-add-spaces/]，作者：Quincy Larson [https://www.freecodecamp.org/news/author/quincylarson/] 在 HTML 中添加空格也许看起来挺困难的，不过我们至少有 5 种方法可用。 这个教程展示了许多示例，还会介绍空格的一些花式用法。 你可以不使用 CSS，仅用 HTML 就能实现这些效果，但是在实践中更推荐使用 CSS 来做。freeCodeCamp 有大量关于如何使用 CSS 盒模型 [https://www.freecodecamp.org/news/css-box-model-explained-with-examples/] 实现这个效果的教程。 空格的 ASCII 码是什么？ 空格对应的 ASCII 码值为 20。使用 ASCII 码只是标准方式，还有许多其它方法。 有 5 种方法可以向 HTML 中添加空格 ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/html-space-how-to-add-spaces/</link>
                <guid isPermaLink="false">629c7e45dbb8cc083a127e23</guid>
                
                    <category>
                        <![CDATA[ HTML ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Humilitas ]]>
                </dc:creator>
                <pubDate>Fri, 03 Jun 2022 04:30:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2022/06/jeremy-thomas-E0AHdsENmDg-unsplash-1.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>原文：<a href="https://www.freecodecamp.org/news/html-space-how-to-add-spaces/">HTML Space – How to Add Spaces in HTML</a>，作者：<a href="https://www.freecodecamp.org/news/author/quincylarson/">Quincy Larson</a></p><!--kg-card-begin: markdown--><p>在 HTML 中添加空格也许看起来挺困难的，不过我们至少有 5 种方法可用。</p>
<p>这个教程展示了许多示例，还会介绍空格的一些花式用法。</p>
<p>你可以不使用 CSS，仅用 HTML 就能实现这些效果，但是在实践中更推荐使用 CSS 来做。freeCodeCamp 有大量关于如何使用 <a href="https://www.freecodecamp.org/news/css-box-model-explained-with-examples/">CSS 盒模型</a>实现这个效果的教程。</p>
<h2 id="ascii">空格的 ASCII 码是什么？</h2>
<p>空格对应的 ASCII 码值为 20。使用 ASCII 码只是标准方式，还有许多其它方法。</p>
<p>有 5 种方法可以向 HTML 中添加空格，它们看起来都一样，但是表示的意义略有不同。</p>
<p>另外还有制表符（Tab），按下键盘上的 tab 键可以得到它；以及回车字符，按下键盘上的 enter 键可以得到。</p>
<table>
<thead>
<tr>
<th>字符</th>
<th style="text-align:center">HTML 代码</th>
</tr>
</thead>
<tbody>
<tr>
<td>不换行空格</td>
<td style="text-align:center"><code>&amp;nbsp; </code></td>
</tr>
<tr>
<td>En 空格</td>
<td style="text-align:center"><code>&amp;ensp;</code></td>
</tr>
<tr>
<td>Em 空格</td>
<td style="text-align:center"><code>&amp;emsp;</code></td>
</tr>
<tr>
<td>窄空格</td>
<td style="text-align:center"><code>&amp;thinsp;</code></td>
</tr>
<tr>
<td>标准空格</td>
<td style="text-align:center"><code>&amp;#20;</code></td>
</tr>
<tr>
<td>换行（Return)</td>
<td style="text-align:center"><code>&amp;#13;</code></td>
</tr>
<tr>
<td>制表符（Tab）</td>
<td style="text-align:center"><code>&amp;#09;</code></td>
</tr>
</tbody>
</table>
<h2 id="">空格字符的宽度是多少？</h2>
<p>空格字符的宽度通常有 4 种情况：</p>
<ol>
<li>标准宽度空格，又称"不换行空格"，因为它会禁用自动换行（或称回车）。（译注：这里的禁用自动换行指的是不在这个空格处换行，其它地方的换行行为不受影响。）</li>
<li>Em 空格，之所以被称为"Em"，是因为它的宽度等于当前所用字体中字母 M 的宽度。（如果你听说过 em-dash 的话，你应该知道它是宽度与字母 M 相同的破折号。）</li>
<li>En 空格，是宽度与字母 n 相同的空格。（译注：这里指的是小写字母 n；宽度为 em 的一半。）</li>
<li>最后，还有一种"窄空格"，它是宽度最小的一种空格。（译注：宽度通常为 em 的六分之一。实际上还有宽度比它更小的发宽空格（Hair Space），甚至还有宽度为 0 的零宽空格（Zero Width Space）。）</li>
</ol>
<h2 id="html">HTML 中的空格用什么符号表示？</h2>
<p>表示空格的最常用的 HTML 实体是 <code>&amp;#20;</code>。</p>
<p>可以用这个符号来强制渲染一个空格。</p>
<p>例如，你想要在一个句子后面留两个空格，但是有时候网页渲染引擎会自动合并连续的空格。可以通过输入 <code>&amp;#20;&amp;#20;</code> 来添加两个空格。</p>
<h2 id="ascii">空格属于非 ASCII 字符吗？</h2>
<p>不，空格是 ASCII 字符，它对应的 ASCII 码值为 20，可以这样来输入：<code>&amp;#20;</code>。</p>
<h2 id="html">如何在 HTML 中添加空格？</h2>
<p>也许你会想用 CSS 来将元素居中，而不是使用硬编码的空格。</p>
<p>不过如果你只是想用一种简单粗暴的方式在文本之间添加空格，可以重复使用空格字符：</p>
<pre><code class="language-html">[一些文本]&amp;emsp;&amp;emsp;&amp;emsp;&amp;emsp;&amp;emsp;&amp;emsp;[一些文本]
</code></pre>
<h2 id="">哪些字符看起来像空格但实际并不是？</h2>
<p>有两个字符看起来像空格，但实际上它们并不是：</p>
<ol>
<li>换行符——也叫"回车"，对应的 HTML 代码为 <code>&amp;#13;</code>。</li>
<li>制表符（Tab），在文本区域按下 tab 键可以得到一个制表符，对应的 HTML 代码为 <code>&amp;#09;</code>。</li>
</ol>
<p>希望这篇教程对你有所帮助。赶快试试吧。🚀</p>
<!--kg-card-end: markdown--> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 使用 CSS 制作丝带效果 ]]>
                </title>
                <description>
                    <![CDATA[ 原文：How to Create a CSS-Only Ribbon for Your Website [https://www.freecodecamp.org/news/make-a-css-only-ribbon/]，作者：Temani Afif [https://www.freecodecamp.org/news/author/temani-afif/] 你注意过网站上那些漂亮的丝带效果吗？它们可以用来提醒用户关注一些新特性或者大事件。这个效果很棒，不过对于很多开发者来说，创建这种效果却并不简单。 网上有很多现成的组件，很容易就能找到开箱即用的代码，不过很难对它们做自定义的修改。开发者还需要做很多调试工作并处理报错，直到它们满足需求为止。 本文会介绍如何使用 CSS 创建两种不同类型的丝带效果，使用这种方式就不再需要浪费时间去一直调试了。 这是我们将要创建的效果： 旋转丝带 & 折叠丝带下面是两种丝带效果的完整代码，看得出来它们非常简洁： 点击此处 [https://codepen.io/t_afif/pen/dyZOBex/55e02b7d8b1dffb5c1 ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/make-a-css-only-ribbon/</link>
                <guid isPermaLink="false">6209ccc4469075064c41d1f0</guid>
                
                    <category>
                        <![CDATA[ CSS ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Humilitas ]]>
                </dc:creator>
                <pubDate>Mon, 14 Feb 2022 03:00:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2022/02/final-header-ribbon.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>原文：<a href="https://www.freecodecamp.org/news/make-a-css-only-ribbon/">How to Create a CSS-Only Ribbon for Your Website</a>，作者：<a href="https://www.freecodecamp.org/news/author/temani-afif/">Temani Afif</a></p><!--kg-card-begin: markdown--><p>你注意过网站上那些漂亮的丝带效果吗？它们可以用来提醒用户关注一些新特性或者大事件。这个效果很棒，不过对于很多开发者来说，创建这种效果却并不简单。</p>
<p>网上有很多现成的组件，很容易就能找到开箱即用的代码，不过很难对它们做自定义的修改。开发者还需要做很多调试工作并处理报错，直到它们满足需求为止。</p>
<p>本文会介绍如何使用 CSS 创建两种不同类型的丝带效果，使用这种方式就不再需要浪费时间去一直调试了。</p>
<p>这是我们将要创建的效果：</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/02/image-18.png" alt="Rotated Ribbon &amp; Folder Ribbon" width="600" height="400" loading="lazy"></p>
<div style="text-align: center; margin-bottom: 2em;">旋转丝带 &amp; 折叠丝带</div>
<p>下面是两种丝带效果的完整代码，看得出来它们非常简洁：</p>
<p>点击<a href="https://codepen.io/t_afif/pen/dyZOBex/55e02b7d8b1dffb5c1a63473d5724dee">此处</a>在 CodePen 查看完整代码。</p>
<pre><code class="language-html">  &lt;div class="box"&gt;
    &lt;div class="ribbon-2"&gt;Folded Ribbon&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="ribbon-1 left"&gt;Rotated Ribbon&lt;/div&gt;
  &lt;div class="ribbon-1 right"&gt;Rotated Ribbon&lt;/div&gt;

</code></pre>
<pre><code class="language-css">  .ribbon-1 {
    position: fixed;
    background: #08769b;
    box-shadow: 0 0 0 999px #08769b;
    clip-path: inset(0 -100%);
  }
  .left {
    inset: 0 auto auto 0;
    transform-origin: 100% 0;
    transform: translate(-29.3%) rotate(-45deg);
  }
  .right {
    inset: 0 0 auto auto;
    transform-origin: 0 0;
    transform: translate(29.3%) rotate(45deg);
  }

  .ribbon-2 {
    --f: 10px; /* 控制折叠部分*/
    --r: 15px; /* 控制丝带形状 */
    --t: 10px; /* 上方偏移距离 */

    position: absolute;
    inset: var(--t) calc(-1*var(--f)) auto auto;
    padding: 0 10px var(--f) calc(10px + var(--r));
    clip-path:
      polygon(0 0,100% 0,100% calc(100% - var(--f)),calc(100% - var(--f)) 100%,
        calc(100% - var(--f)) calc(100% - var(--f)),0 calc(100% - var(--f)),
        var(--r) calc(50% - var(--f)/2));
    background: #BD1550;
    box-shadow: 0 calc(-1*var(--f)) 0 inset #0005;
  }

  .box {
    max-width:500px;
    height:200px;
    margin:50px auto 0;
    background:lightblue;
    position:relative;
  }
  
  html {
  color: #fff;
  line-height: 1.3em;
  font-family: sans-serif;
  font-size: 25px;
}

</code></pre>
<h2 id="">如何创建旋转丝带效果</h2>
<p>这种丝带通常用来在屏幕顶部展示固定的信息，当然也可以用在页面元素上。</p>
<p>为了理解旋转丝带的创建过程，我们来看下面的步骤示意图：</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/02/image-19.png" alt="Step-by-Step illustration of the Rotated Ribbon" width="600" height="400" loading="lazy"></p>
<div style="text-align: center; margin-bottom: 2em;">旋转丝带实现步骤示意图</div>
<p>首先，将丝带元素置于屏幕左上角，红色边框表示屏幕（或者想要在其上添加丝带的页面元素）的边界。</p>
<pre><code class="language-css">.ribbon {
  position: fixed;
  inset: 0 auto auto 0;
  background: #08769b;
}
</code></pre>
<p>目前没什么复杂的。也许你不太了解 <code>inset</code> 属性，它其实是 <code>top</code>、<code>right</code>、<code>bottom</code> 和 <code>left</code> 属性的简写。</p>
<p>接下来，将元素向左偏移：<code>translate(-29.3%)</code>。</p>
<p>之后，旋转元素：<code>rotate(-45deg)</code>，现在完整代码如下：</p>
<pre><code class="language-css">.ribbon {
  position: fixed;
  inset: 0 auto auto 0;
  background: #08769b;
  transform-origin: 100% 0; /* 或 top right */
  transform: translate(-29.3%) rotate(-45deg);
}
</code></pre>
<p>也许你会很好奇，这个 <code>29.3%</code> 是怎么得来的？它的计算公式为：<code>100% * (1 - cos(45deg))</code>。</p>
<p>这里就不做枯燥的数学解释了，总之结果就是旋转之后完美地放置了这个元素（它的左上角和右上角正好处于边界位置）。</p>
<p>你可能还注意到了 <code>transform-origin: top right</code>，这个步骤是将旋转的参考点指定为右上角。</p>
<p>现在元素已经正确地放置了，还需要填充边界的空白。我用一个很大的阴影（<code>box-shadow</code>）来实现，在示意图中用绿色来表示以便区分，实际上它的颜色应该和背景色（<code>background</code>）一致。</p>
<p>接着需要裁切这个阴影，让它只显示左右两边的部分。我会用到 <code>clip-path</code>，<code>inset(0 -100%)</code> 表示将上下两边的阴影裁切掉（值为 <code>0</code>），只显示左右两边（值为 <code>-100%</code>）。</p>
<p>这里的 <code>999px</code> 也可以是别的值，只要它足够大，比如 <code>100vmax</code>——能确保正确显示出左右两边的阴影就行。</p>
<p>现在可以看到最终结果了，其实还有一些溢出的阴影，但是由于元素放在了屏幕左上角边界，所以没人看得见。</p>
<p>如果想把丝带用在页面中的元素上，别忘了给它的父元素设置 <code>overflow: hidden</code>，还要把 <code>fixed</code> 改成 <code>absolute</code>。</p>
<p>最终代码如下：</p>
<pre><code class="language-css">.ribbon-1 {
  position: fixed;
  inset: 0 auto auto 0;
  background: #08769b;
  transform-origin: 100% 0;
  transform: translate(-29.3%) rotate(-45deg);
  box-shadow: 0 0 0 999px #08769b;
  clip-path: inset(0 -100%);
}
</code></pre>
<p>我们只用了 7 个样式声明就实现了旋转丝带效果。我们的代码是通用的，并不依赖于其中的文字内容。不论丝带中的内容是什么，它都能正确地展示，甚至可以展示多行文本。</p>
<p>想要把丝带放在右上角的话，只需要修改几个属性值。更好的做法是使用两个样式来分别控制：</p>
<pre><code class="language-css">.ribbon-1 {
  position: fixed;
  background: #08769b;
  box-shadow: 0 0 0 999px #08769b;
  clip-path: inset(0 -100%);
}
.left {
  inset: 0 auto auto 0; /* top 和 left 的值为 0 */
  transform-origin: 100% 0; /* 或 top right */
  transform: translate(-29.3%) rotate(-45deg);
}
.right {
  inset: 0 0 auto auto; /* top 和 right 的值 0 */
  transform-origin: 0 0; /* 或 top left */
  transform: translate(29.3%) rotate(45deg);
}
</code></pre>
<p>我觉得上面的代码是自解释的，<code>.left</code> 和 <code>.right</code> 两个样式之间的区别也不难理解。</p>
<h2 id="">如何创建折叠丝带</h2>
<p>同样的，一起来看示意图：</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/02/image-20.png" alt="Step-by-Step illustration of the Folded Ribbon" width="600" height="400" loading="lazy"></p>
<div style="text-align: center; margin-bottom: 2em;">折叠丝带实现步骤示意图</div>
<p>首先，将元素放置在父元素的右边：</p>
<pre><code class="language-css">.ribbon-2 {
  --t: 10px; /* 上方偏移距离 */

  position: absolute;
  inset: var(--t) 0 auto auto;
  padding:0 10px;
  background: #BD1550;

}
</code></pre>
<p>我定义了一个变量来控制元素上方的偏移距离，以便通过这个变量来调整丝带的位置。因为用到了 <code>position: absolute</code>，所以别忘了给丝带的父元素指定 <code>position: relative</code>。</p>
<p>我还在左右两边增加了一些内边距（padding），这里指定的 <code>10px</code> 并没有什么特殊含义——你想指定其他值也可以。</p>
<p>现在要引入另一个变量来控制折叠的部分，我用这个变量来定义一个内嵌的阴影：<code>box-shadow: 0 calc(-1*var(--f)) 0 #0005</code>。</p>
<p>正如你在上图中看到的，这个阴影在元素底部生成了一个半透明的黑色色块，高度由 <code>--f</code> 变量指定。我增加了底部内边距，留出展示阴影的空间：<code>padding: 0 10px var(--f)</code>。</p>
<p>接着，继续使用 <code>--f</code> 变量，将丝带往右偏移：把 <code>right:0</code> 更改为 <code>right: calc(-1*var(--f))</code>。</p>
<p>现在代码如下：</p>
<pre><code class="language-css">.ribbon-2 {
  --t: 10px; /* 上方偏移距离 */
  --f :10px /* 控制折叠部分 */

  position: absolute;
  inset: var(--t) calc(-1*var(--f)) auto auto; /* right 属性合并到这里了*/
  padding:0 10px var(--f);
  background: #BD1550;
  box-shadow: 0 calc(-1*var(--f)) 0 inset #0005;
}
</code></pre>
<p>代码看起来可能有点奇怪（实现的效果也是），不过下一步我们会创建出最终的形状，到时一切就都能说得通了。</p>
<p>最后一步，我们要引入 <code>clip-path</code> 来裁切元素。我加入了一个变量 <code>--r</code> 来控制丝带箭头的形状。</p>
<p>在加入 clip-path 之前，首先增加左内边距，为箭头形状留出充足空间：</p>
<pre><code class="language-css">padding: 0 10px var(--f) calc(10px + var(--r));
</code></pre>
<ul>
<li>上内边距为 <code>0</code></li>
<li>右内边距为 <code>10px</code>（一个随机值）</li>
<li>下内边距由 <code>--f</code> 指定</li>
<li>左内边距为 <code>10px</code>（与右内边距相同）与变量 <code>--r</code> 的值之和</li>
</ul>
<p>现在加入 <code>clip-path</code>，下图可以帮助我们理解如何通过裁切路径得到最终的形状。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/02/image-22.png" alt="image-22" width="600" height="400" loading="lazy"></p>
<div style="text-align: center; margin-bottom: 2em;">裁切路径示意图</div>
<p>路径由 7 个点组成，从点(1) 开始，跟随箭头方向，可以得到以下代码：</p>
<pre><code class="language-css">clip-path: polygon(
  0 0,  /* (1) */
  100% 0, /* (2) */
  100% calc(100% - var(--f)), /* (3) */
  calc(100% - var(--f)) 100%, /* (4) */
  calc(100% - var(--f)) calc(100% - var(--f)), /* (5) */
  0 calc(100% - var(--f)), /* (6) */
  var(--r) calc(50% - var(--f)/2) /* (7) */
)
</code></pre>
<p>如果不熟悉 <code>clip-path</code> 也无需担心——虽然看着可能有点陌生。你并不需要操作这个路径，只需要改动 CSS 变量来控制形状就行了。</p>
<p>当然，试着改动一些数值来调整路径能够帮助你更好地理解它的原理。</p>
<p>完工了。最终代码如下：</p>
<pre><code class="language-css">.ribbon-2 {
  --f: 10px; /* 控制折叠部分*/
  --r: 15px; /* 控制丝带形状 */
  --t: 10px; /* 上方偏移距离 */

  position: absolute;
  inset: var(--t) calc(-1*var(--f)) auto auto;
  padding: 0 10px var(--f) calc(10px + var(--r));
  clip-path:
    polygon(0 0,100% 0,100% calc(100% - var(--f)),calc(100% - var(--f)) 100%,
      calc(100% - var(--f)) calc(100% - var(--f)),0 calc(100% - var(--f)),
      var(--r) calc(50% - var(--f)/2));
  background: #BD1550;
  box-shadow: 0 calc(-1*var(--f)) 0 inset #0005;
}
</code></pre>
<p>可以通过调整变量数值来获取不同的效果：</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/02/right-ribbon.png" alt="right-ribbon" width="600" height="400" loading="lazy"></p>
<p>与旋转丝带类似，我们也可以通过修改几个属性值，从而将丝带位置从右边移到左边——不过这里我就不给出代码了，留给你自己去尝试。</p>
<p>尝试理解需要调整的各个参数（尤其是 <code>clip-path</code>）是很好的做法。有任何问题都可以<a href="https://twitter.com/ChallengesCss">随时找我</a>。</p>
<h2 id="">总结</h2>
<p>现在你学会了如何使用 CSS 制作漂亮的丝带效果了。</p>
<p>我还写了<a href="https://dev.to/this-is-learning/fully-responsive-css-folded-ribbon-11h5">另外一篇关于制作丝带效果的文章</a>，有兴趣的话可以看看。其中详细介绍了如何制作旋转的折叠丝带——它结合了本文介绍的两种效果。</p>
<p>感谢阅读！</p>
<p>想要了解更多 CSS 技巧，可以关注我的 <a href="https://twitter.com/ChallengesCss">Twitter</a>。可以<a href="https://www.buymeacoffee.com/afif">为我买杯咖啡</a>或<a href="https://www.patreon.com/temani">赞助我</a>以表达对我的支持。</p>
<!--kg-card-end: markdown--> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 什么是 .gitkeep？如何让 Git 追踪空文件夹 ]]>
                </title>
                <description>
                    <![CDATA[ 假设有这样一个场景：你需要重新组织代码库的文件结构——调整文件夹的位置、把一些文件移动到新创建的文件夹中。 你开始移动代码、检查一切是否正常、新增一些文件夹供下次开发使用。这些新文件夹目前还是空的，下次开发会在两天内开始，但是由于现在就要迁移代码，所以你觉得最好把这些空文件夹先加入代码库。 你把所有东西都推送到项目分支，等待 QA 测试。你在 Slack 上告诉负责审查的同事你的工作已经完成，可以进行审查了。 然后他们克隆你的分支进行审查，由于你忘了把本该添加的文件夹加入代码库，所以审查无法通过。 等等……怎么回事？ 为什么会这样？ 这是因为 Git 不会推送空文件夹 [https://git.wiki.kernel.org/index.php/Git_FAQ#Can_I_add_empty_directories.3F] ，它只会追踪文件。 虽然本地有这些文件夹，但是你尝试推送之后，它们并不会进入你的分支。 所以别人克隆你的代码得到的文件结构和你的本地环境不一致。 那么要如何解决这个问题呢？ 如何使用 .gitkeep 我们知道了 Git 只追踪文件，所以只要往这些 ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/what-is-gitkeep/</link>
                <guid isPermaLink="false">619475e7a8fafe063bba2358</guid>
                
                    <category>
                        <![CDATA[ Git ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Humilitas ]]>
                </dc:creator>
                <pubDate>Mon, 22 Nov 2021 03:00:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2021/11/Capture.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>假设有这样一个场景：你需要重新组织代码库的文件结构——调整文件夹的位置、把一些文件移动到新创建的文件夹中。</p>
<p>你开始移动代码、检查一切是否正常、新增一些文件夹供下次开发使用。这些新文件夹目前还是空的，下次开发会在两天内开始，但是由于现在就要迁移代码，所以你觉得最好把这些空文件夹先加入代码库。</p>
<p>你把所有东西都推送到项目分支，等待 QA 测试。你在 Slack 上告诉负责审查的同事你的工作已经完成，可以进行审查了。</p>
<p>然后他们克隆你的分支进行审查，由于你忘了把本该添加的文件夹加入代码库，所以审查无法通过。</p>
<p>等等……怎么回事？</p>
<h1 id="">为什么会这样？</h1>
<p>这是因为 <a href="https://git.wiki.kernel.org/index.php/Git_FAQ#Can_I_add_empty_directories.3F">Git 不会推送空文件夹</a>，它只会追踪文件。</p>
<p>虽然本地有这些文件夹，但是你尝试推送之后，它们并不会进入你的分支。</p>
<p>所以别人克隆你的代码得到的文件结构和你的本地环境不一致。</p>
<p>那么要如何解决这个问题呢？</p>
<h1 id="gitkeep">如何使用 .gitkeep</h1>
<p>我们知道了 Git 只追踪文件，所以只要往这些文件夹中加入一些文件就行了。</p>
<p>加入任何文件都是可行的。只需要往里面加入简单的虚拟文件，就可以使得文件夹被追踪，从而正常推送。</p>
<p>复制一个空的文本文件（如 <code>file.txt</code>）粘贴进去是可行的，你想放一张猫咪图片也行。</p>
<p>然而，常见的标准做法是在空文件夹中放一个 .<code>gitkeep</code> 文件。</p>
<p>这并不是 Git 提供的特性！所以你可以随意给这个文件命名。<code>.gitkeep</code> 这个名字并没有什么特殊含义——有些开发者也会用 <code>.gitignore</code>。（译注：然而 <code>.gitignore</code> 文件是有<a href="https://git-scm.com/docs/gitignore">特殊含义</a>的，最好别用这个名字。在实践中推荐使用 <code>.gitkeep</code>，如果随意命名可能会造成困惑。）</p>
<p>不过 <code>.gitignore</code> 会令人迷惑，因为你的目的是让 Git <strong>不要忽略</strong>你的文件，并且把它推送到分支。</p>
<p>不管使用那种方式，通过添加这个简单的文件，你的文件夹到时就能够被正确推送。</p>
<h1 id="">总结</h1>
<p><code>.gitkeep</code> 在代码库中是很常见的，如果需要让 Git 追踪一个空文件夹，那其中一般会有这个文件。</p>
<p>这个虚拟文件的名字不一定总是 <code>.gitkeep</code>，不过作为开发者，你会一次又一次地看到这种实践。</p>
<p>如果你喜欢我的文章，可以关注我的 <a href="https://twitter.com/kealanparr">Twitter</a>，以了解文章发布动态。</p>
<!--kg-card-end: markdown--><p>原文：<a href="https://www.freecodecamp.org/news/what-is-gitkeep/">What is .gitkeep? How to Track and Push Empty Folders in Git</a>，作者：<a href="https://www.freecodecamp.org/news/author/kealan/">Kealan Parr</a></p> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 构建可重用的 React 组件 ]]>
                </title>
                <description>
                    <![CDATA[ 在这个教程中，我们会构建一个 React 应用，并学习如何从头开始构建一个可重用的自动建议组件。 这个应用能让用户在一个国家列表中搜索某个国家，它会根据用户输入的内容在输入框下面展示建议。 通过构建这个应用，你将学到：  * 如何创建可重用的组件  * 如何使用 useRef  hook 实现自动建议  * 如何创建自定义的可重用 hook  * 如何高效执行搜索 以及更多其他内容。 你可以在这里 [https://react-autosuggestion-app.netlify.app/]查看这个应用的在线示例。 下图是自动建议组件的功能演示。 开始构建应用吧。 初始化项目 我们使用 create-react-app 来初始化项目。 构建组件时会用到 React Hooks 语法，如果你对它不太了解，可以查看这篇文章 [https://levelup.gitconnected.com/an-introduction-to-react-hooks-50281fd961fe?source=friends_link&sk=89baff89ec8bc637e7c13b7 ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/how-to-create-a-truly-reusable-react-component-from-scratch/</link>
                <guid isPermaLink="false">612f37ddabc6f30645eccd9c</guid>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                    <category>
                        <![CDATA[ 组件 ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Humilitas ]]>
                </dc:creator>
                <pubDate>Wed, 01 Sep 2021 08:00:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2021/09/pexels-sarah-chai-7262760.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>在这个教程中，我们会构建一个 React 应用，并学习如何从头开始构建一个可重用的自动建议组件。</p>
<p>这个应用能让用户在一个国家列表中搜索某个国家，它会根据用户输入的内容在输入框下面展示建议。</p>
<p>通过构建这个应用，你将学到：</p>
<ul>
<li>如何创建可重用的组件</li>
<li>如何使用 <code>useRef</code> hook 实现自动建议</li>
<li>如何创建自定义的可重用 hook</li>
<li>如何高效执行搜索</li>
</ul>
<p>以及更多其他内容。</p>
<p>你可以在<a href="https://react-autosuggestion-app.netlify.app/">这里</a>查看这个应用的在线示例。</p>
<p>下图是自动建议组件的功能演示。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/08/suggestion_demo.gif" alt="suggestion_demo" width="600" height="400" loading="lazy"></p>
<p>开始构建应用吧。</p>
<h2 id="">初始化项目</h2>
<p>我们使用 create-react-app 来初始化项目。</p>
<p>构建组件时会用到 React Hooks 语法，如果你对它不太了解，可以查看<a href="https://levelup.gitconnected.com/an-introduction-to-react-hooks-50281fd961fe?source=friends_link&amp;sk=89baff89ec8bc637e7c13b7554904e54">这篇文章</a>。</p>
<p>执行以下命令，新建一个 React 项目：</p>
<pre><code class="language-js">npx create-react-app react-autosuggestion-app
</code></pre>
<p>项目创建完成后，删除 <code>src</code> 文件夹中的所有文件，并在其中新建 <code>index.js</code>、<code>App.js</code> 和 <code>styles.css</code> 文件。</p>
<p>接着在 <code>src</code> 文件夹中创建 <code>components</code> 和 <code>custom-hooks</code> 文件夹。</p>
<p>在终端或命令行中执行以下命令，安装必要的依赖：</p>
<pre><code class="language-js">yarn add axios@0.21.1 lodash@4.17.21 react-bootstrap@1.6.1 bootstrap@5.1.0
</code></pre>
<p>安装完成之后，打开 <code>src/styles.css</code> 文件，并写入<a href="https://github.com/myogeshchavan97/react-autosuggestion-app/blob/master/src/styles.css">这些内容</a>。</p>
<h2 id="">构建初始页面</h2>
<p>在 <code>public</code> 文件夹中新建 <code>countries.json</code> 文件，并写入<a href="https://github.com/myogeshchavan97/react-autosuggestion-app/blob/master/public/countries.json">这些内容</a>。</p>
<p>在 <code>components</code> 文件夹中新建 <code>AutoComplete.js</code> 文件，并写入以下代码：</p>
<pre><code class="language-js">import React from 'react';

function AutoComplete({ isVisible, suggestions, handleSuggestionClick }) {
  return (
    &lt;div className={`${isVisible ? 'show suggestion-box' : 'suggestion-box'}`}&gt;
      &lt;ul&gt;
        {suggestions.map((country, index) =&gt; (
          &lt;li key={index} onClick={() =&gt; handleSuggestionClick(country)}&gt;
            {country}
          &lt;/li&gt;
        ))}
      &lt;/ul&gt;
    &lt;/div&gt;
  );
}

export default AutoComplete;
</code></pre>
<p>每当用户在输入框中输入一些内容的时候，这个组件就会为其展示建议。</p>
<p>在 <code>custom-hooks</code> 文件夹中新建 <code>useOutsideClick.js</code> 文件，写入以下代码：</p>
<pre><code class="language-js">import { useState, useRef, useEffect } from 'react';

const useOutsideClick = () =&gt; {
  const [isVisible, setIsVisible] = useState(false);
  const ref = useRef();

  const handleOutsideClick = () =&gt; {
    if (ref.current) {
      setIsVisible(false);
    }
  };

  useEffect(() =&gt; {
    document.addEventListener('click', handleOutsideClick);
    return () =&gt; {
      document.removeEventListener('click', handleOutsideClick);
    };
  }, []);

  return [ref, isVisible, setIsVisible];
};

export default useOutsideClick;
</code></pre>
<p>我们在这里创建了一个自定义的 hook，用来控制建议信息的显示/隐藏。</p>
<p>我们将 <code>isVisible</code> 的初始值设为 <code>false</code>，默认隐藏建议信息：</p>
<pre><code class="language-js">const [isVisible, setIsVisible] = useState(false);
</code></pre>
<p>接着声明了一个 ref：</p>
<pre><code class="language-js">const ref = useRef();
</code></pre>
<p>我们的自定义 hook 将这个 <code>ref</code> 和 <code>isVisible</code> 以及 <code>setIsVisible</code> 一起返回：</p>
<pre><code class="language-js">return [ref, isVisible, setIsVisible];
</code></pre>
<p>在使用了 <code>useOutsideClick</code> hook 的组件内部，可以把这个 ref 赋值给对应的建议框。所以如果表单中存在多个输入框，每个输入框都会有其对应的建议框，可以独立地控制显示或隐藏（可以参考后文的“如何使用这个可重用组件”。</p>
<p>在 <code>handleOutsideClick</code> 函数中，有以下代码：</p>
<pre><code class="language-js">const handleOutsideClick = () =&gt; {
  if (ref.current) {
    setIsVisible(false);
  }
};
</code></pre>
<p>这里检查了 <code>ref.current</code>，因为我们希望只在建议框的 ref 可用的情况下才在这里调用 <code>setIsVisible</code> 函数，而不是每次点击页面都调用。</p>
<p>接着为 <code>document</code> 的 <code>click</code> 事件增加了事件处理函数 <code>handleOutsideClick</code>：</p>
<pre><code class="language-js">useEffect(() =&gt; {
  document.addEventListener('click', handleOutsideClick);
  return () =&gt; {
    document.removeEventListener('click', handleOutsideClick);
  };
}, []);
</code></pre>
<p>我们在 <code>useEffect</code> hook 返回了一个函数，以便在组件卸载的时候移除事件处理函数。</p>
<h2 id="react">如何创建可重用的 React 组件</h2>
<p>在 <code>components</code> 文件夹中新建 <code>InputControl.js</code> 文件，写入以下代码：</p>
<pre><code class="language-js">/* eslint-disable react-hooks/exhaustive-deps */
import React, { useState, useEffect, useRef } from 'react';
import axios from 'axios';
import _ from 'lodash';
import { Form } from 'react-bootstrap';
import AutoComplete from './AutoComplete';
import useOutsideClick from '../custom-hooks/useOutsideClick';

const InputControl = ({ name, label, placeholder }) =&gt; {
  const [documentRef, isVisible, setIsVisible] = useOutsideClick();
  const [suggestions, setSuggestions] = useState([]);
  const [selectedCountry, setSelectedCountry] = useState('');
  const [searchTerm, setSearchTerm] = useState('');
  const [errorMsg, setErrorMsg] = useState('');
  const ref = useRef();

  useEffect(() =&gt; {
    ref.current = _.debounce(processRequest, 300);
  }, []);

  function processRequest(searchValue) {
    axios
      .get('/countries.json')
      .then((response) =&gt; {
        const countries = response.data;
        const result = countries.filter((country) =&gt;
          country.toLowerCase().includes(searchValue.toLowerCase())
        );
        setSuggestions(result);
        if (result.length &gt; 0) {
          setIsVisible(true);
        } else {
          setIsVisible(false);
        }
        setErrorMsg('');
      })
      .catch(() =&gt; setErrorMsg('Something went wrong. Try again later'));
  }

  function handleSearch(event) {
    event.preventDefault();
    const { value } = event.target;
    setSearchTerm(value);
    ref.current(value);
  }

  function handleSuggestionClick(countryValue) {
    setSelectedCountry(countryValue);
    setIsVisible(false);
  }

  return (
    &lt;Form.Group controlId="searchTerm"&gt;
      &lt;Form.Label&gt;{label}&lt;/Form.Label&gt;
      &lt;Form.Control
        className="input-control"
        type="text"
        value={searchTerm}
        name={name}
        onChange={handleSearch}
        autoComplete="off"
        placeholder={placeholder}
      /&gt;
      &lt;div ref={documentRef}&gt;
        {isVisible &amp;&amp; (
          &lt;AutoComplete
            isVisible={isVisible}
            suggestions={suggestions}
            handleSuggestionClick={handleSuggestionClick}
          /&gt;
        )}
      &lt;/div&gt;
      {selectedCountry &amp;&amp; (
        &lt;div className="selected-country"&gt;
          Your selected country: {selectedCountry}
        &lt;/div&gt;
      )}
      {errorMsg &amp;&amp; &lt;p className="errorMsg"&gt;{errorMsg}&lt;/p&gt;}
    &lt;/Form.Group&gt;
  );
};

export default InputControl;
</code></pre>
<p>我们创建了一个可重用组件，它包含搜索和建议的功能。</p>
<p>我们用到了 <code>useOutsideClick</code> hook：</p>
<pre><code class="language-js">const [documentRef, isVisible, setIsVisible] = useOutsideClick();
</code></pre>
<p>我们把这个 hook 返回的 ref 保存在 <code>documentRef</code> 变量中。每当用户在文本框中输入内容的时候，我们都会通过一个 API 调用来获取符合搜索条件的国家列表。</p>
<p>为了避免在输入过程中产生不必要的 API 调用，我们会用到 <a href="https://lodash.com/">lodash</a> 的 debounce 方法（因为监听了每次输入都会触发的 <code>change</code> 事件，如果不使用 debounce 处理则每输入一个字符都会产生一次 API 调用）。它使得我们的应用只在用户停止输入 300 毫秒之后才调用 API：</p>
<pre><code class="language-js">ref.current = _.debounce(processRequest, 300);
</code></pre>
<p><code>_.debounce</code> 函数返回一个函数，我们将它保存在 <code>ref.current</code> 变量中。我们会在用户停止输入 300 毫秒之后调用 processRequest 函数。</p>
<p>我们在这里使用 ref 来保存这个函数，而不是使用普通的变量，是为了只在组件挂载的时候执行一次初始化。（每当组件由于 state 或 prop 发生变化而重新渲染时，普通变量的值都会丢失。）</p>
<p>在 <code>handleSearch</code> 函数内部，我们以用户输入的值作为参数调用保存在 <code>ref.current</code> 中的函数。</p>
<p>当我们调用保存在 <code>ref.current</code> 中的函数时，<code>processRequest</code> 函数会在背后执行。（考虑到 <a href="https://www.lodashjs.com/docs/lodash.debounce">debounce</a> 函数的作用，并非每次调用 <code>ref.current</code> 都一定对应着一次 <code>processRequest</code> 调用。）</p>
<p><code>processRequest</code> 函数会自动接收到我们传递给 <code>ref.current</code> 函数的参数。</p>
<p>在 <code>processRequest</code> 函数内部，我们发起了一个 API 调用来获取国家列表。</p>
<pre><code class="language-js">function processRequest(searchValue) {
  axios
    .get('/countries.json')
    .then((response) =&gt; {
      const countries = response.data;
      const result = countries.filter((country) =&gt;
        country.toLowerCase().includes(searchValue.toLowerCase())
      );
      setSuggestions(result);
      if (result.length &gt; 0) {
        setIsVisible(true);
      } else {
        setIsVisible(false);
      }
      setErrorMsg('');
    })
    .catch(() =&gt; setErrorMsg('Something went wrong. Try again later'));
}
</code></pre>
<p>得到 API 返回的响应数据后，使用数组的 filter 方法过滤出与搜索条件相匹配的国家。</p>
<p>接着使用 <code>setSuggestions(result)</code> 方法更新表示建议信息的 state。</p>
<p>再接下来，检查结果数组的长度（length），判断是否要展示建议。</p>
<p>这个组件返回的 JSX 如下：</p>
<pre><code class="language-js">return (
  &lt;Form.Group controlId="searchTerm"&gt;
    &lt;Form.Label&gt;{label}&lt;/Form.Label&gt;
    &lt;Form.Control
      className="input-control"
      type="text"
      value={searchTerm}
      name={name}
      onChange={handleSearch}
      autoComplete="off"
      placeholder={placeholder}
    /&gt;
    &lt;div ref={documentRef}&gt;
      {isVisible &amp;&amp; (
        &lt;AutoComplete
          isVisible={isVisible}
          suggestions={suggestions}
          handleSuggestionClick={handleSuggestionClick}
        /&gt;
      )}
    &lt;/div&gt;
    {selectedCountry &amp;&amp; (
      &lt;div className="selected-country"&gt;
        Your selected country: {selectedCountry}
      &lt;/div&gt;
    )}
    {errorMsg &amp;&amp; &lt;p className="errorMsg"&gt;{errorMsg}&lt;/p&gt;}
  &lt;/Form.Group&gt;
);
</code></pre>
<p>我们为输入框添加了 onChange 处理函数 <code>handleSearch</code>：</p>
<pre><code class="language-js">function handleSearch(event) {
  event.preventDefault();
  const { value } = event.target;
  setSearchTerm(value);
  ref.current(value);
}
</code></pre>
<p>我们将 <code>searchTerm</code> state 的值更新为用户输入的内容，接着以用户输入的内容作为参数调用了保存在 <code>ref.current</code> 中的函数。</p>
<p>调用 <code>ref.current</code> 相当于调用 <code>processRequest</code>，我们在其内部实际调用了 API。</p>
<p>在输入框之后，我们还增加了一个指定了 ref 属性的 div，用来展示建议信息：</p>
<pre><code class="language-js">&lt;div ref={documentRef}&gt;
  {isVisible &amp;&amp; (
    &lt;AutoComplete
      isVisible={isVisible}
      suggestions={suggestions}
      handleSuggestionClick={handleSuggestionClick}
    /&gt;
  )}
&lt;/div&gt;
</code></pre>
<p>仅当 <code>isVisible</code> 为 true 时才展示建议信息，我们在 <code>processRequest</code> 函数中访问接口得到数据之后，会再做判断以确定 <code>isVisible</code> 的值。</p>
<p>我们将建议数据传入 <code>AutoComplete</code> 组件以便展示。</p>
<p>如果点击任意一个建议选项，<code>handleSuggestionClick</code> 函数就会执行，它会更新 <code>selectedCountry</code> 并隐藏建议信息：</p>
<pre><code class="language-js">function handleSuggestionClick(countryValue) {
  setSelectedCountry(countryValue);
  setIsVisible(false);
}
</code></pre>
<h2 id="">如何使用这个可重用组件</h2>
<p>打开 <code>App.js</code>，在其中写入以下代码：</p>
<pre><code class="language-js">import React from 'react';
import { Form } from 'react-bootstrap';
import InputControl from './components/InputControl';

const App = () =&gt; {
  return (
    &lt;div className="main"&gt;
      &lt;h1&gt;React AutoSuggestion Demo&lt;/h1&gt;
      &lt;div className="search-form"&gt;
        &lt;Form&gt;
          &lt;InputControl
            name="country"
            label="Enter Country"
            placeholder="Type a country name"
          /&gt;
        &lt;/Form&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  );
};

export default App;
</code></pre>
<p>在终端或命令行中执行以下命令来启动应用：</p>
<pre><code class="language-js">yarn start
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/08/2.gif" alt="2" width="600" height="400" loading="lazy"></p>
<p>如你所见，你在建议列表中选中的国家会展示在下方的文本中。</p>
<p><strong>注意</strong>：我们创建了一个独立的 <code>InputControl</code> 组件用来展示输入框及其对应的建议信息。</p>
<p>所以我们可以以如下方式重用 <code>InputControl</code> 组件，为另一个输入框展示建议信息：</p>
<pre><code class="language-js">import React from 'react';
import { Form } from 'react-bootstrap';
import InputControl from './components/InputControl';

const App = () =&gt; {
  return (
    &lt;div className="main"&gt;
      &lt;h1&gt;React AutoSuggestion Demo&lt;/h1&gt;
      &lt;div className="search-form"&gt;
        &lt;Form&gt;
          &lt;InputControl
            name="country"
            label="Enter Country"
            placeholder="Type a country name"
          /&gt;
          &lt;InputControl
            name="country"
            label="Enter Country"
            placeholder="Type a country name"
          /&gt;
        &lt;/Form&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  );
};

export default App;
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/08/3.gif" alt="3" width="600" height="400" loading="lazy"></p>
<p>如你所见，我们添加了另一个 <code>InputControl</code> 组件，因此可以分别处理两个输入框各自的建议信息。</p>
<p>所以假如你想要为另一个输入框展示其它建议的话，可以为 <code>InputControl</code> 组件额外传入一个 prop，用来指明应该展示哪种类型的建议信息（需要对 <code>InputControl</code> 组件做相应的修改）。</p>
<h2 id="">总结</h2>
<p>正如我们在教程中看到的，通过创建可重用的 <code>InputControl</code> 组件并使用 <code>ref</code> 来分别管理每个输入框的建议信息，我们创建了真正可重用的自动完成建议组件。</p>
<p><strong>你可以查看本教程的<a href="https://github.com/myogeshchavan97/react-autosuggestion-app">源代码</a>和<a href="https://react-autosuggestion-app.netlify.app/">在线示例</a>。</strong></p>
<h2 id=""><strong>感谢阅读！</strong></h2>
<p>想要学习 Redux 并用它构建应用吗？请查看这个课程——<a href="https://master-redux.yogeshchavan.dev/">掌握 Redux</a>。</p>
<p>在这个课程中，你将会学到：</p>
<ul>
<li>基础和进阶的 Redux 知识</li>
<li>如何管理复杂的 state，如数组和对象</li>
<li>如何使用多个 reducer 管理复杂的 redux state</li>
<li>如何调试 Redux 应用</li>
<li>如何使用 react-redux 库实现数据响应式</li>
<li>如何使用 redux-thunk 库处理异步 API 调用</li>
<li>使用 Redux 构建三个不同的应用</li>
</ul>
<p>以及更多其他内容。</p>
<p>最终，我们会从头开始构建一个完整的<a href="https://www.youtube.com/watch?v=2zaPDfCKAvM">订餐应用</a>、通过 stripe 集成支付并发布上线。</p>
<p><a href="https://www.linkedin.com/in/yogesh-chavan97/">关注我的 LinkedIn</a>，以便收到 JavaScript、React 及 Node.js 相关内容的更新通知。</p>
<!--kg-card-end: markdown--><p>原文：<a href="https://www.freecodecamp.org/news/how-to-create-a-truly-reusable-react-component-from-scratch/">How to Create a Truly Reusable React Component from Scratch</a>，作者：<a href="https://www.freecodecamp.org/news/author/yogesh/">Yogesh Chavan</a></p> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Linux 符号链接教程——如何创建和删除符号链接 ]]>
                </title>
                <description>
                    <![CDATA[ 符号链接（symbolic link）是 Linux 系统中的一种文件，它指向系统中的另一个文件或目录。符号链接类似于 Windows 系统中的快捷方式。 也有人称它“软链接（soft links）”——Linux/UNIX 系统中的一种链接形式——与之对应的是“硬链接（hard links）”。 软链接和硬链接的区别 软链接类似于快捷方式，它可以指向任意文件系统中的一个文件或目录。 硬链接也可以看作是文件或目录的快捷方式，但是无法在两个不同文件系统之间创建硬链接。 我们将会学习如何创建及删除符号链接，还会了解什么是失效链接，以及如何删除它们。 如何创建符号链接 创建符号链接的语法： ln -s <path to the file/folder to be linked> <the path of the link to be created> ln  是链接命令，-s  指定此链接为软链接，-s  也可以写为 -symbolic。 ln  命令默认会创建硬链接。path to the file (or folder)  ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/symlink-tutorial-in-linux-how-to-create-and-remove-a-symbolic-link/</link>
                <guid isPermaLink="false">611f20ceb03439064c61c579</guid>
                
                    <category>
                        <![CDATA[ Linux ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Humilitas ]]>
                </dc:creator>
                <pubDate>Fri, 20 Aug 2021 03:20:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2021/08/5f9c9b4f740569d1a4ca2b02-1.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>符号链接（symbolic link）是 Linux 系统中的一种文件，它指向系统中的另一个文件或目录。符号链接类似于 Windows 系统中的快捷方式。</p>
<p>也有人称它“软链接（soft links）”——Linux/UNIX 系统中的一种链接形式——与之对应的是“硬链接（hard links）”。</p>
<h2 id="">软链接和硬链接的区别</h2>
<p>软链接类似于快捷方式，它可以指向任意文件系统中的一个文件或目录。</p>
<p>硬链接也可以看作是文件或目录的快捷方式，但是无法在两个不同文件系统之间创建硬链接。</p>
<p>我们将会学习如何创建及删除符号链接，还会了解什么是失效链接，以及如何删除它们。</p>
<h2 id="">如何创建符号链接</h2>
<p>创建符号链接的语法：</p>
<pre><code class="language-shell">ln -s &lt;path to the file/folder to be linked&gt; &lt;the path of the link to be created&gt;
</code></pre>
<p><code>ln</code> 是链接命令，<code>-s</code> 指定此链接为软链接，<code>-s</code> 也可以写为 <code>-symbolic</code>。</p>
<p><code>ln</code> 命令默认会创建硬链接。<code>path to the file (or folder)</code> 声明了链接目标，即想要为其创建快捷方式的文件或目录。</p>
<p><code>path to link</code> 即链接（快捷方式）名称。</p>
<h2 id="">如何为一个文件创建符号链接——命令示例</h2>
<pre><code class="language-shell">ln -s /home/james/transactions.txt trans.txt
</code></pre>
<p>执行这个命令之后，就可以通过 <code>trans.txt</code> 来访问 <code>/home/james/transactions.txt</code>。对于 <code>trans.txt</code> 的修改会体现到源文件上。</p>
<p>注意，以上命令会在当前目录创建链接文件 <code>trans.txt</code>。你也可以使用以下命令在其它目录中创建链接文件：</p>
<pre><code class="language-shell">ln -s /home/james/transactions.txt my-stuffs/trans.txt
</code></pre>
<p>以上命令要求当前目录必须存在一个名为 "my-stuffs" 的目录——否则会抛出错误。</p>
<h2 id="">如何为目录创建符号链接——命令示例</h2>
<p>与上面的命令类似：</p>
<pre><code class="language-shell">ln -s /home/james james
</code></pre>
<p>这会创建一个名为 "james" 的符号链接文件夹，其中包含了 <code>/home/james</code> 目录中的内容。对于链接文件夹的操作也会体现到原始文件夹。</p>
<h2 id="">如何删除符号链接</h2>
<p>在删除符号链接之前，需要确认这个文件或文件夹确实是符号链接，以免误删源文件。</p>
<p>可以这样做：</p>
<pre><code class="language-shell">ls -l &lt;path-to-assumed-symlink&gt;
</code></pre>
<p>在终端运行以上命令会打印出这个文件的属性信息。如果第一个字符是小写的 "L"（<code>l</code>）的话，即表明这个文件（或文件夹）是一个符号链接。</p>
<p>你还可以看到末尾有一个箭头（-&gt;），指向这个符号链接的目标文件（或文件夹）。</p>
<p>有两种方式可以删除符号链接：</p>
<h3 id="unlink">使用 unlink 删除符号链接</h3>
<p>语法如下：</p>
<pre><code class="language-shell">unlink &lt;path-to-symlink&gt;
</code></pre>
<p>如果命令成功执行的话，将会删除指定的符号链接。</p>
<p>即使符号链接是文件夹形式的，也不要在前面加 "/"，如果加了 "/"，Linux 会把它当成是一个目录，然而 <code>unlink</code> 是无法删除目录的。</p>
<h3 id="rm">使用 rm 删除符号链接</h3>
<p>正如我们所见，符号链接只是一种指向源文件（或目录）的文件（或目录）。只要删除链接文件就可以解除这种关系。</p>
<p>语法如下：</p>
<pre><code class="language-shell">rm &lt;path-to-symlink&gt;
</code></pre>
<p>例如：</p>
<pre><code class="language-shell">rm trans.txt
rm james
</code></pre>
<p>注意，试图执行 <code>rm james/</code> 会引发错误，因为 Linux 会把 <code>james/</code> 当成目录来处理，要想删除目录还需要提供 <code>r</code> 和 <code>f</code> 等参数，然而这并不是我们想要的。虽然符号链接有可能是文件夹形式的，但我们只需要关心它的名字。</p>
<p>比起 <code>unlink</code>，<code>rm</code> 的主要优势在于可以一次性删除多个符号链接，就像删除多个文件那样。</p>
<h2 id="">如何找出失效链接并将其删除</h2>
<p>当源文件（或目录）被移动或者被删除时，指向它的符号链接就会失效。</p>
<p>如果把 "transactions.txt" 从 <code>/home/james</code> 移动到 <code>/home/james/personal</code>，"trans.txt" 这个链接就会失效。之后尝试访问 "trans.txt" 会引发错误："No such file or directory"。</p>
<p>如果发现失效的链接，可以很轻松地将其删除。以下方法可以很方便地找出失效链接：</p>
<pre><code class="language-shell">find /home/james -xtype l
</code></pre>
<p>这个命令会列出 <code>james</code> 目录下各种类型（如：文件、目录及子目录）的所有失效链接。</p>
<p>传入 <code>-delete</code> 参数就可以将它们删除：</p>
<pre><code class="language-shell">find /home/james -xtype l -delete
</code></pre>
<h2 id="">总结</h2>
<p>符号链接是 Linux/UNIX 系统的有趣特性。</p>
<p>可以为不方便访问的文件或文件夹创建符号链接，以便于访问。多加练习，你就能对它的工作方式有一个直观的理解。符号链接能够帮助你更高效地管理文件系统。</p>
<!--kg-card-end: markdown--><p>原文：<a href="https://www.freecodecamp.org/news/symlink-tutorial-in-linux-how-to-create-and-remove-a-symbolic-link/">Symlink Tutorial in Linux – How to Create and Remove a Symbolic Link</a>，作者：<a href="https://www.freecodecamp.org/news/author/dillionmegida/">Dillion Megida</a></p> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ JavaScript 数组完全手册 ]]>
                </title>
                <description>
                    <![CDATA[ 在编程世界中，数组是指元素的集合。数组将数据作为元素进行存储，并在需要时将其取出。 在支持数组的编程语言中广泛地采用了这个数据结构。 这个手册会介绍 JavaScript 数组的所有知识。你将会学到复杂数据处理、解构、常用数组方法等内容。 我为什么写这篇文章 网上已经有很多介绍 JavaScript 数组的优秀文章，那我为什么还要写一篇相同主题的文章呢？动机是什么？ 多年来，通过和学员的交流，我意识到大多数初学者都需要这样一个教程：通过示例从头到尾彻底地介绍数组。 所以我决定编写这样一篇包含大量示例的文章。如果你是初学者，希望这篇文章对你有所帮助。 不过，这个手册也能帮助有经验的开发者梳理知识。我在写作这篇文章的过程中，也重新学习了相关知识。我们开始吧。 JavaScript 中的数组是什么 在 JavaScript 中，一对方括号（[]）  表示一个数组，其中的所有元素以逗号（,）  分隔。 在 JavaScript 中，数组可以是任意类型元素组成的集合。这意味着，创建一个数组，它的元素类型可以是 String、Boolean、Number、Object，甚至是另一个 ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/the-javascript-array-handbook/</link>
                <guid isPermaLink="false">60eff6d2cffc7f0618708e9f</guid>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Humilitas ]]>
                </dc:creator>
                <pubDate>Thu, 15 Jul 2021 08:56:26 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2021/07/freeCodeCamp-Cover-1.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>在编程世界中，<code>数组</code>是指元素的集合。数组将数据作为元素进行存储，并在需要时将其取出。</p>
<p>在支持数组的编程语言中广泛地采用了这个数据结构。</p>
<p>这个手册会介绍 JavaScript 数组的所有知识。你将会学到复杂数据处理、解构、常用数组方法等内容。</p>
<h1 id="">我为什么写这篇文章</h1>
<p>网上已经有很多介绍 JavaScript 数组的优秀文章，那我为什么还要写一篇相同主题的文章呢？动机是什么？</p>
<p>多年来，通过和学员的交流，我意识到大多数初学者都需要这样一个教程：通过示例从头到尾彻底地介绍数组。</p>
<p>所以我决定编写这样一篇包含大量示例的文章。如果你是初学者，希望这篇文章对你有所帮助。</p>
<p>不过，这个手册也能帮助有经验的开发者梳理知识。我在写作这篇文章的过程中，也重新学习了相关知识。我们开始吧。</p>
<h1 id="javascript">JavaScript 中的数组是什么</h1>
<p>在 JavaScript 中，一对方括号<code>（[]）</code> 表示一个数组，其中的所有元素以逗号<code>（,）</code> 分隔。</p>
<p>在 JavaScript 中，数组可以是任意类型元素组成的集合。这意味着，创建一个数组，它的元素类型可以是 String、Boolean、Number、Object，甚至是另一个数组。</p>
<p>示例中的数组包含 4 个元素，类型分别是：Number、Boolean、String 和 Object。</p>
<pre><code class="language-js">const mixedTypedArray = [100, true, 'freeCodeCamp', {}];
</code></pre>
<p>元素在数组中的位置称为<code>索引（index）</code>，JavaScript 中的数组索引是从 0 开始计数的，每加入一个新元素，其对应的索引加 1。</p>
<p>例如，上面的数组中，<code>100</code> 这个元素的位置是 <code>索引 0</code>，<code>true</code> 的位置是<code>索引 1</code>，<code>'freeCodeCamp'</code> 的位置是<code>索引 2</code>，以此类推。</p>
<p>数组中的元素数量决定了数组长度（length）。比如说，上面的数组长度是 4。</p>
<p>有趣的是，JavaScript 数组的长度是可变的，你可以随时将它指定为一个非负整数值。我们稍后会学习更多相关知识。</p>
<h1 id="">如何创建数组</h1>
<p>在 JavaScript 中有多种方式可以创建数组，最直接的方式是把数组字面量赋值给一个变量。</p>
<pre><code class="language-js">const salad = ['🍅', '🍄', '🥦', '🥒', '🌽', '🥕', '🥑'];
</code></pre>
<p>也可以使用 <code>Array</code> 构造函数来创建数组。</p>
<pre><code class="language-js">const salad = new Array('🍅', '🍄', '🥦', '🥒', '🌽', '🥕', '🥑');
</code></pre>
<blockquote>
<p>注意：<code>new Array(2)</code> 会创建一个长度为 2 的空数组，然而 <code>new Array(1,2)</code> 则会创建一个包含两个元素（1 和 2）的数组。</p>
</blockquote>
<p>另外，<code>Array.of()</code> 和 <code>Array.from()</code> 方法，以及<code>展开</code>运算符（<code>...</code>）也可以创建数组。我们后面会学习它们。</p>
<h2 id="">如何访问数组元素</h2>
<p>可以使用数组索引来获取数组元素，访问数组元素需要用到方括号 <code>[]</code>。</p>
<pre><code class="language-js">const element = array[index];
</code></pre>
<p>根据使用场景，你可能需要一个一个地访问数组元素或者使用循环来遍历。</p>
<p>可以像这样使用索引来访问数组元素：</p>
<pre><code class="language-js">const salad = ['🍅', '🍄', '🥦', '🥒', '🌽', '🥕', '🥑'];
salad[0]; // '🍅'
salad[2]; // '🥦'
salad[5]; // '🥕'
</code></pre>
<p>也可以利用数组长度（length 属性）值，反向遍历访问数组元素。</p>
<pre><code class="language-js">const salad = ['🍅', '🍄', '🥦', '🥒', '🌽', '🥕', '🥑'];
const len = salad.length;
salad[len - 1]; // '🥑'
salad[len - 3]; // '🌽'
</code></pre>
<p>可以使用一般的 <code>for</code> 循环或 <code>forEach</code> 方法来遍历数组，也可以使用其它方式来遍历。</p>
<pre><code class="language-js">const salad = ['🍅', '🍄', '🥦', '🥒', '🌽', '🥕', '🥑'];

for(let i=0; i&lt;salad.length; i++) {
  console.log(`Element at index ${i} is ${salad[i]}`);
}
</code></pre>
<p>结果如下：</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/05/image-30.png" alt="image-30" width="600" height="400" loading="lazy"></p>
<h2 id="">如何向数组中添加元素</h2>
<p>可以使用 <code>push()</code> 方法向数组中插入一个元素，它会将元素追加到数组的末尾。我们往沙拉中加入一些花生：</p>
<pre><code class="language-js">const salad = ['🍅', '🍄', '🥦', '🥒', '🌽', '🥕', '🥑'];
salad.push('🥜');
</code></pre>
<p>现在沙拉数组看起来像这样：</p>
<p>["🍅", "🍄", "🥦", "🥒", "🌽", "🥕", "🥑", "🥜"]</p>
<p>注意，<code>push()</code> 方法会把元素追加到数组末尾，如果想要在数组头部插入一个元素，需要使用 <code>unshift()</code> 方法。</p>
<pre><code class="language-js">const salad = ['🍅', '🍄', '🥦', '🥒', '🌽', '🥕', '🥑'];
salad.unshift('🥜');
</code></pre>
<p>现在沙拉数组看起来像这样：</p>
<p>["🥜", "🍅", "🍄", "🥦", "🥒", "🌽", "🥕", "🥑"]</p>
<h2 id="">如何移除数组元素</h2>
<p>移除单个数组元素的最简单方式是使用 <code>pop()</code> 方法。每次调用 <code>pop()</code> 方法，都会移除数组末尾的那个元素。<code>pop()</code> 方法的返回值是那个被移除的元素，这个方法会改变原始数组。</p>
<pre><code class="language-js">const salad = ['🍅', '🍄', '🥦', '🥒', '🌽', '🥕', '🥑'];
salad.pop(); // 🥑

console.log(salad); // ['🍅', '🍄', '🥦', '🥒', '🌽', '🥕']
</code></pre>
<p>使用 <code>shift()</code> 方法可以移除数组头部的一个元素。与 <code>pop()</code> 方法类似，<code>shift()</code> 方法会返回那个被移除的元素，并且会改变原始数组。</p>
<pre><code class="language-js">const salad = ['🍅', '🍄', '🥦', '🥒', '🌽', '🥕', '🥑'];
salad.shift(); // 🍅

console.log(salad); // ['🍄', '🥦', '🥒', '🌽', '🥕', '🥑'];
</code></pre>
<h2 id="">如何克隆数组</h2>
<p>可以使用 <code>slice()</code> 方法来克隆数组。注意，<code>slice()</code> 方法不改变原始数组，而是创建一个原始数组的浅拷贝副本。</p>
<pre><code class="language-js">const salad = ['🍅', '🍄', '🥦', '🥒', '🌽', '🥕', '🥑'];
const saladCopy = salad.slice();

console.log(saladCopy); // ['🍅', '🍄', '🥦', '🥒', '🌽', '🥕', '🥑']

salad === saladCopy; // returns false
</code></pre>
<p>也可以使用<code>展开</code>运算符来创建数组副本，我们很快会学到。</p>
<h2 id="">如何判断某个值是不是数组</h2>
<p>可以使用 <code>Array.isArray(value)</code> 方法来判断某个值是不是数组，如果传入的值是一个数组的话，它会返回 true。</p>
<pre><code class="language-js">Array.isArray(['🍅', '🍄', '🥦', '🥒', '🌽', '🥕', '🥑']); // returns true
Array.isArray('🍅'); // returns false
Array.isArray({ 'tomato': '🍅'}); // returns false
Array.isArray([]); // returns true
</code></pre>
<h1 id="">数组解构</h1>
<p>ECMAScript 6（ES6）提供了一些新语法，可以一次性从数组中获取多个元素并赋值给多个变量。它有助于保持代码简洁明了。这个新语法被称为解构语法。</p>
<p>下面是使用解构语法从数组中获取多个元素的例子：</p>
<pre><code class="language-js">let [tomato, mushroom, carrot] = ['🍅', '🍄', '🥕'];
</code></pre>
<p>现在就可以使用这些变量了：</p>
<pre><code class="language-js">console.log(tomato, mushroom, carrot); // Output, 🍅 🍄 🥕
</code></pre>
<p>如果不使用解构语法的话，代码会是这样：</p>
<pre><code class="language-js">let vegetables = ['🍅', '🍄', '🥕'];
let tomato = vegetables[0];
let mushroom= vegetables[1];
let carrot= vegetables[2];
</code></pre>
<p>所以，解构语法能够有助于减少代码量、极大地提高生产力。</p>
<h2 id="">如何为变量指定默认值</h2>
<p>使用解构语法时，可以为变量指定默认值，当数组中没有对应的元素或者元素的值为 <code>undefined</code> 时，就会使用默认值。</p>
<p>下面的例子中，我们为 mushroom 变量指定了一个默认值。</p>
<pre><code class="language-js">let [tomato , mushroom = '🍄'] = ['🍅'];
console.log(tomato); // '🍅'
console.log(mushroom ); // '🍄'
</code></pre>
<h2 id="">如何跳过某个数组元素</h2>
<p>使用解构获取数组元素时，可以跳过某个元素。比如说，你可能只关注数组的部分元素，这时候这个语法就派上用场了。</p>
<p>下面的例子中，我们跳过了“蘑菇”元素。注意表达式左边变量声明中的空格。</p>
<pre><code class="language-js">let [tomato, , carrot] = ['🍅', '🍄', '🥕'];

console.log(tomato); // '🍅'
console.log(carrot); // '🥕'
</code></pre>
<h2 id="">嵌套数组解构</h2>
<p>JavaScript 中，数组是可以嵌套的。这意味着一个数组的元素可以是另一个数组。数组可以嵌套任意深度。</p>
<p>例如，我们创建一个嵌套数组 <code>fruits</code>，其元素包含一些水果和一个“蔬菜”数组。</p>
<pre><code class="language-js">let fruits = ['🍈', '🍍', '🍌', '🍉', ['🍅', '🍄', '🥕']];
</code></pre>
<p>要如何获取以上数组中的 '🥕' 呢？同样的，不使用解构的话，可以这样做：</p>
<pre><code class="language-js">const veg = fruits[4]; // returns the array ['🍅', '🍄', '🥕']
const carrot = veg[2]; // returns '🥕'
</code></pre>
<p>或者，也可以使用简写语法：</p>
<pre><code class="language-js">fruits[4][2]; // returns '🥕'
</code></pre>
<p>还可以使用解构语法：</p>
<pre><code class="language-js">let [,,,,[,,carrot]] = ['🍈', '🍍', '🍌', '🍉', ['🍅', '🍄', '🥕']];
</code></pre>
<h1 id="spreadsyntaxrestparameter">如何使用展开语法（Spread Syntax）和剩余参数（Rest Parameter）</h1>
<p>从 ES6 开始，通过 <code>...</code>（连续的三个点）可以在数组解构中使用展开语法和剩余参数。</p>
<ul>
<li>使用剩余参数时，<code>...</code> 出现在解构语法表达式的左边。</li>
<li>使用展开语法时，<code>...</code> 出现在解构语法表达式的右边。</li>
</ul>
<h2 id="">如何使用剩余参数</h2>
<p>通过<code>剩余参数</code>，可以将剩下的元素映射到一个新的数组中。剩余参数必须是解构语法中的最后一个变量。</p>
<p>下面的例子中，我们把数组的前两个参数分别映射到了 tomato 和 mushroom 变量中，剩下的元素则使用 <code>...</code> 映射到了 <code>rest</code> 变量中。<code>rest</code> 是一个新数组，其中包含了剩下的元素。</p>
<pre><code class="language-js">const [tomato, mushroom, ...rest] = ['🍅', '🍄', '🥦', '🥒', '🌽', '🥕', '🥑'];

console.log(tomato); // '🍅'
console.log(mushroom); // '🍄'
console.log(rest); // ["🥦", "🥒", "🌽", "🥕", "🥑"]
</code></pre>
<h2 id="">如何使用展开运算符</h2>
<p>使用展开运算符，可以这样来克隆现有的数组：</p>
<pre><code class="language-js">const salad = ['🍅', '🍄', '🥦', '🥒', '🌽', '🥕', '🥑'];

const saladCloned = [...salad];
console.log(saladCloned); // ["🍅", "🍄", "🥦", "🥒", "🌽", "🥕", "🥑"]

salad === saladCloned // false
</code></pre>
<h1 id="">解构的使用场景</h1>
<p>我们一起来看看数组解构、展开运算符和剩余参数的一些激动人心的使用场景。</p>
<h2 id="">使用解构交换变量值</h2>
<p>使用数组解构语法可以很轻松地交换两个变量的值。</p>
<pre><code class="language-js">let first = '😔';
let second = '🙂';
[first, second] = [second, first];

console.log(first);  // '🙂'
console.log(second); // '😔'
</code></pre>
<h2 id="">合并数组</h2>
<p>我们可以通过合并两个数组的所有元素来创建一个新数组（不改变原始数组）。假设现在有两个数组——一个包含一些笑脸，另一个包含一些蔬菜。</p>
<pre><code class="language-js">const emotion = ['🙂', '😔'];
const veggies = ['🥦', '🥒', '🌽', '🥕'];
</code></pre>
<p>现在，我们要把它们合并成一个新数组。</p>
<pre><code class="language-js">const emotionalVeggies = [...emotion, ...veggies];
console.log(emotionalVeggies); // ["🙂", "😔", "🥦", "🥒", "🌽", "🥕"]
</code></pre>
<h1 id="javascript">JavaScript 数组方法</h1>
<p>到目前为止，我们已经了解了一些数组属性和方法。我们做一个简单的回顾：</p>
<ul>
<li><code>push()</code> – 在数组末尾插入一个元素。</li>
<li><code>unshift()</code> – 在数组头部插入一个元素。</li>
<li><code>pop()</code> – 移除数组末尾的最后一个元素。</li>
<li><code>shift()</code> – 移除数组头部的第一个元素。</li>
<li><code>slice()</code> – 创建数组的浅拷贝副本。</li>
<li><code>Array.isArray()</code> – 判断某个值是不是数组。</li>
<li><code>length</code> – 数组的长度。</li>
</ul>
<p>现在我们将通过示例来学习其它重要的数组方法。</p>
<h2 id="">如何创建数组、删除数组元素、更新数组元素以及访问数组元素</h2>
<p>这一节，我们要学习用于创建新数组、移除数组元素及清空数组、访问数组元素等操作的方法。</p>
<h3 id="concat"><code>concat()</code> 方法</h3>
<p><code>concat()</code> 方法可以将多个数组合并在一起并返回合并后的数组。这是一个不可变方法，意味着它不会改变现有的数组。</p>
<p>拼接两个数组：</p>
<pre><code class="language-js">const first = [1, 2, 3];
const second = [4, 5, 6];

const merged = first.concat(second);

console.log(merged); // [1, 2, 3, 4, 5, 6]
console.log(first); // [1, 2, 3]
console.log(second); // [4, 5, 6]
</code></pre>
<p>使用 <code>concat()</code> 方法也可以拼接两个以上的数组。我们可以这样拼接任意数量的数组：</p>
<pre><code class="language-js">array.concat(arr1, arr2,..,..,..,arrN);
</code></pre>
<p>示例如下：</p>
<pre><code class="language-js">const first = [1, 2, 3];
const second = [4, 5, 6];
const third = [7, 8, 9];

const merged = first.concat(second, third);

console.log(merged); // [1, 2, 3, 4, 5, 6, 7, 8, 9]
</code></pre>
<h3 id="join"><code>join()</code> 方法</h3>
<p><code>join()</code> 方法使用一个分隔符将数组的所有元素拼接成一个字符串，并返回这个字符串。默认的分隔符是逗号（<code>,</code>）。</p>
<pre><code class="language-js">const emotions = ['🙂', '😍', '🙄', '😟'];

const joined = emotions.join();
console.log(joined); // "🙂,😍,🙄,😟"
</code></pre>
<p>可以传入一个自定义分隔符用于拼接数组元素。下面是一个使用自定义分隔符拼接数组元素的例子：</p>
<pre><code class="language-js">const joined = emotions.join('&lt;=&gt;');
console.log(joined); // "🙂&lt;=&gt;😍&lt;=&gt;🙄&lt;=&gt;😟"
</code></pre>
<p>在空数组上调用 <code>join()</code> 方法，返回一个空字符串：</p>
<pre><code class="language-js">[].join() // returns ""
</code></pre>
<h3 id="fill"><code>fill()</code> 方法</h3>
<p><code>fill()</code> 方法使用一个固定值填充数组。可以使用这个固定值填充整个数组，也可以只覆盖选定的元素。注意，<code>fill()</code> 方法会改变原始数组。</p>
<pre><code class="language-js">const colors = ['red', 'blue', 'green'];

colors.fill('pink');
console.log(colors); // ["pink", "pink", "pink"]
</code></pre>
<p>下面是一个使用 <code>fill()</code> 方法覆盖数组的最后两个元素的例子：</p>
<pre><code class="language-js">const colors = ['red', 'blue', 'green'];

colors.fill('pink', 1,3); // ["red", "pink", "pink"]
</code></pre>
<p>这个例子中，<code>fill()</code> 方法的第一个参数是用来填充数组的值，第二个参数是替换的起始索引（从 <code>0</code> 开始计算），最后一个参数是终止索引（最大值可以是 <code>colors.length</code>）。</p>
<p>请查看这个 <a href="https://twitter.com/tapasadhikary/status/1360185996768251904?ref_src=twsrc%5Etfw%7Ctwcamp%5Etweetembed%7Ctwterm%5E1360185996768251904%7Ctwgr%5E%7Ctwcon%5Es1_&amp;ref_url=https%3A%2F%2Fwww.freecodecamp.org%2Fnews%2Fthe-javascript-array-handbook%2F">Twitter 主题</a>以了解 <code>fill()</code> 方法的实际用法。</p>
<p>你也可以查看这个示例项目：<a href="https://github.com/atapas/array-fill-color-cards">https://github.com/atapas/array-fill-color-cards</a>。</p>
<h3 id="includes"><code>includes()</code> 方法</h3>
<p>可以使用 <code>includes()</code> 方法来判断一个数组中是否包含某个元素，如果包含则返回 <code>true</code>，否则返回 <code>false</code>。</p>
<pre><code class="language-js">const names = ['tom', 'alex', 'bob', 'john'];

names.includes('tom'); // returns true
names.includes('july'); // returns false
</code></pre>
<h3 id="indexof"><code>indexOf()</code> 方法</h3>
<p>可以使用 <code>indexOf()</code> 方法找到某个元素在数组中的索引位置。它返回这个元素在数组中首次出现的索引，如果没有找到这个元素则返回 <code>-1</code>。</p>
<pre><code class="language-js">const names = ['tom', 'alex', 'bob', 'john'];

names.indexOf('alex'); // returns 1
names.indexOf('rob'); // returns -1
</code></pre>
<p>还有一个 <code>lastIndexOf()</code> 方法，可以找出某个元素在数组中最后出现的位置。与 <code>indexOf()</code> 类似，<code>lastIndexOf()</code> 在找不到这个元素时也返回 <code>-1</code>。</p>
<pre><code class="language-js">const names = ['tom', 'alex', 'bob', 'tom'];

names.indexOf('tom'); // returns 0
names.lastIndexOf('tom'); // returns 3
</code></pre>
<h3 id="reverse"><code>reverse()</code> 方法</h3>
<p>顾名思义，<code>reverse()</code> 方法将数组中元素的位置颠倒，最后一个元素变成第一个、第一个元素变成最后一个。</p>
<pre><code class="language-js">const names = ['tom', 'alex', 'bob'];

names.reverse(); // returns ["bob", "alex", "tom"]
</code></pre>
<p><code>reverse()</code> 方法会改变原始数组。</p>
<h3 id="sort"><code>sort()</code> 方法</h3>
<p><code>sort()</code> 方法可能是最常用的数组方法之一。<code>sort()</code> 方法默认会把元素转换为字符串再对它们进行排序。默认的排序方式是升序排列。<code>sort()</code> 方法会改变原始数组。</p>
<pre><code class="language-js">const names = ['tom', 'alex', 'bob'];

names.sort(); // returns ["alex", "bob", "tom"]
</code></pre>
<p><code>sort()</code> 方法接收一个可选的比较器函数作为参数，可以编写一个比较器函数传入 <code>sort()</code> 方法来覆盖默认的排序行为。</p>
<p>假设现在有一个数字数组，我们使用比较器函数将它按升序和降序排序：</p>
<pre><code class="language-js">const numbers = [23, 5, 100, 56, 9, 13, 37, 10, 1]
</code></pre>
<p>首先，调用 <code>sort()</code> 方法，并观察结果：</p>
<pre><code class="language-js">numbers.sort();
</code></pre>
<p>现在，排序后的数组为 [1, 10, 100, 13, 23, 37, 5, 56, 9]。这并不是我们预期的结果。得到这个结果是因为 <code>sort()</code> 方法默认会将元素转换为字符串，再基于字符串诸个字符对应的 <code>UTF-16</code> 编码值进行比较。</p>
<p>为了解决这个问题，我们编写一个比较器函数。这是用于升序排序的：</p>
<pre><code class="language-js">function ascendingComp(a, b){
  return (a-b);
}
</code></pre>
<p>把比较器函数传入 <code>sort()</code> 方法：</p>
<pre><code class="language-js">numbers.sort(ascendingComp); // retruns [1, 5, 9, 10, 13, 23, 37, 56, 100]

/* 

也可以使用行内函数：

numbers.sort(function(a, b) {
  return (a-b);
});

或者使用箭头函数的写法：

numbers.sort((a, b) =&gt; (a-b));

*/
</code></pre>
<p>降序排序：</p>
<pre><code class="language-js">numbers.sort((a, b) =&gt; (b-a));
</code></pre>
<p>查看这个 GitHub 仓库以获取更多排序示例和技巧：<a href="https://github.com/atapas/js-array-sorting">https://github.com/atapas/js-array-sorting</a>。</p>
<h3 id="splice"><code>splice()</code> 方法</h3>
<p><code>splice()</code> 方法可以帮助你向数组中添加元素、更新数组元素以及移除数组元素。刚开始接触这个方法可能会令人困惑，不过只要你理解了它的正确用法，就能够掌握。</p>
<p><code>splice()</code> 方法的主要目标是从数组中移除元素。它会返回由被移除的元素组成的数组，并且会改变原始数组。你也可以用它来向数组中添加元素或者替换数组中的元素。</p>
<p>使用 <code>splice()</code> 方法向数组中添加一个元素，需要传入插入的目标位置、从目标位置算起想要删除的元素数量以及要插入的元素。</p>
<p>下面的例子中，我们在索引为 <code>1</code> 的位置上插入了一个元素 <code>zack</code>，没有删除任何元素。</p>
<pre><code class="language-js">const names = ['tom', 'alex', 'bob'];

names.splice(1, 0, 'zack');

console.log(names); // ["tom", "zack", "alex", "bob"]
</code></pre>
<p>看看下面的例子，我们移除了索引 <code>2</code> 位置之后的一个元素（即第三个元素），并添加了一个元素 <code>zack</code>。<code>splice()</code> 方法返回一个由移除掉的元素——<code>bob</code>——组成的数组。</p>
<pre><code class="language-js">const names = ['tom', 'alex', 'bob'];

const deleted = names.splice(2, 1, 'zack');

console.log(deleted); // ["bob"]
console.log(names); // ["tom", "alex", "zack"]
</code></pre>
<p>查看这个 <a href="https://twitter.com/tapasadhikary/status/1313112900085579776?ref_src=twsrc%5Etfw%7Ctwcamp%5Etweetembed%7Ctwterm%5E1313112900085579776%7Ctwgr%5E%7Ctwcon%5Es1_&amp;ref_url=https%3A%2F%2Fwww.freecodecamp.org%2Fnews%2Fthe-javascript-array-handbook%2F">Twitter 主题</a>以了解如何使用 <code>splice()</code> 方法清空数组。</p>
<h2 id="">静态数组方法</h2>
<p>在 JavaScript 中，数组有三个静态方法。我们已经讨论过 <code>Array.isArray()</code>，接下来要探讨其余两个方法。</p>
<h3 id="arrayfrom"><code>Array.from()</code> 方法</h3>
<p>假设有以下 HTML 代码片段，其中包含一个 div 和一些列表元素：</p>
<pre><code class="language-html">&lt;div id="main"&gt;
  &lt;ul&gt;
    &lt;ol type="1"&gt;
      &lt;li&gt;...&lt;/li&gt;
      &lt;li&gt;...&lt;/li&gt;
      &lt;li&gt;...&lt;/li&gt;
      &lt;li&gt;...&lt;/li&gt;
      &lt;li&gt;...&lt;/li&gt;
      &lt;li&gt;...&lt;/li&gt;
      &lt;li&gt;...&lt;/li&gt;
      &lt;li&gt;...&lt;/li&gt;
      &lt;li&gt;...&lt;/li&gt;
      &lt;li&gt;...&lt;/li&gt;
    &lt;/ol&gt;
  &lt;/ul&gt; 
&lt;/div&gt;
</code></pre>
<p>我们使用 <code>getElementsByTagName()</code> 方法获取这些列表元素。</p>
<pre><code class="language-js">document.getElementsByTagName('li');
</code></pre>
<p>它返回如下 <code>HTMLCollection</code> 对象：</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/05/htmlCollec.png" alt="htmlCollec" width="600" height="400" loading="lazy"></p>
<div style="text-align: center; margin-bottom: 1.5em;">HTMLCollection 是类数组对象</div>
<p>它和数组类似，我们试着使用 <code>forEach</code> 来遍历它：</p>
<pre><code class="language-js">document.getElementsByTagName('li').forEach(() =&gt; {
 // Do something here..
})
</code></pre>
<p>猜猜会输出什么？会报出以下错误：</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/05/htmlcolc_error.png" alt="htmlcolc_error" width="600" height="400" loading="lazy"></p>
<div style="text-align: center; margin-bottom: 1.5em;">在类数组对象上调用 forEach 发生错误</div>
<p>为什么会这样？这是因为 <code>HTMLCollection</code> 并不是数组，而是 <code>类数组</code> 对象，所以不能使用 <code>forEach</code> 来遍历它。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/05/htmlCollec_object.png" alt="htmlCollec_object" width="600" height="400" loading="lazy"></p>
<div style="text-align:center; margin-bottom: 1.5em;">其原型（proto）是 Object</div>
<p>这里就需要用到 <code>Array.from()</code> 方法了，<code>Array.from()</code> 能将类数组对象转换为数组，进而能够在它上面执行所有数组操作。</p>
<pre><code class="language-js">const collection = Array.from(document.getElementsByTagName('li'))
</code></pre>
<p>这里的 <code>collection</code> 是一个数组：</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/05/collection.png" alt="collection" width="600" height="400" loading="lazy"></p>
<div style="text-align:center; margin-bottom: 1.5em;">其原型为 Array</div>
<h3 id="arrayof"><code>Array.of()</code> 方法</h3>
<p><code>Array.of()</code> 可以使用任意数量任意类型的元素创建一个新数组。</p>
<pre><code class="language-js">Array.of(2, false, 'test', {'name': 'Alex'})
</code></pre>
<p>输出如下：</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/05/image-49.png" alt="image-49" width="600" height="400" loading="lazy"></p>
<div style="text-align:center; margin-bottom: 1.5em;">Array.of() 方法的输出结果</div>
<h2 id="">数组迭代器方法</h2>
<p>现在我们要学习数组迭代器方法。这些方法在执行数组迭代、计算、做判断、过滤元素等操作时很有用。</p>
<p>到目前为止，我们还没见过对象数组的示例。在这一节，我们将会使用下面的对象数组来解释和演示这些迭代器方法。</p>
<p>这个数组包含了一些订阅各种付费课程的学生的信息：</p>
<pre><code class="language-js">let students = [
   {
      'id': 001,
      'f_name': 'Alex',
      'l_name': 'B',
      'gender': 'M',
      'married': false,
      'age': 22,
      'paid': 250,  
      'courses': ['JavaScript', 'React']
   },
   {
      'id': 002,
      'f_name': 'Ibrahim',
      'l_name': 'M',
      'gender': 'M',
      'married': true,
      'age': 32,
      'paid': 150,  
      'courses': ['JavaScript', 'PWA']
   },
   {
      'id': 003,
      'f_name': 'Rubi',
      'l_name': 'S',
      'gender': 'F',
      'married': false,
      'age': 27,
      'paid': 350,  
      'courses': ['Blogging', 'React', 'UX']
   },
   {
      'id': 004,
      'f_name': 'Zack',
      'l_name': 'F',
      'gender': 'M',
      'married': true,
      'age': 36,
      'paid': 250,  
      'courses': ['Git', 'React', 'Branding']
   } 
];
</code></pre>
<p>让我们开始吧。所有数组迭代器方法都接收一个函数作为参数，需要在这个函数中声明迭代逻辑。</p>
<h3 id="filter"><code>filter()</code> 方法</h3>
<p><code>filter()</code> 方法用所有满足过滤条件的元素来创建一个新数组。我们要找出女学生，所以过滤条件应该是 <code>gender === 'F'</code>。</p>
<pre><code class="language-js">const femaleStudents = students.filter((element, index) =&gt; {
  return element.gender === 'F';
})

console.log(femaleStudents);
</code></pre>
<p>输出如下：</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/05/image-50.png" alt="image-50" width="600" height="400" loading="lazy"></p>
<p>结果是正确的，名为 <code>Rubi</code> 的学生是目前唯一的女学生。</p>
<h3 id="map"><code>map()</code>方法</h3>
<p><code>map()</code> 方法遍历整个数组，依次对数组元素执行回调函数并用这些返回值创建一个新数组。我们将会创建一个由 <code>students</code> 数组中所有学生的全名组成的新数组。</p>
<pre><code class="language-js">const fullNames = students.map((element, index) =&gt; {
  return {'fullName': element['f_name'] + ' ' + element['l_name']}
});

console.log(fullNames);
</code></pre>
<p>输出如下：</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/05/image-51.png" alt="image-51" width="600" height="400" loading="lazy"></p>
<p>这里我们可以看到由包含 <code>fullName</code> 属性的对象组成的数组，<code>fullName</code> 是由 student 对象的 <code>f_name</code> 和 <code>l_name</code> 属性计算得到的。</p>
<h3 id="reduce"><code>reduce()</code> 方法</h3>
<p><code>reduce()</code> 方法对每个数组元素执行 reducer 函数，并将其结果汇总为单个返回值。我们将会在 <code>students</code> 数组中应用一个 reducer 函数来计算所有学生支付的总额。</p>
<pre><code class="language-js">const total = students.reduce(
   (accumulator, student, currentIndex, array) =&gt; {
      accumulator = accumulator + student.paid;
      return (accumulator);
   }, 
0);

console.log(total); // 1000
</code></pre>
<p>在上面的代码中，</p>
<ul>
<li>我们将<code>累加器（accumulator）</code>初始化为 <code>0</code>。</li>
<li>我们对每个 student 对象执行 <code>reduce</code> 方法，读取 <code>paid</code> 属性值并把它累加在累加器上。</li>
<li>最后，返回累加器。</li>
</ul>
<h3 id="some"><code>some()</code> 方法</h3>
<p><code>some()</code> 方法返回一个布尔值（true/false），其返回值取决于数组中是否至少有一个元素符合回调函数中的判断条件。我们来看看是否有学生的年龄小于 30 岁。</p>
<pre><code class="language-js">let hasStudentBelow30 = students.some((element, index) =&gt; {
  return element.age &lt; 30;
});

console.log(hasStudentBelow30); // true
</code></pre>
<p>是的，我们看到至少有一个学生的年龄是小于 30 岁的。</p>
<h3 id="find"><code>find()</code> 方法</h3>
<p>使用 <code>some()</code> 方法，我们已经看到有一个 30 岁以下的学生。让我们找出这个学生。</p>
<p>为此，我们会用到 <code>find()</code> 方法，它会返回数组中第一个满足判断条件的元素。</p>
<p>还有另一个相关的方法 <code>findIndex()</code>，这个方法返回我们使用 <code>find()</code> 方法找到的元素的索引，如果没有符合条件的元素则返回 <code>-1</code>。</p>
<p>下面的例子中，我们向 <code>find()</code> 方法中传入了一个函数用来判断学生的年龄，它会返回满足判断条件的学生。</p>
<pre><code class="language-js">const student = students.find((element, index) =&gt; {
  return element.age &lt; 30;
});

console.log(student);
</code></pre>
<p>输出如下：</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/05/image-52.png" alt="image-52" width="600" height="400" loading="lazy"></p>
<p>可以看到，他就是 22 岁的 Alex，我们找到他了。</p>
<h3 id="every"><code>every()</code> 方法</h3>
<p><code>every()</code> 方法检查是否数组的每个元素都满足给定的判断条件。让我们检查一下是不是所有学生都订阅了至少两门课程。</p>
<pre><code class="language-js">const atLeastTwoCourses = students.every((elements, index) =&gt; {
  return elements.courses.length &gt;= 2;
});

console.log(atLeastTwoCourses); // true
</code></pre>
<p>正如预期，我们看到结果为 <code>true</code>。</p>
<h2 id="">提案中的方法</h2>
<p>截至 2021 年 5 月，ECMAScript 提案中有一个<a href="https://tc39.es/proposal-relative-indexing-method/#sec-array-prototype-additions">新的数组方法</a>，即 <code>at()</code> 方法。</p>
<h3 id="at"><code>at()</code> 方法</h3>
<p>提案中的 <code>at()</code> 方法可以让你使用负数索引来访问数组元素（译注：使用负数索引即从数组末尾开始访问元素，<code>-1</code> 表示最后一个元素、<code>-2</code> 表示倒数第二个元素……以此类推）。截至目前，这个方法还不可用。现在只能使用正数索引从数组开头访问元素。</p>
<p>目前想从数组末尾开始访问数组元素要借助 length 属性。通过引入 <code>at()</code> 方法，就可以在单个方法里面使用正数索引或者负数索引来访问元素。</p>
<pre><code class="language-js">const junkFoodILove = ['🥖', '🍔', '🍟', '🍕', '🌭', '🥪', '🌮', '🍿'];

junkFoodILove.at(0); // 🥖
junkFoodILove.at(3); // 🍕
junkFoodILove.at(-1); // 🍿
junkFoodILove.at(-5); // 🍕
junkFoodILove.at(-8); // 🥖
junkFoodILove.at(10); // undefined
</code></pre>
<p>这是一个简单示例：</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/05/demo.gif" alt="demo" width="600" height="400" loading="lazy"></p>
<div style="text-align:center; margin-bottom: 1.5em;">JavaScript at() 方法示例</div>
<p>在 <code>at()</code> 方法加入 JavaScript 语言之前，你可以使用这个 <a href="https://github.com/es-shims/Array.prototype.at">polyfill</a> 来获得它的功能。查看这个 GitHub 仓库以获取 <code>at()</code> 方法的示例：<a href="https://github.com/atapas/js-array-at-method">https://github.com/atapas/js-array-at-method</a>。</p>
<h1 id="">结束之前......</h1>
<p>希望你觉得这篇文章有价值，也希望它能够帮助你更好地理解 JavaScript 数组。请多多练习文中的示例，以便更好地掌握它们。你可以在<a href="https://github.com/atapas/js-handbook-examples#%EF%B8%8F-list-of-content">我的 GitHub 仓库</a>中找到所有代码示例。</p>
<p>保持联系，我平时活跃在 <a href="https://twitter.com/tapasadhikary">Twitter (@tapasadhikary)</a>，欢迎关注我。</p>
<p>推荐阅读：</p>
<ul>
<li><a href="https://blog.greenroots.info/why-do-you-need-to-know-about-array-like-objects-ckgsynazh07er06s18ppn32n0">为什么需要了解类数组对象？</a></li>
<li><a href="https://blog.greenroots.info/5-useful-tips-about-the-javascript-array-sort-method-ckfs2cifq00eju9s17dfy3jq8">关于 JavaScript 排序方法（sort）的 5 个实用技巧</a></li>
<li><a href="https://blog.greenroots.info/ways-to-empty-an-array-in-javascript-and-the-consequences-cjwt45q9d002h2fs1kz5a77a2">JavaScript 中清空数组的各种方式及其后果</a></li>
<li><a href="https://blog.greenroots.info/build-your-javascript-muscles-with-map-reduce-filter-and-other-array-iterators-cjyo22miw000xzss1ydfqveib">使用 map、reduce、filter 和其它数组迭代器增强你的 JavaScript 水平</a></li>
<li><a href="https://blog.greenroots.info/why-do-you-need-to-know-about-the-javascript-array-at-method-ckoskkkee0ftmbws1ag0b4udt">为什么需要了解 JavaScript 数组的 at() 方法？</a></li>
</ul>
<!--kg-card-end: markdown--><p>原文：<a href="https://www.freecodecamp.org/news/the-javascript-array-handbook/">The JavaScript Array Handbook – JS Array Methods Explained with Examples</a>，作者：<a href="https://www.freecodecamp.org/news/author/tapas/">TAPAS ADHIKARY</a></p> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 通过创建计数器应用程序来学习 Redux ]]>
                </title>
                <description>
                    <![CDATA[ Redux 是前端应用的状态管理库。开发者通常会通过 react-redux 包来使用它，不过它也可以在任意前端框架或库中单独使用，如 Vanilla JavaScript。 刚接触 Redux 可能会被吓到，你可能需要一段时间才能适应它，而且可能最终要翻看以前的代码才能找到解决问题的办法。 常遭人诟病的是，Redux 需要使用很多样板代码来管理应用状态。确实如此——在小型应用中使用 Redux 犹如拿大炮打蚂蚁。在 React 项目中，还有其他可用方案，如属性（prop）钻取和上下文（context）；Vue项目中可以使用 VueX；Angular 项目中可以使用 NGRX。 在 React 应用中，Redux 解决的主要问题是，将应用的所有状态保存在一个全局的 store 中，而不是通过（单向的）属性传递来管理状态。这样就可以在任意需要的地方访问到全局状态了，非常方便！ 本文会教你使用 Redux 实现一个 React 计数器应用，让你有足够的基础知识在项目中使用 Redux。 项目设置 首先，运行 npm i redux react-redux  命令，安装 redux ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/learn-redux-by-making-a-counter-application/</link>
                <guid isPermaLink="false">60d98aec240b4e0653a3e052</guid>
                
                    <category>
                        <![CDATA[ Redux ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Humilitas ]]>
                </dc:creator>
                <pubDate>Mon, 28 Jun 2021 08:00:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2021/06/reduxpic.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Redux 是前端应用的状态管理库。开发者通常会通过 react-redux 包来使用它，不过它也可以在任意前端框架或库中单独使用，如 Vanilla JavaScript。</p>
<p>刚接触 Redux 可能会被吓到，你可能需要一段时间才能适应它，而且可能最终要翻看以前的代码才能找到解决问题的办法。</p>
<p>常遭人诟病的是，Redux 需要使用很多样板代码来管理应用状态。确实如此——在小型应用中使用 Redux 犹如拿大炮打蚂蚁。在 React 项目中，还有其他可用方案，如属性（prop）钻取和上下文（context）；Vue项目中可以使用 VueX；Angular 项目中可以使用 NGRX。</p>
<p>在 React 应用中，Redux 解决的主要问题是，将应用的所有状态保存在一个全局的 store 中，而不是通过（单向的）属性传递来管理状态。这样就可以在任意需要的地方访问到全局状态了，非常方便！</p>
<p>本文会教你使用 Redux 实现一个 React 计数器应用，让你有足够的基础知识在项目中使用 Redux。</p>
<h2 id="">项目设置</h2>
<p>首先，运行 <code>npm i redux react-redux</code> 命令，安装 redux 和 react-redux。Redux 是独立的包，react-redux 则提供了一些方便使用的钩子（hook）。</p>
<h2 id="">创建目录和文件</h2>
<p>接下来，创建 action 和 reducer。顾名思义，action 就是决定要干什么的对象。另一方面，reducer 会检查执行了哪个 action 并根据 action 更新 state，它接收 state 和 action 作为参数。</p>
<p>我喜欢创建一个名为 <code>Redux</code> 的目录，再在其中创建创建 actions 和 reducers 目录，分别保存 action 和 reducer 文件。</p>
<p>在 reducers 目录中创建 <code>counterReducer.js</code> 文件。很多人通常会使用 switch 语句来实现 reducer，不过你也可以使用 if 语句。示例中会使用 switch。</p>
<p>所以，在 <code>counterReducer.js</code> 中写入以下代码：</p>
<pre><code class="language-js">// src/Redux/reducers/counterReducer.js
const counterReducer = (state = 1, action) =&gt; {
  switch (action.type) {
    case "INCREMENT":
      return state + 1;
    case "DECREMENT":
      return state - 1;
    case "RESET":
      return (state = 0);
    default:
      return state;
  }
};
export default counterReducer;
</code></pre>
<p>这里我将 state 初始值硬编码为 1，你也可以选择从 0 开始计数。</p>
<p>在 switch 语句中，<code>INCREMENT</code> 表示让计数器加 1，<code>DECREMENT</code> 表示让计数器减 1，<code>RESET</code> 表示将计数器重置为 0。</p>
<p>按照惯例会将 action 的类型（action.type）全部大写，但不作强制要求——大小写都行。需要把 counterReducer 导出，以便在其他文件中使用它。</p>
<h2 id="">加入虚拟的身份验证</h2>
<p>为计数器应用添加虚拟的身份验证，让应用功能更进一步。我会在那里向你展示一个关于我的小秘密。所以，除了 <code>counterReducer</code>，还要创建一个 <code>authReducer</code>，其代码如下：</p>
<pre><code class="language-javascript">// src/Redux/reducers/authReducer.js
const authReducer = (state = false, action) =&gt; {
  switch (action.type) {
    case "LOG_IN":
      return true;
    case "LOG_OUT":
      return false;
    default:
      return state;
  }
};
export default authReducer;
</code></pre>
<p>在 <code>authReducer</code> 中，state 被初始化为 false，<code>LOG_IN</code> 会将它设置为 true，<code>LOG_OUT</code> 会将它设置为 false。</p>
<h2 id="combinereducer">如何使用 <code>combineReducer</code> 辅助函数</h2>
<p>因为有多个 reducer，所以需要引入 Redux 提供的 <code>combineReducer</code> 辅助函数。这个函数可以将多个 reducer 合并成单个 reducer，以便传入 <code>createStore</code> API。不必为不了解 <code>createStore</code> API 而困惑——我稍后会介绍。</p>
<p><code>combineReducer</code> 使用方式如下：</p>
<p><code>combineReducer({reducer-a, reducer-b, reducer-c})</code></p>
<p>在 reducer 目录中创建 <code>index</code> 文件（译注：文中提及的默认都是 JavaScript 文件，此处指的是 <code>index.js</code>），引入 <code>combineReducer</code> 和 reducer，并把这些 reducer 组合在一起。</p>
<p>其代码如下：</p>
<pre><code class="language-js">// src/Redux/reducers/index.js
import counterReducer from "./counterReducer";
import authReducer from "./authReducer";
import {combineReducers} from "redux";

const allReducers = combineReducers({
    counter: counterReducer,
    auth: authReducer,
});
export default allReducers;
</code></pre>
<h2 id="store">如何创建全局 Store</h2>
<p>接下来要做的是创建一个 store，我喜欢在 React 的 <code>index</code> 文件中创建它。</p>
<p>需要先从 Redux 中引入 <code>createStore</code> API：</p>
<p><code>import { createStore } from "redux";</code></p>
<p>还需要引入已经组合好的 reducer，然后就可以使用它们来创建 store 了。</p>
<p>为了让我们的计数器功能可用，必须把 Redux 连接到应用中。</p>
<p>在 index 文件里，首先从 react-redux 引入 <code>Provider</code>，并用它包裹整个组件树。Provider 将全局的 state 连接到应用中。<code>Provider</code> 接收一个名为 store 的参数，我们在其中传入之前创建的 store。</p>
<p>index 文件的代码如下：</p>
<pre><code class="language-js">// src/index.js
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import { createStore } from "redux";
import allReducers from "./redux/reducers";
import { Provider } from "react-redux";

// 创建 store
const store = createStore(
  allReducers,
);
ReactDOM.render(
  &lt;React.StrictMode&gt;
    &lt;Provider store={store}&gt;
      &lt;App /&gt;
    &lt;/Provider&gt;
  &lt;/React.StrictMode&gt;,
  document.getElementById("root")
);
</code></pre>
<h2 id="action">如何定义 Action</h2>
<p>为了实现增加计数、减少计数、登入、登出，我们必须回到 actions 目录，定义相应的 action，并将它们导出。dispatch 函数可以执行我们定义的 action。</p>
<p>我在 reducer 文件中实现了 <code>INCREMENT</code>、<code>DECREMENT</code>、<code>RESET</code>、<code>LOG_IN</code> 和 <code>LOG_OUT</code> action 的逻辑。actions 目录中的 index 文件里要以同样的标识符（即 <code>type</code>）来使用它们。</p>
<p>actions 目录中，index 文件的代码如下：</p>
<pre><code class="language-js">// src/Redux/actions/index.js
export const increment = () =&gt; {
  return {
    type: "INCREMENT",
  };
};

export const decrement = () =&gt; {
  return {
    type: "DECREMENT",
  };
};

export const reset = () =&gt; {
  return {
    type: "RESET",
  };
};

export const logIn = () =&gt; {
  return {
    type: "LOG_IN",
  };
};

export const logOut = () =&gt; {
  return {
    type: "LOG_OUT",
  };
};

</code></pre>
<h2 id="reducerdispatchaction">如何获取 Reducer 以及 Dispatch Action</h2>
<p>从 <code>react-redux</code> 中引入 <code>useSelector</code>（在其中可以访问整个 state），以便获取 <code>counterReducer</code>。为了看到我之前说过的关于我的小秘密，我们还需要获取 <code>authReducer</code>。</p>
<p>我会在 App.js 文件中操作：</p>
<pre><code class="language-js">import { useSelector } from "react-redux";
</code></pre>
<p>我们需要引入定义好的 action，并从 react-redux 引入 <code>useDispatch</code> 钩子，以便 dispatch action。</p>
<pre><code class="language-js">import { useDispatch } from "react-redux";
</code></pre>
<p>现在 <code>useDispatch</code> 钩子和其它东西都是可用的，我们需要设置“增加”、“减少”、“重置”、“登入”和“登出”按钮。</p>
<p>需要为每个按钮绑定点击事件处理程序，来 dispatch 相应的 action。现在 app.js 的完整代码如下：</p>
<pre><code class="language-js">// src/App.js
import "./App.css";
import { useSelector, useDispatch } from "react-redux";
import {
  decrement,
  increment,
  reset,
  logIn,
  logOut,
} from "./redux/actions/index";

function App() {
  const counter = useSelector((state) =&gt; state.counter);
  const auth = useSelector((state) =&gt; state.auth);
  const dispatch = useDispatch();

  return (
    &lt;div className="App"&gt;
      &lt;h1&gt;
         Hello World &lt;br /&gt; A little Redux Project. YaaY!
      &lt;/h1&gt;
      &lt;h3&gt;Counter&lt;/h3&gt;
      &lt;h3&gt;{counter}&lt;/h3&gt;
      &lt;button onClick={() =&gt; dispatch(increment())}&gt;Increase&lt;/button&gt;
      &lt;button onClick={() =&gt; dispatch(reset())}&gt;Reset&lt;/button&gt;
      &lt;button onClick={() =&gt; dispatch(decrement())}&gt;Decrease&lt;/button&gt;

      &lt;h2&gt;For Logged in users only&lt;/h2&gt;
      &lt;p&gt;Log in to see a secret about me&lt;/p&gt;
      &lt;button onClick={() =&gt; dispatch(logIn())}&gt;Login&lt;/button&gt;
      &lt;button onClick={() =&gt; dispatch(logOut())}&gt;Logout&lt;/button&gt;
      {auth ? (
        &lt;div&gt;
          &lt;p&gt;
            I don't know more than 50% of redux. But if you know 50% of it, you're like a Superman.
          &lt;/p&gt;
        &lt;/div&gt;
      ) : (
        ""
      )}
    &lt;/div&gt;
  );
}

export default App;
</code></pre>
<p>最终结果如下：<br>
<img src="https://www.freecodecamp.org/news/content/images/2021/06/redux.gif" alt="redux" width="600" height="400" loading="lazy"></p>
<p>希望你能找到那个关于我的小秘密。</p>
<h2 id="">总结</h2>
<p>感谢你的阅读，希望你通过本文能够对 Redux 有基本的了解，进而使用它开发一些自己的东西。</p>
<p>想和我联系的话，可以关注我的 <a href="http://twitter.com/koladechris">Twitter</a>，我经常会在上面讨论编码以及 web 开发相关的话题。</p>
<!--kg-card-end: markdown--><p>原文：<a href="https://www.freecodecamp.org/news/learn-redux-by-making-a-counter-application/">Learn Redux by Making a Counter Application</a>，作者：<a href="https://www.freecodecamp.org/news/author/kayode/">Kayode, Kolade Christopher</a></p> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Fetch API – 如何在 JavaScript 中发出 GET 请求和 POST 请求 ]]>
                </title>
                <description>
                    <![CDATA[ 你的系统经常会通过与其它服务器通信来获取信息。 比如说，一个新用户想要在你的网站注册一个账号，比起手动地填写个人信息表单，他们更希望使用其它服务或平台（即第三方认证）上已经保存的信息来注册。 这样，你的系统需要和第三方系统通信来获取用户信息。这将通过 API [https://chinese.freecodecamp.org/news/what-is-an-api/]  调用来完成。 > API（Application Programming Interface，应用程序接口），就是一组定义软件或系统之间如何通信的规则。 我手绘的 API 示意图在异步编程语言（如 Javascript）编写的单页应用中，有一个很有用的工具可以实现这个功能：fetch()。 什么是 Fetch API？ fetch()  是一种可以发起 AJAX（异步 JavaScript 和 XML）请求的机制。 异步意味着你可以使用 fetch 调用外部 API 而不会阻塞其它指令的执行。这样，即使 API 调用还没有完成，其它函数也可以继续执行。 API 返回响应（数据）的时候，会接着执行异步 ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/how-to-make-api-calls-with-fetch/</link>
                <guid isPermaLink="false">60d066cc3a9d980616323cf7</guid>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Humilitas ]]>
                </dc:creator>
                <pubDate>Mon, 21 Jun 2021 09:00:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2021/06/Fetch-API.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>你的系统经常会通过与其它服务器通信来获取信息。</p>
<p>比如说，一个新用户想要在你的网站注册一个账号，比起手动地填写个人信息表单，他们更希望使用其它服务或平台（即<strong>第三方认证</strong>）上已经保存的信息来注册。</p>
<p>这样，你的系统需要和第三方系统通信来获取用户信息。这将通过 <a href="https://chinese.freecodecamp.org/news/what-is-an-api/"><strong>API</strong></a> 调用来完成。</p>
<blockquote>
<p>API（Application Programming Interface，应用程序接口），就是一组定义软件或系统之间如何通信的规则。</p>
</blockquote>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/05/IMG_20210530_115853.jpg" alt="IMG_20210530_115853" width="600" height="400" loading="lazy"></p>
<div style="text-align:center">我手绘的 API 示意图</div>
<p>在异步编程语言（如 Javascript）编写的单页应用中，有一个很有用的工具可以实现这个功能：<code>fetch()</code>。</p>
<h2 id="fetchapi">什么是 Fetch API？</h2>
<p><code>fetch()</code> 是一种可以发起 AJAX（异步 JavaScript 和 XML）请求的机制。</p>
<p><strong>异步</strong>意味着你可以使用 fetch 调用外部 API 而不会阻塞其它指令的执行。这样，即使 API 调用还没有完成，其它函数也可以继续执行。</p>
<p>API 返回响应（数据）的时候，会接着执行异步任务（fetch）。如果还是难以理解的话，可以查看我的<a href="https://ubahthebuilder.tech/introduction-to-asynchronous-programming-with-javascript">这篇文章</a>，其中详细介绍了异步代码的概念。</p>
<p>然而，需要注意的是，fetch 并不是 JavaScript 规范的一部分，所以不能在 Node.js 环境中使用它（除非安装了相应模块）。</p>
<h2 id="javascriptfetch">如何在 JavaScript 中使用 <code>fetch()</code></h2>
<p>谈到 API，就需要谈到<strong>端点（endpoint）</strong>，端点是一个唯一的 URL，可以通过调用它来与其它系统交互。</p>
<p>假设我们要请求一个外部 API 来获取一些数据（如博客）。这里我们使用 GET 请求。</p>
<p>以端点 URL 作为参数，调用 <code>fetch()</code>：</p>
<pre><code class="language-js">fetch('https://ubahthebuilder.tech/posts/1');
</code></pre>
<div style="text-align:center">从外部 API 获取博客内容</div>
<p>这个端点的响应体是一篇博客的信息：</p>
<pre><code class="language-js">{
userId: 1,
id: 1,
title: 'A post by Kingsley',
body: 'Brilliant post on fetch...',
};
</code></pre>
<p>我们的最终目的是要获取响应体，但是除了响应体之外，响应对象还包含很多其它信息，包括状态码、响应头等。</p>
<blockquote>
<p>注意，fetch API 返回一个 promise。因此，需要使用 then() 函数来处理决议（resolution）。<a href="https://ubahthebuilder.tech/introduction-to-asynchronous-programming-with-javascript">点击此处</a>了解更多关于 promise 的内容。</p>
</blockquote>
<p>fetch API 返回的数据通常不是可用的格式，所以需要把它转换为 JavaScript 代码方便操作的格式。幸好，可以使用 <code>json()</code> 方法来做：</p>
<pre><code class="language-js">fetch('https://ubahthebuilder.tech/posts/1')
.then(data =&gt; {
return data.json();
})
.then(post =&gt; {
console.log(post.title);
});
</code></pre>
<div style="text-align:center">从 API 获取博客信息，取出博客标题</div>
<p>如上方代码所示，可以在后续的 <code>then()</code> 方法中解析数据（例中我只取出了标题）。</p>
<p>这个示例中，我们只是从 API 中获取了一篇博客，如果我们想要发布一篇博客呢？</p>
<h2 id="post">如何发起 POST 请求</h2>
<p>跳出 GET 请求，要发起 POST 请求还需要设置一些选项参数。到目前为止，只为 <code>fetch()</code> 提供了一个参数——URL 端点。</p>
<p>对于 post 请求，需要传入一个选项配置对象作为第二个参数。这个对象中可以包含许多参数，示例中只包含了一些必要信息。</p>
<p>由于要发送 POST 请求，所以需要声明请求方法是 POST。</p>
<p>还需要传入一些博客信息来创建一个新博客。因为发送的是 JSON 格式的数据，所以需要在请求头中将 <em>Content-Type</em> set 设置为 <em>application/json</em>。最后，设置请求体，它是一个 JSON 字符串。</p>
<pre><code class="language-js">const update = {
title: 'A blog post by Kingsley',
body: 'Brilliant post on fetch API',
userId: 1,
};

const options = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(update),
};
</code></pre>
<p>调用 API：</p>
<pre><code class="language-js">fetch('https://jsonplaceholder.typicode.com/posts', options)
  .then(data =&gt; {
      if (!data.ok) {
        throw Error(data.status);
       }
       return data.json();
      }).then(update =&gt; {
      console.log(update);
      // {
      //
      // title: 'A blog post by Kingsley',
      //
      // body: 'Brilliant post on fetch API',
      //
      // userId: 1,
      //
      // id: 101
      // };
      }).catch(e =&gt; {
      console.log(e);
      });
</code></pre>
<p>如果请求成功，会得到一个响应，其中包含了刚刚创建的博客信息。响应的具体内容取决于 API 的实现方式。</p>
<p>最后要注意的是，随着时间的推移，端点可能会改变、API 也可能会重构，所以应该把所有 fetch 调用放在一起以便于后续调整。</p>
<h2 id="">总结</h2>
<p>本文要点如下：</p>
<ul>
<li>计算机系统、软件通过 API 来互相通信。</li>
<li>API 包含了系统之间交互的规则集和协议。例如，Facebook 可能会调用 Google 的 API 来获取用户信息。</li>
<li>在前端 JavaScript 代码中，可以使用 <code>fetch</code> 来调用 API。</li>
<li>使用 fetch 发起 GET 请求，只需要传入一个 URL 端点作为参数。</li>
<li>要发起 POST 请求，还需传入配置对象作为参数。</li>
</ul>
<p>如果你喜欢我的文章，可以<a href="https://buymeacoffee.com/ubahthebuilder">为我买一杯咖啡</a>以示支持。</p>
<p>谢谢阅读！</p>
<!--kg-card-end: markdown--><p>原文：<a href="https://www.freecodecamp.org/news/how-to-make-api-calls-with-fetch/">Fetch API – How to Make a GET Request and POST Request in JavaScript</a>，作者：<a href="https://www.freecodecamp.org/news/author/ubahthebuilder/">Kingsley Ubah</a></p> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 五个关键的 React 知识点 ]]>
                </title>
                <description>
                    <![CDATA[ 有些概念和知识是每个 React 开发者都需要知道的，然而大多数教程都没有涉及到。 我为你们挑选了几个我认为最重要的、很少文章会详细介绍的主题。 一起来看一下这五个关键的 React 知识点，你在别处可能看不到这些内容。 1. React state 是如何更新的 作为 React 开发者，你知道可以通过 useState  和 useReducer  钩子来创建和更新 state。 当你使用这两个钩子更新组件 state 时到底发生了什么呢？state 是立即更新还是在一段时间之后才会更新呢？ 一起来看下面的代码，它是一个简单的计数器应用。正如你所想，点击其中的按钮，计数器就会加 1。 import React from 'react'; export default function App() {   const [count, setCount] = React.useState(0)   function addOne() {   ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/5-react-lessons-tutorials-dont-teach/</link>
                <guid isPermaLink="false">60c867411b6c2406049fd786</guid>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Humilitas ]]>
                </dc:creator>
                <pubDate>Tue, 15 Jun 2021 07:30:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2021/06/5-key-lessons-react-tutorials-dont-teach.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>有些概念和知识是每个 React 开发者都需要知道的，然而大多数教程都没有涉及到。</p>
<p>我为你们挑选了几个我认为最重要的、很少文章会详细介绍的主题。</p>
<p>一起来看一下这五个关键的 React 知识点，你在别处可能看不到这些内容。</p>
<h2 id="1reactstate">1. React state 是如何更新的</h2>
<p>作为 React 开发者，你知道可以通过 <code>useState</code> 和 <code>useReducer</code> 钩子来创建和更新 state。</p>
<p>当你使用这两个钩子更新组件 state 时到底发生了什么呢？state 是立即更新还是在一段时间之后才会更新呢？</p>
<p>一起来看下面的代码，它是一个简单的计数器应用。正如你所想，点击其中的按钮，计数器就会加 1。</p>
<pre><code class="language-js">import React from 'react';

export default function App() {
  const [count, setCount] = React.useState(0)

  function addOne() {
    setCount(count + 1);
  }

  return (
    &lt;div&gt;
      &lt;h1&gt;Count: {count}&lt;/h1&gt; {/* 1 (as we expect) */}

      &lt;button onClick={addOne}&gt;+ 1&lt;/button&gt;
    &lt;/div&gt;
  );
}
</code></pre>
<p>如果再加一行代码，它同样尝试让计数器加 1——会发生什么？</p>
<p>点击按钮的时候，计数会增加 1 还是增加 2 呢？</p>
<pre><code class="language-js">import React from 'react';

export default function App() {
  const [count, setCount] = React.useState(0)

  function addOne() {
    setCount(count + 1);
    setCount(count + 1);
  }

  return (
    &lt;div&gt;
      &lt;h1&gt;Count: {count}&lt;/h1&gt; {/* 1?! */}

      &lt;button onClick={addOne}&gt;+ 1&lt;/button&gt;
    &lt;/div&gt;
  );
}
</code></pre>
<p>运行这个代码会发现，它只增加了 1！尽管执行了两次 state 更新操作。</p>
<p><em>尽管明确执行了两次加 1 的操作，为什么计数器显示为 1？</em></p>
<p>原因在于我们第一次执行更新操作的时候 React 只是计划了一次 state 更新。因为它只是做计划而不是立即执行（它是异步的），所以我们第二次尝试更新时 <code>count</code> 变量还是原来的值（两次 <code>setCount()</code> 中 <code>count</code> 的值是相同的）。</p>
<p>换句话说，由于 state 更新是计划性的而不是立即执行的，第二次调用 <code>setCount</code> 时，<code>count</code> 还是 <code>0</code>，而不是 <code>1</code>。</p>
<p>尽管 state 更新是异步的，我们可以在 <code>useState</code> 的 setter 函数中使用内部函数来更可靠地更新 state。</p>
<p>这个内部函数允许我们获取前一个 state 的值，使用这种方式，我们可以看到计数器如我们期望的那样增加了 2：</p>
<pre><code class="language-js">import React from 'react';

export default function App() {
  const [count, setCount] = React.useState(0)

  function addOne() {
    setCount(prevCount =&gt; prevCount + 1); // 1
    setCount(prevCount =&gt; prevCount + 1); // 2
  }

  return (
    &lt;div&gt;
      &lt;h1&gt;Count: {count}&lt;/h1&gt;
      &lt;button onClick={addOne}&gt;+ 1&lt;/button&gt;
    &lt;/div&gt;
  );
}
</code></pre>
<h2 id="2effect">2. 最好使用多个 effect 而不是使用单个</h2>
<p>在执行副作用（side effect）时，很多 React 开发者会试图在单次 <code>useEffect</code> 调用中执行多个副作用。</p>
<p>那看起来像什么样呢？可以看到，下面的代码在一个 useEffect 钩子里同时获取了帖子和评论，并分别更新了对应的 state 变量：</p>
<pre><code class="language-js">import React from "react";

export default function App() {
  const [posts, setPosts] = React.useState([]);
  const [comments, setComments] = React.useState([]);

  React.useEffect(() =&gt; {
    // 获取帖子数据
    fetch("https://jsonplaceholder.typicode.com/posts")
      .then((res) =&gt; res.json())
      .then((data) =&gt; setPosts(data));

    // 获取评论数据
    fetch("https://jsonplaceholder.typicode.com/comments")
      .then((res) =&gt; res.json())
      .then((data) =&gt; setComments(data));
  }, []);

  return (
    &lt;div&gt;
      &lt;PostsList posts={posts} /&gt;
      &lt;CommentsList comments={comments} /&gt;
    &lt;/div&gt;
  );
}
</code></pre>
<p>与其把所有副作用都挤在一个 effect 钩子里，不如通过多次调用把它们放在独立的钩子里。</p>
<p>这种做法允许我们把不同的操作分离到不同的 effect 中，更好地做到关注点分离。</p>
<p>比起类组件中的生命周期函数，React hooks 的一个主要优势就是更好的关注点分离。</p>
<p>类组件中每个生命周期函数只能调用一次，所以无法将副作用分离到多个函数里。例如，只能把组件挂载后要执行的所有操作都包含在一个 <code>componentDidMount</code> 函数里。</p>
<p>React hooks 的主要优势就是能够让我们根据功能来组织代码。我们不仅能够将组件渲染之后要执行的操作分离到多个 effect，还可以移动 state 相关代码的位置。</p>
<pre><code class="language-js">import React from "react";

export default function App() {
  const [posts, setPosts] = React.useState([]);
  React.useEffect(() =&gt; {
    fetch("https://jsonplaceholder.typicode.com/posts")
      .then((res) =&gt; res.json())
      .then((data) =&gt; setPosts(data));
  }, []);

  const [comments, setComments] = React.useState([]);
  React.useEffect(() =&gt; {
    fetch("https://jsonplaceholder.typicode.com/comments")
      .then((res) =&gt; res.json())
      .then((data) =&gt; setComments(data));
  }, []);

  return (
    &lt;div&gt;
      &lt;PostsList posts={posts} /&gt;
      &lt;CommentsList comments={comments} /&gt;
    &lt;/div&gt;
  );
}
</code></pre>
<p>这意味着我们可以把 state 钩子和相关的 effect 钩子放在一起，使得代码结构清晰、功能一目了然。</p>
<h2 id="3stateusestateusereducer">3. 不要优化更新 state 的函数（useState, useReducer）</h2>
<p>从父组件向子组件传递一个回调函数时有一个常见问题要处理，即避免它被重新创建，除非它的参数发生了变化。</p>
<p>我们可以借助于 <code>useCallback</code> 钩子来完成这个优化。</p>
<p>useCallback 钩子是专门为传递给子组件的回调函数设计的，可以避免不必要地重新创建这些函数，重新创建这些函数的过程会在每次重渲染时引起性能问题。</p>
<p>因为每次父组件重渲染时，会引起它所有子组件也重新渲染，这就导致了回调函数在每次重渲染时都被重新创建。</p>
<p>然而，如果我们使用 setter 函数来更新由 useState 或 useReducer 钩子创建的 state，则不需要用到 useCallback。</p>
<p>换句话说，下面这种做法是不必要的：</p>
<pre><code class="language-js">import React from "react";

export default function App() {
  const [text, setText] = React.useState("")

  // 不要使用 useCallback 包裹 setText（因为 setText 是不会变化的）
  const handleSetText = React.useCallback((event) =&gt; {
    setText(event.target.value);
  }, [])

  return (
    &lt;form&gt;
      &lt;Input text={text} handleSetText={handleSetText} /&gt;
      &lt;button type="submit"&gt;Submit&lt;/button&gt;
    &lt;/form&gt;
  );
}

function Input({ text, handleSetText }) {
  return(
    &lt;input type="text" value={text} onChange={handleSetText}  /&gt;
  )
}
</code></pre>
<p>React 文档中说明了其原因：</p>
<blockquote>
<p>React 保证了 setState 函数标识是稳定的，并且不会在重渲染时改变，因此在 useEffect 和 useCallback 的依赖列表中忽略它是安全的。</p>
</blockquote>
<p>所以说，不仅不需要用 useCallBack 优化它们，也不需要在 useEffect 的依赖列表中包含它们，因为它们是稳定不变的。</p>
<p>这一点很重要，因为在许多情况下，它能精简代码。最重要的是，这种优化是徒劳的，因为它自身就有可能导致性能问题。</p>
<h2 id="4userefstate">4. useRef 钩子能够在重渲染过程中保存 state</h2>
<p>作为 React 开发者，利用 ref 来获取指定 React 元素的引用有时是非常有用的。我们使用 <code>useRef</code> 钩子来创建 ref。</p>
<p>记住，<code>useRef</code> 的用处并不仅限于引用某个 DOM 元素。React 文档中说明了这一点：</p>
<blockquote>
<p>useRef 创建的 ref 对象是一个带有 current 属性的通用容器，current 属性可以保存任意类型的值，并且它的值是可变的。</p>
</blockquote>
<p>使用 <code>useRef</code> 保存和更新一些数据是有一定好处的，它可以不通过内存来保存数据，使得这些数据在重渲染时不会被清除掉。</p>
<p>如果我们想利用普通的变量在重渲染过程中追踪数据变化是不可行的，因为每次组件渲染时它都会被重新初始化。然而，如果使用 ref 的话，其中的数据能在每次组件渲染时保持不变。</p>
<p><em>useRef 的这种用法的使用场景是什么？</em></p>
<p>如果只想在初始渲染时执行某些副作用，它就派上用场了：</p>
<pre><code class="language-js">import React from "react";

export default function App() {
  const [count, setCount] = React.useState(0);
  const ref = React.useRef({ hasRendered: false });

  React.useEffect(() =&gt; {
    if (!ref.current.hasRendered) {
      ref.current.hasRendered = true;
      console.log("perform action only once!");
    }
  }, []);

  return (
    &lt;div&gt;
      &lt;button onClick={() =&gt; setCount(count + 1)}&gt;Count: {count}&lt;/button&gt;
    &lt;/div&gt;
  );
}
</code></pre>
<p>你可以自己尝试运行一下这段代码。</p>
<p>如你所见，不管点击按钮多少次、state 如何更新、重新渲染多少次，我们想要执行的操作（<code>console.log</code>）只会执行一次。</p>
<h2 id="5react">5. 如何防止 React 应用崩溃</h2>
<p>React 开发者需要知道的最重要的一点——尤其是在还没把应用发布出去的时候——就是如何处理未捕获的错误。</p>
<p>下面的例子中，我们尝试在应用中展示一个 Header 组件，同时执行了一个将会抛出错误的操作，即试图从 null 值中读取属性：</p>
<pre><code class="language-js">import React from "react";

export default function App() {
  return (
    &lt;&gt;
      &lt;Header /&gt;
    &lt;/&gt;
  );
}

function Header() {
  const user = null;

  return &lt;h1&gt;Hello {user.name}&lt;/h1&gt;; // error!
}
</code></pre>
<p>如果我们把这些代码推送到生产中，就会看到如下空白内容：</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/04/5-key-lessons-1.png" alt="5-key-lessons-1" width="600" height="400" loading="lazy"></p>
<p><em>为什么我们什么也看不到？</em></p>
<p>我们依然能够在 React 文档中找到答案：</p>
<blockquote>
<p>在 React 16 中，未被任何错误边界捕获的错误将会导致整个 React 组件树的卸载。</p>
</blockquote>
<p>在开发过程中，你会看到一大片红色的包含栈追踪信息的错误消息，告诉你哪里产生了错误。然而，当线上应用出错时，你只会看到一片空白。</p>
<p>这并不是你期望的行为。</p>
<p>有一个办法可以修复这个问题，即应用意外崩溃时向用户展示一些内容，告诉他们应用出错了。可以把组件树包含在一个错误边界中。</p>
<p>错误边界是一种能够捕获错误并展示后备内容的组件，后备内容中可以展示消除错误的步骤（如刷新页面）。</p>
<p>可以借助于 <code>react-error-boundary</code> 包来使用错误边界。可以将那些易于出错的组件包含在错误边界中，也可以把整个组件树都包含在错误边界中：</p>
<pre><code class="language-js">import React from "react";
import { ErrorBoundary } from "react-error-boundary";

export default function App() {
  return (
    &lt;ErrorBoundary FallbackComponent={ErrorFallback}&gt;
      &lt;Header /&gt;
    &lt;/ErrorBoundary&gt;
  );
}

function Header() {
  const user = null;

  return &lt;h1&gt;Hello {user.name}&lt;/h1&gt;;
}

function ErrorFallback({ error }) {
  return (
    &lt;div role="alert"&gt;
      &lt;p&gt;Oops, there was an error:&lt;/p&gt;
      &lt;p style={{ color: "red" }}&gt;{error.message}&lt;/p&gt;
    &lt;/div&gt;
  );
}
</code></pre>
<p>可以以任意形式来展示这些错误信息，就像编写普通组件那样。</p>
<p>现在，发生错误时展现的结果好多了：</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/04/5-key-lessons-2.png" alt="5-key-lessons-2" width="600" height="400" loading="lazy"></p>
<!--kg-card-end: markdown--><p>原文：<a href="https://www.freecodecamp.org/news/5-react-lessons-tutorials-dont-teach/">5 Key React Lessons the Tutorials Don't Teach You</a>，作者：<a href="https://www.freecodecamp.org/news/author/reed/">Reed Barger</a></p> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 用 CSS 实现自定义滚动条样式 ]]>
                </title>
                <description>
                    <![CDATA[ 你见过自定义滚动条样式的网站吗？是不是很想知道它们是怎么实现的？读完这篇文章，你会了解到使用 CSS 自定义滚动条样式的所有知识。 在本教程中，你将：  * 学习能用来自定义滚动条样式的原生 CSS 属性  * 使用 CSS 创建 4 种独特的滚动条  * 了解一些自定义滚动条样式的外部库，它们提供了跨浏览器的支持 特色滚动条视频教程 如果你更喜欢看视频教程，可以观看这个视频 [https://www.youtube.com/watch?v=Gp6c9lJgPUI] 。欢迎在视频页面留下你的评论或提出疑问，也欢迎留下你的作品链接（如 CodePen 链接），这样其他人也可以看到你设计的自定义滚动条。 自定义滚动条的利弊 在编码之前，我觉得有必要先了解一下在网站或应用中使用自定义滚动条所带来的影响。 好的方面在于它能让你的网站在无数使用默认滚动条的网站中脱颖而出，任何能加深网站访问者印象的东西都能让你长期受益。 另一方面，许多 UI 设计师认为绝不应该干涉滚动条等“标准化” UI 组件的表现。如果过度修改滚动条，可能会给网站/应用的用户造成困扰。 如果是自己的个人网站， ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/css-scrollbar-tutorial/</link>
                <guid isPermaLink="false">60a13660724d3b04e234ef2f</guid>
                
                    <category>
                        <![CDATA[ CSS ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Humilitas ]]>
                </dc:creator>
                <pubDate>Fri, 14 May 2021 10:00:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2021/05/CSS-scrollbar-thumbnail.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>你见过自定义滚动条样式的网站吗？是不是很想知道它们是怎么实现的？读完这篇文章，你会了解到使用 CSS 自定义滚动条样式的所有知识。</p>
<p>在本教程中，你将：</p>
<ul>
<li>学习能用来自定义滚动条样式的原生 CSS 属性</li>
<li>使用 CSS 创建 4 种独特的滚动条</li>
<li>了解一些自定义滚动条样式的外部库，它们提供了跨浏览器的支持</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/04/crazy-scrollbar.PNG" alt="crazy-scrollbar" width="600" height="400" loading="lazy"></p>
<div style="text-align:center">特色滚动条</div>
<h2 id="">视频教程</h2>
<p>如果你更喜欢看视频教程，可以观看<a href="https://www.youtube.com/watch?v=Gp6c9lJgPUI">这个视频</a>。欢迎在视频页面留下你的评论或提出疑问，也欢迎留下你的作品链接（如 CodePen 链接），这样其他人也可以看到你设计的自定义滚动条。</p>
<h2 id="">自定义滚动条的利弊</h2>
<p>在编码之前，我觉得有必要先了解一下在网站或应用中使用自定义滚动条所带来的影响。</p>
<p>好的方面在于它能让你的网站在无数使用默认滚动条的网站中脱颖而出，任何能加深网站访问者印象的东西都能让你长期受益。</p>
<p>另一方面，许多 UI 设计师认为绝不应该干涉滚动条等“标准化” UI 组件的表现。如果过度修改滚动条，可能会给网站/应用的用户造成困扰。</p>
<p>如果是自己的个人网站，就不必担心这些了，只要你自己看着喜欢就行。</p>
<p>如果想要在工作项目或者用来获利的项目中实现自定义滚动条，则需要进行对照测试，基于结果数据来做决定。</p>
<p>归根结底，大多数人写代码是为了增加业务收入，你需要牢记这一点。</p>
<h2 id="">起步</h2>
<p>首先要做的是创建一个基本的页面布局，页面要足够大，使浏览器出现滚动条：</p>
<pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html lang="en"&gt;
&lt;head&gt;
    &lt;meta charset="UTF-8"&gt;
    &lt;meta http-equiv="X-UA-Compatible" content="IE=edge"&gt;
    &lt;meta name="viewport" content="width=device-width, initial-scale=1.0"&gt;
    &lt;link rel='stylesheet' href="styles.css"&gt;
    &lt;title&gt;Document&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
    &lt;div class="container"&gt;
        &lt;h1&gt;CSS Scrollbar Customization&lt;/h1&gt;
        &lt;p class="para"&gt;
        "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
      &lt;/p&gt;
      &lt;p class="para"&gt;
        "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
      &lt;/p&gt;
      &lt;p class="para"&gt;
        "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
      &lt;/p&gt;
      &lt;p class="para"&gt;
        "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
      &lt;/p&gt;
      &lt;p class="para"&gt;
        "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
      &lt;/p&gt;
      &lt;p class="para"&gt;
        "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
      &lt;/p&gt;
      &lt;p class="para"&gt;
        "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
      &lt;/p&gt;
      &lt;p class="para"&gt;
        "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
      &lt;/p&gt;
      &lt;p class="para"&gt;
        "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
      &lt;/p&gt;
      &lt;p class="para"&gt;
        "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
      &lt;/p&gt;
      &lt;/div&gt;
&lt;/body&gt;
&lt;/html&gt;
</code></pre>
<p>没什么好看的内容，只有一个用来构建页面布局的 div 容器、一个标题、一些用来填充页面的段落。</p>
<p>以下是一些 CSS 样式，它们能让页面好看一点：</p>
<pre><code class="language-css">body {
    font-family: Arial, Helvetica, sans-serif;
    margin: 0;
    
  }
.para {
  font-size: 16px;
  padding: 20px;
  width: 70%;
}

.container {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
}
</code></pre>
<p>页面效果如下：</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/04/layout-preview.PNG" alt="layout-preview" width="600" height="400" loading="lazy"></p>
<h2 id="css">如何使用 CSS 创建自定义滚动条</h2>
<p>准备工作做好了，接下来进入本教程中有趣的部分。本节内容的第一部分会介绍一些滚动条样式相关的 CSS 属性。第二部分我们会实现 4 种不同类型的滚动条，能为你之后制作自己的滚动条提供一些思路。</p>
<h3 id="css">滚动条样式相关的 CSS 属性</h3>
<p>很不幸，现在还没有对这些 CSS 属性的标准化跨浏览器支持。Firefox 和一些基于 Webkit 内核的浏览器（如 Chrome、Edge、Safari）各自提供了不同的属性。</p>
<p>本教程主要针对 Webkit 内核的浏览器，因为它们提供了更多样式属性，不过我也会简单介绍一下 Firefox。</p>
<h3 id="webkit">Webkit 滚动条样式属性</h3>
<ul>
<li><code>::-webkit-scrollbar</code> – 整个滚动条</li>
<li><code>::-webkit-scrollbar-track</code> – 滚动条的滚动区域（轨道）</li>
<li><code>::-webkit-scrollbar-thumb</code> – 滚动条的可拖拽部分（滑块）</li>
</ul>
<p>以下是可用但不常用的属性：</p>
<ul>
<li><code>::-webkit-scrollbar-button</code> – 滚动条两端的上/下（或左/右）按钮</li>
<li><code>::-webkit-scrollbar-track-piece</code> – 滚动条轨道未被滑块覆盖的部分</li>
<li><code>::-webkit-scrollbar-corner</code> – 垂直滚动条和水平滚动条交汇的部分</li>
</ul>
<h3 id="firefox">Firefox 滚动条样式属性</h3>
<p>Firefox 中当前可用的两个滚动条样式属性：</p>
<ul>
<li><code>scrollbar-width</code> – 控制滚动条的宽度，只有两个可选项：<code>auto</code> 或 <code>thin</code></li>
<li><code>scrollbar-color</code> – 接收两个颜色，依次指定滑块和轨道的颜色</li>
</ul>
<p>了解了自定义滚动条的样式属性，我们通过几个例子将它们付诸实践。</p>
<h3 id="">暗色主题滚动条</h3>
<p>现在暗色主题的网站非常流行。坚持使用默认的滚动条可能会惹恼用户，因为它与整个网站的暗色主题不搭。</p>
<p>用我们新学的知识创建一个暗色主题的滚动条，它的边框是圆形的（灵感来自 CSS Tricks 网站）：</p>
<pre><code class="language-css">html::-webkit-scrollbar {
      width: 20px; 
   }

html::-webkit-scrollbar-track {
    background-color: black;
  }

html::-webkit-scrollbar-thumb {
    background: #4e4e4e;
    border-radius: 25px;
  }
</code></pre>
<p>最终效果在截图中比较难看清，不过可以看到轨道是黑色的、滑块是深灰色的。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/04/dark-theme.PNG" alt="dark-theme" width="600" height="400" loading="lazy"></p>
<h3 id="">极简滚动条</h3>
<p>这个示例中将会制作一个极简的滚动条。如果你的网站追求简单优雅的风格，这种滚动条会很适合。</p>
<p>需要注意的重点是，你可以使用 <code>hover</code> 和 <code>active</code> 伪元素来进一步设置滚动条样式。本例中，当你把鼠标悬停在滑块上以及拖动滑块时它的颜色会变成更深的灰色。</p>
<pre><code class="language-css">html::-webkit-scrollbar {
    width: 10px;
  }

html::-webkit-scrollbar-track {
    background: rgb(179, 177, 177);
    border-radius: 10px;
}

html::-webkit-scrollbar-thumb {
    background: rgb(136, 136, 136);
    border-radius: 10px;
  }

html::-webkit-scrollbar-thumb:hover {
    background: rgb(100, 100, 100);
    border-radius: 10px;
  }

html::-webkit-scrollbar-thumb:active {
    background: rgb(68, 68, 68);
    border-radius: 10px;
  }
</code></pre>
<p>最终效果：</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/04/minimalist.PNG" alt="minimalist" width="600" height="400" loading="lazy"></p>
<h3 id="">图案滚动条</h3>
<p>这个部分的重点是使用重复的线性渐变在滚动条轨道中创建图案效果，这个方法也可以运用在滑块上。</p>
<p>另外一点需要注意的是，你也可以为滑块设置边框样式，利用边框样式可以创建许多有趣的效果。本例中，我把滑块的背景颜色设为透明，这样就可以在滚动的同时看到轨道中的图案。</p>
<pre><code class="language-css">html::-webkit-scrollbar {
   	width: 20px;
  }
html::-webkit-scrollbar-track {
  	background-image: repeating-linear-gradient(45deg, red 0, red 1px, transparent 0, transparent 50%);
  	background-size: 10px 10px;
	}
html::-webkit-scrollbar-thumb {
    background: transparent;
    border-radius: 5px;
    border: 2px solid black;
    box-shadow: inset 1px 1px 5px black ;
   }
</code></pre>
<p>最终效果：</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/04/patterned.PNG" alt="patterned" width="600" height="400" loading="lazy"></p>
<h3 id="">“动态”渐变滚动条</h3>
<p>这个例子用到了线性渐变，并使用了一个小技巧：利用滑块的阴影使得滚动条在页面滚动时看起来像是在变换颜色，实际上是轨道的背景透过滑块显示了出来。</p>
<p>阴影遮盖住了滑块之外的所有轨道空间，又由于滑块是透明的，所以轨道背景的渐变颜色透过它显示出来。</p>
<pre><code class="language-css">html::-webkit-scrollbar {
    width: 20px; 
  }
  
html::-webkit-scrollbar-track {
    background:  linear-gradient(0deg, rgba(255, 0, 0, 1) 0%, rgba(7, 0, 211, 1) 100%);
  }

html::-webkit-scrollbar-thumb {
    background: transparent;
    box-shadow: 0px 0px 0px 100vh black;
  }
</code></pre>
<p>最终效果：</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/04/animated.PNG" alt="animated" width="600" height="400" loading="lazy"></p>
<h2 id="">自定义滚动条的限制及备选方案</h2>
<p>显然，创建自定义滚动条存在一些问题。首先是缺少跨浏览器支持。其他可能问题包括：无法为滚动条增加过渡和动画效果、移动设备不支持自定义滚动条。</p>
<p>一个备选方案是：隐藏默认的滚动条，并使用外部库来实现，但这可能会影响页面性能。而且可能还有其他潜在的可用性问题，因为这些库依赖 JavaScript 来模拟原生的滚动条行为。</p>
<p>下面我会介绍两个用于制作滚动条的流行开源库。</p>
<h3 id="simplebar">SimpleBar</h3>
<p>使用原生滚动行为的自定义滚动条 JavaScript 库：操作简单、轻量、易用、跨浏览器。- Grsmto/simplebar</p>
<p><a href="https://github.com/Grsmto/simplebar"><img src="https://opengraph.githubassets.com/a3236d200e4fa9e299d00a79c560678cfeaf07d684bc6ec998bd25f3ebe12efb/Grsmto/simplebar" alt="simplebar" width="600" height="400" loading="lazy"></a></p>
<p>顾名思义，SimpleBar 旨在简化创建自定义滚动条的过程。唯一的缺点是它不能作为网站的主滚动条（译注：即根元素的滚动条）来使用，也不支持表格元素、文本输入区域和下拉选择框。</p>
<p>SimpleBar 主要适用于诸如动态聊天应用或一些在页面内部元素中有滚动行为的场景。</p>
<h3 id="overlayscrollbars">Overlay Scrollbars</h3>
<p>一个隐藏原生滚动条、提供自定义样式滚动条的插件，保留了原生的功能和体验。- KingSora/OverlayScrollbars</p>
<p><a href="https://github.com/KingSora/OverlayScrollbars"><img src="https://opengraph.githubassets.com/ad151bf617e4361235fd3bc54163fab57fea0c0552703c89100ebc063483e341/KingSora/OverlayScrollbars" alt="OverlayScrollbars" width="600" height="400" loading="lazy"></a></p>
<p>Overlay Scrollbars 与 SimpleBar 很相似，但是它提供了对 HTML body 元素的支持。这意味着除了跨浏览器支持和移动端支持等特性，还可以把它作为网站的主滚动条来使用。</p>
<h2 id="">总结</h2>
<p>希望本文能让你深入理解使用 CSS 自定义滚动条样式的工作原理。</p>
<p>如果有任何疑问，你可以在 YouTube 视频下方留下评论，我会尽力解答。也欢迎留下你自己的作品链接，与大家分享。</p>
<p>Github 仓库链接：<a href="https://github.com/renaissanceengineer/css-scrollbar-tutorial">https://github.com/renaissanceengineer/css-scrollbar-tutorial</a></p>
<!--kg-card-end: markdown--><p>原文：<a href="https://www.freecodecamp.org/news/css-scrollbar-tutorial/">CSS Scrollbar Styling Tutorial – How to Make a Custom Scrollbar</a>，作者：<a href="https://www.freecodecamp.org/news/author/charles-mahler/">Charles Mahler</a></p> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ MVC 架构详解 ]]>
                </title>
                <description>
                    <![CDATA[ MVC 架构让复杂应用的开发过程变得更易于管理，它允许多个开发者协同开发。 第一次了解 MVC 模式时，我被这些术语吓到了，当我实际运用这些概念时更是如此。 回过头去，理解了 MVC 的含义以及作用，就能更轻松地将它运用于 web 应用的开发。 什么是 MVC MVC，即 model-view-controller，其中每个组件的含义如下：  * 模型（Model）：后端，包含了所有数据逻辑  * 视图（View）：前端界面或 GUI  * 控制器（Controller）：应用的大脑，控制数据如何展示 MVC 的概念最早由 Trygve Reenskaug 提出，他提出将其作为一种开发桌面应用 GUI 的方式。 如今 MVC 被用于现代 web 应用开发，因为它增强了应用的灵活性、可维护性和可扩展性。 为什么要用 MVC？ 五个字：关注点分离（separation of concerns，缩写为 SoC）。 MVC 模式有助于将前端和后端代码拆分为独立的组件，这样更便于管理，而且能够更方便地改动其中的某一部分而不会互相影响。 不过说起来容易做起来难，尤其是多个开发 ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/the-model-view-controller-pattern-mvc-architecture-and-frameworks-explained/</link>
                <guid isPermaLink="false">6098ef4b0998fd05ae8c87ab</guid>
                
                    <category>
                        <![CDATA[ 设计模式 ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Humilitas ]]>
                </dc:creator>
                <pubDate>Mon, 10 May 2021 08:43:15 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2021/05/BG.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>MVC 架构让复杂应用的开发过程变得更易于管理，它允许多个开发者协同开发。</p>
<p>第一次了解 MVC 模式时，我被这些术语吓到了，当我实际运用这些概念时更是如此。</p>
<p>回过头去，理解了 MVC 的含义以及作用，就能更轻松地将它运用于 web 应用的开发。</p>
<h2 id="mvc">什么是 MVC</h2>
<p>MVC，即 model-view-controller，其中每个组件的含义如下：</p>
<ul>
<li><strong>模型（Model）</strong>：后端，包含了所有数据逻辑</li>
<li><strong>视图（View）</strong>：前端界面或 GUI</li>
<li><strong>控制器（Controller）</strong>：应用的大脑，控制数据如何展示</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/04/MVC3.png" alt="MVC3" width="600" height="400" loading="lazy"></p>
<p>MVC 的概念最早由 Trygve Reenskaug 提出，他提出将其作为一种开发桌面应用 GUI 的方式。</p>
<p>如今 MVC 被用于现代 web 应用开发，因为它增强了应用的灵活性、可维护性和可扩展性。</p>
<h2 id="mvc">为什么要用 MVC？</h2>
<p>五个字：<strong>关注点分离（separation of concerns，缩写为 SoC）</strong>。</p>
<p>MVC 模式有助于将前端和后端代码拆分为独立的组件，这样更便于管理，而且能够更方便地改动其中的某一部分而不会互相影响。</p>
<p>不过说起来容易做起来难，尤其是多个开发者同时更新、修改或调试一个成熟应用时。</p>
<h2 id="mvc">如何使用 MVC</h2>
<p>为了更好地说明 MVC 模式，我引入了一个 web 应用，它展示了这些概念是如何工作的。</p>
<p>我的 Car Clicker 应用是著名的 Cat Clicker 应用的变体。</p>
<p>我的应用主要有以下区别：</p>
<ol>
<li>没有猫咪，<strong>只有</strong>性能车的图片（对不住了，爱猫人士！）</li>
<li>列出了多种车型</li>
<li>有多个点击计数器</li>
<li>只展示选中的车</li>
</ol>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/04/Screen-Recording-2021-04-11-at-11.31.27.07-PM.gif" alt="Screen-Recording-2021-04-11-at-11.31.27.07-PM" width="600" height="400" loading="lazy"></p>
<p>让我们深入了解一下构成 MVC 架构模式的三个组件。</p>
<h3 id="">模型（数据）</h3>
<p>模型的任务是管理数据。不论数据是来自数据库、API 还是 JSON 对象，模型都要负责管理它们。</p>
<p>在 Car Clicker 应用中，模型对象包含一个由 car 对象组成的数组，其中含有这个应用所需的所有信息（数据）。</p>
<p>它还通过一个初始值为 <code>null</code> 的变量 <code>currentCar</code> 控制当前展示哪个汽车。</p>
<pre><code class="language-javaScript">const model = {
    currentCar: null,
    cars: [
        {
            clickCount: 0,
            name: 'Coupe Maserati',
            imgSrc: 'img/black-convertible-coupe.jpg',
        },
        {
            clickCount: 0,
            name: 'Camaro SS 1LE',
            imgSrc: 'img/chevrolet-camaro.jpg',
        },
        {
            clickCount: 0,
            name: 'Dodger Charger 1970',
            imgSrc: 'img/dodge-charger.jpg',
        },
        {
            clickCount: 0,
            name: 'Ford Mustang 1966',
            imgSrc: 'img/ford-mustang.jpg',
        },
        {
            clickCount: 0,
            name: '190 SL Roadster 1962',
            imgSrc: 'img/mercedes-benz.jpg',
        },
    ],
};
</code></pre>
<h3 id="ui">视图（UI）</h3>
<p>视图决定了用户看到的内容以及交互方式。</p>
<p>Car Clicker 应用有两个视图：<code>carListView</code> 和 <code>CarView</code>。</p>
<p>每个视图都有两个关键函数，定义其如何初始化及如何渲染。</p>
<p>这些函数决定了用户将会看到的内容以及交互方式。</p>
<h4 id="carlistview">carListView</h4>
<pre><code class="language-js">const carListView = {
    init() {
        // 保存 DOM 元素，方便后续访问
        this.carListElem = document.getElementById('car-list');

        // 渲染视图（使用正确的数据更新 DOM 元素）
        this.render();
    },

    render() {
        let car;
        let elem;
        let i;
        // 从控制器中获取待展示的汽车
        const cars = controller.getCars();

        // 确保渲染前列表是空的
        this.carListElem.innerHTML = '';

        // 遍历 cars 数组
        for(let i = 0; i &lt; cars.length; i++) {
            // 当前遍历到的车
            car = cars[i];

            // 创建一个汽车列表项（&lt;li&gt;）并设置其文本
            elem = document.createElement('li');
            elem.className = 'list-group-item d-flex justify-content-between lh-condensed';
            elem.style.cursor = 'pointer';
            elem.textContent = car.name;
            elem.addEventListener(
                'click',
                (function(carCopy) {
                    return function() {
                        controller.setCurrentCar(carCopy);
                        carView.render();
                    };
                })(car)
            );
            // 最后将其加入列表
            this.carListElem.appendChild(elem);
        }
    },
};
</code></pre>
<h4 id="carview">CarView</h4>
<pre><code class="language-js">const carView = {
    init() {
        // 保存 DOM 元素指针，方便后续访问
        this.carElem = document.getElementById('car');
        this.carNameElem = document.getElementById('car-name');
        this.carImageElem = document.getElementById('car-img');
        this.countElem = document.getElementById('car-count');
        this.elCount = document.getElementById('elCount');


        // 点击时增加当前汽车的计数
        this.carImageElem.addEventListener('click', this.handleClick);

        // 渲染视图（使用正确的数据更新 DOM 元素）
        this.render();
    },

    handleClick() {
    	return controller.incrementCounter();
    },

    render() {
        // 使用当前汽车的数据更新 DOM 元素
        const currentCar = controller.getCurrentCar();
        this.countElem.textContent = currentCar.clickCount;
        this.carNameElem.textContent = currentCar.name;
        this.carImageElem.src = currentCar.imgSrc;
        this.carImageElem.style.cursor = 'pointer';
    },
};
</code></pre>
<h3 id="">控制器（大脑）</h3>
<p>控制器负责获取数据、修改数据，并为用户提供数据。本质上，控制器就是视图和模型之间的链接。</p>
<p>通过 getter 和 setter 函数，控制器从模型拉取数据并初始化视图。</p>
<p>如果视图要更新后台数据，它会通过 setter 函数来修改数据。</p>
<pre><code class="language-js">const controller = {
    init() {
        // 将当前展示的车设为列表中的第一辆车
        model.currentCar = model.cars[0];

        // 初始化视图
        carListView.init();
        carView.init();
    },

    getCurrentCar() {
    	return model.currentCar;
    },

    getCars() {
    	return model.cars;
    },

    // 把“当前选中汽车”设为传入的对象
    setCurrentCar(car) {
    	model.currentCar = car;
    },

    // 增加当前选中汽车的计数
    incrementCounter() {
        model.currentCar.clickCount++;
        carView.render();
    },
};

// Let's goooo!
controller.init();
</code></pre>
<h2 id="mvc">MVC 框架</h2>
<p>JavaScript 越来越受欢迎，近年来还接管了后端。越来越多成熟的 JavaScript 应用选择了 MVC 架构模式。</p>
<p>框架来来去去，但是 MVC 架构模式的概念是不变的。</p>
<p>运用了这些概念的早期框架包括：<strong>KnockoutJS</strong>、<strong>Django</strong>、<strong>Ruby on Rails</strong>。</p>
<h2 id="">总结</h2>
<p>MVC 模式最吸引人的概念是关注点分离。</p>
<p>现代 web 应用非常复杂，有时做一些修改会令人很头疼。</p>
<p>将前端和后端作为独立的小组件来管理，可以使应用更灵活、更易于维护和扩展。</p>
<p><strong>如果你想了解这个 Car Clicker 应用，可以查看<a href="https://github.com/RafaelDavisH/car-clicker/blob/main/README.md">源码</a>或体验<a href="https://rafaeldavish.github.io/car-clicker/">在线版本</a>。</strong></p>
<p>🌟感谢你的阅读！🌟</p>
<!--kg-card-end: markdown--><p>原文：<a href="https://www.freecodecamp.org/news/the-model-view-controller-pattern-mvc-architecture-and-frameworks-explained/">The Model View Controller Pattern – MVC Architecture and Frameworks Explained</a>，作者：<a href="https://www.freecodecamp.org/news/author/rafaeldavish/">Rafael D. Hernandez</a></p> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 如何使用 React 和 React Hooks 创建一个天气应用 ]]>
                </title>
                <description>
                    <![CDATA[ React 是一个很棒的前端框架，可以用来构建用户界面。 使用 React 的优势之一是：我们创建的组件会被封装起来，换句话说，组件是不可见的。 在本文中，我们通过构建一个天气应用来学习 React。 如何安装 Node 和 npm 为了构建 React 应用，我们需要安装 Node 运行时环境，它主要是用来执行 JavaScript 代码。 点击 https://nodejs.org/en/，下载安装包。 还需要安装 npm，它是用 Node 构建的一个包管理器，可以在 JavaScript 应用中用它来安装依赖包。幸运的是，npm  包含在 Node 中，所以不必另外安装它。 安装完成后，打开终端（terminal）或命令提示符（command prompt），输入 node -v  命令，查看当前系统中的 Node 版本。 如何创建 React 应用 要创建 react 应用，可以在终端输入 npx create-react-app <Your app name>  ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/learn-react-by-building-a-weather-app/</link>
                <guid isPermaLink="false">60858ff1045e7705d7011517</guid>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Humilitas ]]>
                </dc:creator>
                <pubDate>Sat, 24 Apr 2021 09:00:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2021/04/Pink-Cute-Chic-Vintage-90s-Virtual-Trivia-Quiz-Presentations--39-.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>React 是一个很棒的前端框架，可以用来构建用户界面。</p>
<p>使用 React 的优势之一是：我们创建的组件会被封装起来，换句话说，组件是不可见的。</p>
<p>在本文中，我们通过构建一个天气应用来学习 React。</p>
<h2 id="nodenpm">如何安装 Node 和 npm</h2>
<p>为了构建 React 应用，我们需要安装 Node 运行时环境，它主要是用来执行 JavaScript 代码。</p>
<p>点击 <a href="https://nodejs.org/en/">https://nodejs.org/en/</a>，下载安装包。</p>
<p>还需要安装 <strong>npm</strong>，它是用 Node 构建的一个包管理器，可以在 JavaScript 应用中用它来安装依赖包。幸运的是，<strong>npm</strong> 包含在 Node 中，所以不必另外安装它。</p>
<p>安装完成后，打开终端（terminal）或命令提示符（command prompt），输入 <code>node -v</code> 命令，查看当前系统中的 Node 版本。</p>
<h2 id="react">如何创建 React 应用</h2>
<p>要创建 react 应用，可以在终端输入 <strong><code>npx create-react-app &lt;Your app name&gt;</code></strong> 命令（此例中命令为 <strong><code>npx create-react-app my-weather-app</code></strong>）。</p>
<p>可以看到相关依赖包正在自动安装。</p>
<p>依赖包安装完成之后，进入项目根目录（通过 <code>cd</code> 命令），执行 <strong><code>npm start</code></strong> 命令。</p>
<p>可以看到默认的 React 模板页面：</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/03/Screenshot-2021-03-12-12-07-22.png" alt="Screenshot-2021-03-12-12-07-22" width="600" height="400" loading="lazy"></p>
<p>默认的 React 代码模板：</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/03/Screenshot-2021-03-12-12-08-28.png" alt="Screenshot-2021-03-12-12-08-28" width="600" height="400" loading="lazy"></p>
<div style="text-align: center; margin-bottom: 2em;">App.js</div>
<p>我们不需要这些，所以要清理一些无用代码。</p>
<p>在 <strong>app.js</strong> 中，删除 <code>div</code> 标签内的内容，删除导入 logo 的代码。</p>
<p>完成之后，页面内容会变成空白。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/03/Screenshot-2021-03-12-12-12-25.png" alt="Screenshot-2021-03-12-12-12-25" width="600" height="400" loading="lazy"></p>
<div style="text-align: center; margin-bottom: 2em;">清理之后的 app.js</div>
<h2 id="">如何安装我们需要的依赖包</h2>
<p>为了让这个应用更有吸引力，我们需要安装一些外部依赖包。</p>
<p>我们需要用到 <a href="https://react.semantic-ui.com/usage/">Semantic React UI</a> 库，执行以下命令来安装：</p>
<pre><code class="language-bash">npm install semantic-ui-react semantic-ui-css
</code></pre>
<p>安装完成后，在 <strong>index.js</strong> 中引入这个库，将以下代码复制到 <strong>index.js</strong> 中即可：</p>
<pre><code>import 'semantic-ui-css/semantic.min.css'
</code></pre>
<p>还需要安装 <a href="https://momentjs.com/">moment.js</a> 来格式化时间，执行以下命令：</p>
<pre><code>npm install moment --save
</code></pre>
<p>可以查看 package.json 文件来了解安装了哪些包：</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/03/Screenshot-2021-03-12-12-21-01.png" alt="Screenshot-2021-03-12-12-21-01" width="600" height="400" loading="lazy"></p>
<div style="text-align: center; margin-bottom: 2em;">package.json</div>
<p>在这里，可以看到目前安装的所有依赖包。</p>
<h2 id="">如何创建我们的天气应用</h2>
<p>为了让我们的天气应用正常工作，需要使用 OpenWeatherMap 提供的 API 来获取天气信息。</p>
<p>访问 <a href="https://home.openweathermap.org/users/sign_up">https://home.openweathermap.org/users/sign_up</a> 页面，创建自己的账号。</p>
<p>完成之后，点击导航栏上的 API 选项，可以看到不同类型的天气数据，如当前天气、未来 4 天逐小时预报、未来 16 天预报等。这些就是用来获取天气信息的 API 端点。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/03/Screenshot-2021-03-12-12-30-10.png" alt="Screenshot-2021-03-12-12-30-10" width="600" height="400" loading="lazy"></p>
<p>调用这些 API 需要用到 API key，点击右上角的用户名，接着点击 “My API Keys”，可以查看自己的 API key。</p>
<p>如果没有的话就创建一个。</p>
<p>在项目根目录新建一个 <strong>.env</strong> 文件。</p>
<p>这是一个环境变量文件，保存所有 API 端点和 key 信息。</p>
<pre><code>REACT_APP_API_URL = 'https://api.openweathermap.org/data/2.5'
REACT_APP_API_KEY = 【把你的 API key 粘贴在这里】
REACT_APP_ICON_URL = 'https://openweathermap.org/img/w'
</code></pre>
<p>把你的 API key 粘贴在 REACT_APP_API_KEY 变量中。</p>
<h2 id="reacthooks">如何使用 React Hooks</h2>
<p>React Hooks 让我们能够在函数组件中使用、管理 state。</p>
<p>我们会用到 <code>useState</code> 和 <code>useEffect</code> 两个 hook，在顶部引入它们：</p>
<pre><code>import React, { useEffect, useState } from "react";
</code></pre>
<p>创建两个 state，一个是纬度（lat），一个是经度（long）。</p>
<pre><code>const [lat, setLat] = useState([]);
const [long, setLong] = useState([]);
</code></pre>
<p>创建一个 <code>useEffect</code> 函数，在应用加载及刷新时会执行它。</p>
<pre><code>useEffect(() =&gt; {
    navigator.geolocation.getCurrentPosition(function(position) {
      setLat(position.coords.latitude);
      setLong(position.coords.longitude);
    });

    console.log("Latitude is:", lat)
    console.log("Longitude is:", long)
  }, [lat, long]);
</code></pre>
<p>使用 <code>navigator.geolocation</code> 获取纬度和经度，并使用 <strong>setLong</strong> 和 <strong>setLat</strong> 来设置纬度和经度 state。</p>
<pre><code>import './App.css';
import React, { useEffect, useState } from "react";
export default function App() {

  const [lat, setLat] = useState([]);
  const [long, setLong] = useState([]);

  useEffect(() =&gt; {
    navigator.geolocation.getCurrentPosition(function(position) {
      setLat(position.coords.latitude);
      setLong(position.coords.longitude);
    });

    console.log("Latitude is:", lat)
    console.log("Longitude is:", long)
  }, [lat, long]);

  return (
    &lt;div className="App"&gt;

    &lt;/div&gt;
  );
}

</code></pre>
<div style="text-align: center; margin-bottom: 2em;">app.js</div>
<p>现在 app.js 的内容如上。可以在浏览器控制台中查看纬度和经度的值：</p>
<pre><code>Latitude is: 25.5922166
Longitude is: 85.12761069999999
</code></pre>
<div style="text-align: center; margin-bottom: 2em;">纬度和经度值</div>
<h2 id="">如何利用纬度和经度来获取当前位置的天气</h2>
<p>创建 <strong>getWeather</strong> 函数，基于我们的纬度和经度从 API 中获取天气数据。</p>
<p>这个函数中，使用 fetch 方法调用 API 获取天气数据。<strong>process.env.REACT_APP_API_URL</strong> 和 <strong>process.env.REACT_APP_API_KEY</strong> 分别获取了 <strong>.env</strong> 文件中配置的 API 地址和 API key，lat 和 long 是之前获取到的纬度和经度。</p>
<p>接着将数据转换为 <strong>JSON</strong> 格式。</p>
<p>再接着，使用 <strong>setData</strong> 将结果存储在 <strong>data</strong> 对象中。</p>
<pre><code>await fetch(`${process.env.REACT_APP_API_URL}/weather/?lat=${lat}&amp;lon=${long}&amp;units=metric&amp;APPID=${process.env.REACT_APP_API_KEY}`)
      .then(res =&gt; res.json())
      .then(result =&gt; {
        setData(result)
        console.log(result);
      });
</code></pre>
<p>然后把数据打印在控制台：</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/03/Screenshot-2021-03-12-13-36-26.png" alt="Screenshot-2021-03-12-13-36-26" width="600" height="400" loading="lazy"></p>
<p>现在可以看到基于纬度和经度获取到的天气数据。</p>
<p>现在，app.js 文件的内容如下：</p>
<pre><code>import './App.css';
import React, { useEffect, useState } from "react";
import Weather from './components/weather';
export default function App() {

  const [lat, setLat] = useState([]);
  const [long, setLong] = useState([]);
  const [data, setData] = useState([]);

  useEffect(() =&gt; {
    const fetchData = async () =&gt; {
      navigator.geolocation.getCurrentPosition(function(position) {
        setLat(position.coords.latitude);
        setLong(position.coords.longitude);
      });

      await fetch(`${process.env.REACT_APP_API_URL}/weather/?lat=${lat}&amp;lon=${long}&amp;units=metric&amp;APPID=${process.env.REACT_APP_API_KEY}`)
      .then(res =&gt; res.json())
      .then(result =&gt; {
        setData(result)
        console.log(result);
      });
    }
    fetchData();
  }, [lat,long])

  return (
    &lt;div className="App"&gt;

    &lt;/div&gt;
  );
}

</code></pre>
<div style="text-align: center; margin-bottom: 2em;">app.js</div>
<h3 id="">如何创建天气组件</h3>
<p>创建展示天气数据的组件。</p>
<p>在 src 文件夹中，创建 <strong>components</strong> 文件夹，并在其中创建 <strong>weather.js</strong> 文件。</p>
<p>在 <strong>app.js</strong> 中使用天气组件：</p>
<pre><code>import './App.css';
import React, { useEffect, useState } from "react";
import Weather from './components/weather';
export default function App() {

  const [lat, setLat] = useState([]);
  const [long, setLong] = useState([]);
  const [data, setData] = useState([]);

  useEffect(() =&gt; {
    const fetchData = async () =&gt; {
      navigator.geolocation.getCurrentPosition(function(position) {
        setLat(position.coords.latitude);
        setLong(position.coords.longitude);
      });

      await fetch(`${process.env.REACT_APP_API_URL}/weather/?lat=${lat}&amp;lon=${long}&amp;units=metric&amp;APPID=${process.env.REACT_APP_API_KEY}`)
      .then(res =&gt; res.json())
      .then(result =&gt; {
        setData(result)
        console.log(result);
      });
    }
    fetchData();
  }, [lat,long])

  return (
    &lt;div className="App"&gt;
      {(typeof data.main != 'undefined') ? (
        &lt;Weather weatherData={data}/&gt;
      ): (
        &lt;div&gt;&lt;/div&gt;
      )}

    &lt;/div&gt;
  );
}

</code></pre>
<div style="text-align: center; margin-bottom: 2em;">在 app.js 文件中引入天气组件（Weather）</div>
<p>我在 return 语句加了一个判断，如果 data.main 的值为 undefined 则展示一个空的 div。因为 fetch 函数是异步的，所以必须加入这个检查。所有其他函数（除了异步的 fetch 函数）执行完毕之后，就会执行这个 return 语句，如果没有这个判断就会报错：</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/03/Screenshot-2021-03-13-05-19-29-1.png" alt="Screenshot-2021-03-13-05-19-29-1" width="600" height="400" loading="lazy"></p>
<p>这是由于我们的应用在 API 调用完成之前渲染了 return 语句中返回的内容，而此时没有数据可以展示，所以抛出了 undefined 错误。</p>
<p>关于 async/await 的更多信息，可以查看<a href="https://www.freecodecamp.org/news/async-await-in-javascript/">这篇文章</a>。</p>
<h3 id="">如何创建天气组件的主体部分</h3>
<p>这个部分我们将会使用 Semantic UI 来设计界面。（译注：Semantic UI 使用了严格模式下已经弃用的 findDOMNode() 方法，会在控制台抛出警告，不必理会。)</p>
<p>创建展示天气信息的卡片：</p>
<pre><code>import React from 'react';
import './styles.css';
import { Card } from 'semantic-ui-react'

const CardExampleCard = ({weatherData}) =&gt; (
  &lt;Card&gt;
    &lt;Card.Content&gt;
        &lt;Card.Header className="header"&gt;{weatherData.name}&lt;/Card.Header&gt;
    &lt;/Card.Content&gt;
  &lt;/Card&gt;
)

export default CardExampleCard;
</code></pre>
<div style="text-align: center; margin-bottom: 2em;">Weather.js</div>
<p>我们引入了并使用了 semantic-ui-react 的 Card 组件，在卡片中还有一个 Header 用来展示当前城市。</p>
<p>现在问题是，怎么把 app.js 中的数据传给 weather.js 组件？</p>
<p>答案很简单，可以使用 props 从父组件向子组件传递数据。本例中，父组件是 app.js，子组件是 weather.js。</p>
<p>只要在 <strong>app.js</strong> 中使用 Weather 组件的地方加上 props 即可：</p>
<pre><code>&lt;Weather weatherData={data}/&gt;
</code></pre>
<p>我们在这里通过名为 weatherData 的 props 传入数据，就可以在 <strong>weather.js.</strong> 中接收到。</p>
<pre><code>import React from 'react';
import './styles.css';
import { Card } from 'semantic-ui-react'

const CardExampleCard = ({weatherData}) =&gt; (
  &lt;Card&gt;
    &lt;Card.Content&gt;
        &lt;Card.Header className="header"&gt;{weatherData.name}&lt;/Card.Header&gt;
    &lt;/Card.Content&gt;
  &lt;/Card&gt;
)

export default CardExampleCard;
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/03/Screenshot-2021-03-12-17-36-56.png" alt="Screenshot-2021-03-12-17-36-56" width="600" height="400" loading="lazy"></p>
<p>可以看到，我们基于位置获取到了城市名。</p>
<p>同样的，我们可以为天气组件加入更多字段：</p>
<pre><code>import React from 'react';
import './styles.css';
import { Card } from 'semantic-ui-react'

const CardExampleCard = ({weatherData}) =&gt; (
  &lt;Card&gt;
    &lt;Card.Content&gt;
        &lt;Card.Header className="header"&gt;City Name: {weatherData.name}&lt;/Card.Header&gt;
        &lt;p&gt;Temprature: {weatherData.main.temp}&lt;/p&gt;
        &lt;p&gt;Sunrise: {weatherData.sys.sunrise}&lt;/p&gt;
        &lt;p&gt;Sunset: {weatherData.sys.sunset}&lt;/p&gt;
        &lt;p&gt;Description: {weatherData.weather[0].description}&lt;/p&gt;
    &lt;/Card.Content&gt;
  &lt;/Card&gt;
)

export default CardExampleCard;
</code></pre>
<p>我们可以通过 API 获取温度、日出时间、日落时间以及描述信息。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/03/Screenshot-2021-03-12-17-45-36-1.png" alt="Screenshot-2021-03-12-17-45-36-1" width="600" height="400" loading="lazy"></p>
<p>可以任意添加其他字段，比如湿度、风速、能见度等。</p>
<h3 id="">如何格式化数据和日期</h3>
<p>格式化数据，使其更易读。我们会增加一些字段。</p>
<p>首先，为温度加上单位，在温度后面加上 <strong>°C</strong>。</p>
<p>并把日出日落时间转换为本地时间。</p>
<pre><code>import React from 'react';
import './styles.css';
import { Card } from 'semantic-ui-react'

const CardExampleCard = ({weatherData}) =&gt; (
  &lt;Card&gt;
    &lt;Card.Content&gt;
        &lt;Card.Header className="header"&gt;City Name: {weatherData.name}&lt;/Card.Header&gt;
        &lt;p&gt;Temprature: {weatherData.main.temp} &amp;deg;C&lt;/p&gt;
        &lt;p&gt;Sunrise: {new Date(weatherData.sys.sunrise * 1000).toLocaleTimeString('en-IN')}&lt;/p&gt;
        &lt;p&gt;Sunset: {new Date(weatherData.sys.sunset * 1000).toLocaleTimeString('en-IN')}&lt;/p&gt;
        &lt;p&gt;Description: {weatherData.weather[0].main}&lt;/p&gt;
        &lt;p&gt;Humidity: {weatherData.main.humidity} %&lt;/p&gt;
    &lt;/Card.Content&gt;
  &lt;/Card&gt;
)

export default CardExampleCard;
</code></pre>
<p>使用 <strong>moment.js.</strong> 计算出当天的礼拜日期和公历日期：</p>
<pre><code>import moment from 'moment';

&lt;p&gt;Day: {moment().format('dddd')}&lt;/p&gt;
&lt;p&gt;Date: {moment().format('LL')}&lt;/p&gt;
</code></pre>
<div style="text-align: center; margin-bottom: 2em;">使用 moment.js</div>
<p>在顶部引入 <strong>moment</strong> 包，并分别展示当天的礼拜日期和公历日期。这个包的好处在于，它会自动更新公历日期和礼拜日期。</p>
<p>现在 <strong>weather.js</strong> 内容如下：</p>
<pre><code>import React from 'react';
import './styles.css';
import { Card } from 'semantic-ui-react';
import moment from 'moment';

const CardExampleCard = ({weatherData}) =&gt; (
  &lt;Card&gt;
    &lt;Card.Content&gt;
        &lt;Card.Header className="header"&gt;City Name: {weatherData.name}&lt;/Card.Header&gt;
        &lt;p&gt;Temprature: {weatherData.main.temp} &amp;deg;C&lt;/p&gt;
        &lt;p&gt;Sunrise: {new Date(weatherData.sys.sunrise * 1000).toLocaleTimeString('en-IN')}&lt;/p&gt;
        &lt;p&gt;Sunset: {new Date(weatherData.sys.sunset * 1000).toLocaleTimeString('en-IN')}&lt;/p&gt;
        &lt;p&gt;Description: {weatherData.weather[0].main}&lt;/p&gt;
        &lt;p&gt;Humidity: {weatherData.main.humidity} %&lt;/p&gt;
        &lt;p&gt;Day: {moment().format('dddd')}&lt;/p&gt;
        &lt;p&gt;Date: {moment().format('LL')}&lt;/p&gt;
    &lt;/Card.Content&gt;
  &lt;/Card&gt;
)

export default CardExampleCard;
</code></pre>
<div style="text-align: center; margin-bottom: 2em;">weather.js</div>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/03/Screenshot-2021-03-13-12-16-14.png" alt="Screenshot-2021-03-13-12-16-14" width="600" height="400" loading="lazy"></p>
<p>上图是现在的页面效果。</p>
<h2 id="">加上一些样式</h2>
<p>现在已经得到了所有数据，我们加一些样式来让它更有吸引力。</p>
<p>首先，让卡片变大一点、改变 border-radius、加入更酷的字体和颜色、删除文本对齐（译注：文中 css 代码没有涉及文本对齐）。</p>
<pre><code>import React from 'react';
import './styles.css';
import moment from 'moment';

const CardExampleCard = ({weatherData}) =&gt; (
  &lt;div className="main"&gt;
      &lt;p className="header"&gt;{weatherData.name}&lt;/p&gt;
      &lt;div&gt;
        &lt;p className="day"&gt;Day: {moment().format('dddd')}&lt;/p&gt;
      &lt;/div&gt;

      &lt;div&gt;
        &lt;p className="temp"&gt;Temprature: {weatherData.main.temp} &amp;deg;C&lt;/p&gt;
      &lt;/div&gt;

  &lt;/div&gt;
)

export default CardExampleCard;
</code></pre>
<div style="text-align: center; margin-bottom: 2em;">weather.js</div>
<pre><code>@import url('https://fonts.googleapis.com/css2?family=Recursive&amp;display=swap');

.main{
    width: 700px;
    border-radius: 15px;
    background-color: #01579b;
}

.header{
    background-color: #424242;
    color: whitesmoke;
    padding: 10px;
    font-size: 28px;
    border-radius: 15px;
    font-family: 'Recursive', sans-serif;
}

.day{
    padding: 15px;
    color: whitesmoke;
    font-family: 'Recursive', sans-serif;
    font-size: 24px;
    font-weight: 600;
}

.temp{
    padding: 15px;
    color: whitesmoke;
    font-family: 'Recursive', sans-serif;
    font-size: 18px;
}
</code></pre>
<div style="text-align: center; margin-bottom: 2em;">styles.css</div>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/03/Screenshot-2021-03-13-12-48-03.png" alt="Screenshot-2021-03-13-12-48-03" width="600" height="400" loading="lazy"></p>
<p>现在我们的应用效果如上。</p>
<p>使用 <strong>flexbox</strong> 来排列数据：</p>
<pre><code>&lt;div className="flex"&gt;
   &lt;p className="day"&gt;Day: {moment().format('dddd')}&lt;/p&gt;
&lt;/div&gt;

&lt;div className="flex"&gt;
   &lt;p className="temp"&gt;Temprature: {weatherData.main.temp} &amp;deg;C&lt;/p&gt;
&lt;/div&gt;
</code></pre>
<p>给 div 元素加上“flex”类，并在 <em><strong>styles.css.</strong></em> 中加入以下样式：</p>
<pre><code>.flex{
    display: flex;
    justify-content: space-between;
}
</code></pre>
<p>现在 weather.js 内容如下：</p>
<pre><code>import React from 'react';
import './styles.css';
import moment from 'moment';

const CardExampleCard = ({weatherData}) =&gt; (
  &lt;div className="main"&gt;
      &lt;p className="header"&gt;{weatherData.name}&lt;/p&gt;
      &lt;div className="flex"&gt;
        &lt;p className="day"&gt;Day: {moment().format('dddd')}&lt;/p&gt;
        &lt;p className="day"&gt;{moment().format('LL')}&lt;/p&gt;
      &lt;/div&gt;

      &lt;div className="flex"&gt;
        &lt;p className="temp"&gt;Temprature: {weatherData.main.temp} &amp;deg;C&lt;/p&gt;
        &lt;p className="temp"&gt;Humidity: {weatherData.main.humidity} %&lt;/p&gt;
      &lt;/div&gt;

  &lt;/div&gt;
)

export default CardExampleCard;
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/03/Screenshot-2021-03-13-12-56-27.png" alt="Screenshot-2021-03-13-12-56-27" width="600" height="400" loading="lazy"></p>
<p>同样的，加入剩下的字段：</p>
<pre><code>import React from 'react';
import './styles.css';
import moment from 'moment';

const WeatherCard = ({weatherData}) =&gt; (
  &lt;div className="main"&gt;
      &lt;p className="header"&gt;{weatherData.name}&lt;/p&gt;
      &lt;div className="flex"&gt;
        &lt;p className="day"&gt;{moment().format('dddd')}, &lt;span&gt;{moment().format('LL')}&lt;/span&gt;&lt;/p&gt;
        &lt;p className="description"&gt;{weatherData.weather[0].main}&lt;/p&gt;
      &lt;/div&gt;

      &lt;div className="flex"&gt;
        &lt;p className="temp"&gt;Temprature: {weatherData.main.temp} &amp;deg;C&lt;/p&gt;
        &lt;p className="temp"&gt;Humidity: {weatherData.main.humidity} %&lt;/p&gt;
      &lt;/div&gt;

      &lt;div className="flex"&gt;
        &lt;p className="sunrise-sunset"&gt;Sunrise: {new Date(weatherData.sys.sunrise * 1000).toLocaleTimeString('en-IN')}&lt;/p&gt;
        &lt;p className="sunrise-sunset"&gt;Sunset: {new Date(weatherData.sys.sunset * 1000).toLocaleTimeString('en-IN')}&lt;/p&gt;
      &lt;/div&gt;

  &lt;/div&gt;
)

export default WeatherCard;
</code></pre>
<div style="text-align: center; margin-bottom: 2em;">weather.js</div>
<pre><code>@import url('https://fonts.googleapis.com/css2?family=Recursive&amp;display=swap');

.main{
    width: 700px;
    border-radius: 20px;
    background-color: #01579b;
}

.top{
    height: 60px;
    background-color: #424242;
    color: whitesmoke;
    padding: 10px;
    border-radius: 20px 20px 0 0;
    font-family: 'Recursive', sans-serif;
    display: flex;
    justify-content: space-between;
}

.header{
    background-color: #424242;
    color: whitesmoke;
    margin: 10px 0px 0px 10px;
    font-size: 25px;
    border-radius: 20px 20px 0 0;
    font-family: 'Recursive', sans-serif;
}

.day{
    padding: 15px;
    color: whitesmoke;
    font-family: 'Recursive', sans-serif;
    font-size: 24px;
    font-weight: 600;
}

.temp{
    padding: 15px;
    color: whitesmoke;
    font-family: 'Recursive', sans-serif;
    font-size: 18px;
}

.flex{
    display: flex;
    justify-content: space-between;
}

.sunrise-sunset{
    padding: 15px;
    color: whitesmoke;
    font-family: 'Recursive', sans-serif;
    font-size: 16px;
}

.description{
    padding: 15px;
    color: whitesmoke;
    font-family: 'Recursive', sans-serif;
    font-size: 24px;
    font-weight: 600;
}
</code></pre>
<div style="text-align: center; margin-bottom: 2em;">styles.css</div>
<p>现在我们的应用效果如下：</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/03/Screenshot-2021-03-13-13-37-46.png" alt="Screenshot-2021-03-13-13-37-46" width="600" height="400" loading="lazy"></p>
<h3 id="">如何加入刷新按钮</h3>
<p>在页面顶部增加一个刷新按钮：</p>
<pre><code>import React from 'react';
import './styles.css';
import moment from 'moment';
import { Button } from 'semantic-ui-react';

const refresh = () =&gt; {
  window.location.reload();
}

const WeatherCard = ({weatherData}) =&gt; (
  &lt;div className="main"&gt;

      &lt;div className="top"&gt;
        &lt;p className="header"&gt;{weatherData.name}&lt;/p&gt;
        &lt;Button className="button" inverted color='blue' circular icon='refresh' onClick={refresh} /&gt;
      &lt;/div&gt;
      &lt;div className="flex"&gt;
        &lt;p className="day"&gt;{moment().format('dddd')}, &lt;span&gt;{moment().format('LL')}&lt;/span&gt;&lt;/p&gt;
        &lt;p className="description"&gt;{weatherData.weather[0].main}&lt;/p&gt;
      &lt;/div&gt;

      &lt;div className="flex"&gt;
        &lt;p className="temp"&gt;Temprature: {weatherData.main.temp} &amp;deg;C&lt;/p&gt;
        &lt;p className="temp"&gt;Humidity: {weatherData.main.humidity} %&lt;/p&gt;
      &lt;/div&gt;

      &lt;div className="flex"&gt;
        &lt;p className="sunrise-sunset"&gt;Sunrise: {new Date(weatherData.sys.sunrise * 1000).toLocaleTimeString('en-IN')}&lt;/p&gt;
        &lt;p className="sunrise-sunset"&gt;Sunset: {new Date(weatherData.sys.sunset * 1000).toLocaleTimeString('en-IN')}&lt;/p&gt;
      &lt;/div&gt;

  &lt;/div&gt;
)

export default WeatherCard;
</code></pre>
<div style="text-align: center; margin-bottom: 2em;">weather.js</div>
<pre><code>.button{
    width: 35px;
    height: 35px;
}
</code></pre>
<div style="text-align: center; margin-bottom: 2em;">styles.css</div>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/03/Screenshot-2021-03-13-13-51-37.png" alt="Screenshot-2021-03-13-13-51-37" width="600" height="400" loading="lazy"></p>
<p>可以看到，页面上多了一个刷新按钮，点击它就会触发 refresh 函数、刷新页面。</p>
<h3 id="">如何加入页面加载动画</h3>
<p>加入加载动画，增强应用的用户体验。</p>
<p>引入 Semantic UI 的 Loader 组件，并在数据尚未加载完成的情况下显示：</p>
<pre><code>import { Dimmer, Loader } from 'semantic-ui-react';

&lt;div className="App"&gt;
      {(typeof data.main != 'undefined') ? (
        &lt;Weather weatherData={data}/&gt;
      ): (
        &lt;div&gt;
          &lt;Dimmer active&gt;
            &lt;Loader&gt;Loading..&lt;/Loader&gt;
          &lt;/Dimmer&gt;
       &lt;/div&gt;
     )}
 &lt;/div&gt;
</code></pre>
<div style="text-align: center; margin-bottom: 2em;">app.js</div>
<h2 id="">回顾一下</h2>
<p>我们创建了一个基于地理位置展示当前天气信息的 React 应用。</p>
<p>一起回顾一下我们所做的东西。</p>
<h3 id="stateprops">我们学习了 State 和 Props</h3>
<p>State 和 Props 是 React 提供的非常强大的特性，可以用来管理数据、控制不同组件之间的数据流。</p>
<p>在我们的应用中，使用 State 管理应用程序的状态，比如城市名称、温度、日期、湿度等。这些数据因人而异，取决于用户所处的位置。</p>
<p>另一方面，使用 Props 在不同组件之间传递数据。我们在 <strong>app.js</strong> 中获取天气数据，又在 <strong>weather.js</strong> 中读取这些数据。记住，使用 props 只能从父组件向子组件传递数据。</p>
<h3 id="reacthooks">我们使用了 React Hooks</h3>
<p>如果使用过类组件（class component），那么你肯定了解生命周期方法（life-cycle methods）。如果没用过的话，可以把它们理解为一些在页面渲染或重新渲染是执行的方法。但是我们不能在函数组件（functional component）中使用生命周期方法，因为它们是专门为类组件设计的。</p>
<p>所以，使用 React Hooks 来替代。在我们的应用中用到了两个 hook，一个是用来管理应用 state 的 useState，另一个是用来在页面渲染或加载时执行一些任务的 useEffect。</p>
<h3 id="semanticui">我们尝试了 Semantic UI</h3>
<p>Semantic UI 是为 React 设计的库，它提供了许多出色的组件。</p>
<p>朋友们，以上就是本文的全部内容了。你可以试着为这个应用添加更多特性，比如未来 5 天预报、图标说明等。</p>
<p>想要进一步了解的话，可以查看<a href="https://github.com/nishant-666/React-weather">项目代码</a>。</p>
<blockquote>
<p>不断尝试，快乐学习。</p>
</blockquote>
<!--kg-card-end: markdown--><p>原文：<a href="https://www.freecodecamp.org/news/learn-react-by-building-a-weather-app/">How to Build a Weather Application with React and React Hooks</a>，作者：<a href="https://www.freecodecamp.org/news/author/nishant-kumar/">Nishant Kumar</a></p> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ DOM 完全指南 ]]>
                </title>
                <description>
                    <![CDATA[ 想必你已经听说过全能的 DOM 了——所以你才会在这里看这篇文章，对吧？如果你觉得它有点难，我可以保证，你在读完这篇文章之后就能够掌握操作 DOM 的相关知识。 开始之前，请允许我分享一个关于我是如何了解到 DOM 的趣事。 我是如何了解到 DOM 的 做了几个月的 web 开发之后，我还在学习古老的 HTML 和 CSS。我偶然地发现了 w3schools 的一个 DOM 课程，其中第一个示例是关于一个灯泡和两个按钮。 点击其中一个按钮会“打开”灯泡，点击另一个按钮则会“关闭”灯泡。我真的被搞晕了。 网站上的按钮为什么可以打开灯泡？怎么回事！？ 我甚至为此发了个 Twitter。然后我发现他们不过是改变了图片元素的 src 属性，我心都碎了。不过无论如何，这事让我爱上了 DOM，它激发了我的求知欲。 如果你耐心读完本文，并动手实践其中涉及的示例，我保证 DOM 操作再也难不倒你。准备好了吗？开始吧！ > 为了便于理解，我把相关知识归纳为以下几个章节。  * DOM 的定义及基本概念  * 如何选中 DOM 中的元素  * 如何遍历及移动 DOM 中的元素  * 如何 ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/how-to-manipulate-the-dom-beginners-guide/</link>
                <guid isPermaLink="false">601e8c5d6183a705401563d4</guid>
                
                    <category>
                        <![CDATA[ DOM ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Humilitas ]]>
                </dc:creator>
                <pubDate>Sat, 17 Apr 2021 07:21:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2021/02/photo-1581291518857-4e27b48ff24e.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>想必你已经听说过全能的 DOM 了——所以你才会在这里看这篇文章，对吧？如果你觉得它有点难，我可以保证，你在读完这篇文章之后就能够掌握操作 DOM 的相关知识。</p>
<p>开始之前，请允许我分享一个关于我是如何了解到 DOM 的趣事。</p>
<h2 id="dom">我是如何了解到 DOM 的</h2>
<p>做了几个月的 web 开发之后，我还在学习古老的 HTML 和 CSS。我偶然地发现了 w3schools 的一个 DOM 课程，其中第一个示例是关于一个灯泡和两个按钮。</p>
<p>点击其中一个按钮会“打开”灯泡，点击另一个按钮则会“关闭”灯泡。我真的被搞晕了。</p>
<p>网站上的按钮为什么可以打开灯泡？怎么回事！？</p>
<p>我甚至为此发了个 Twitter。然后我发现他们不过是改变了图片元素的 src 属性，我心都碎了。不过无论如何，这事让我爱上了 DOM，它激发了我的求知欲。</p>
<p>如果你耐心读完本文，并动手实践其中涉及的示例，我保证 DOM 操作再也难不倒你。准备好了吗？开始吧！</p>
<blockquote>
<p>为了便于理解，我把相关知识归纳为以下几个章节。</p>
</blockquote>
<ul>
<li>DOM 的定义及基本概念</li>
<li>如何选中 DOM 中的元素</li>
<li>如何遍历及移动 DOM 中的元素</li>
<li>如何编辑 DOM 中的元素</li>
<li>指定样式</li>
<li>DOM 的事件处理</li>
</ul>
<p>喝杯咖啡放松一下，跟着我一起学习下面的章节。</p>
<p><img src="https://media.giphy.com/media/ceeFbVxiZzMBi/source.gif" alt="vvv-1" width="600" height="400" loading="lazy"></p>
<h2 id="dom">DOM 的定义及基本概念</h2>
<h3 id="dom">什么是 DOM？</h3>
<p>DOM（Document Object Model——文档对象模型），可以简单理解为浏览器创建的节点树，每个节点有自己的属性和方法，可以通过 JavaScript 来操作这些属性、调用这些方法。</p>
<p>操作 DOM 的能力是 JavaScript 最独特和有用的特性之一。</p>
<p>下图是 DOM 树的视觉表示。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/01/images.png" alt="images" width="600" height="400" loading="lazy"></p>
<p>document 对象是 DOM 的核心/基础，执行任何形式的 DOM 操作都要访问 document 对象。</p>
<p>根元素 <code>html</code> 是 document 对象的子节点。</p>
<p>下一行是 <code>body</code> 和 <code>head</code> 元素，它们互为兄弟节点，并且都是 <code>html</code> 的子节点。</p>
<p>head 元素下面有一个 title 元素，它是 head 元素的子节点，同时也是文本节点“my text”的父节点。</p>
<p>body 元素下面有两个元素（<code>a</code> 元素和 <code>h1</code> 元素），它们互为兄弟节点，并且都是 <code>body</code> 元素的子节点。</p>
<p>最后，<code>href</code> 属性和文本节点“my link”都是 <code>a</code> 元素的子节点，同样的，文本节点“My header”是 <code>h1</code> 元素的子节点。</p>
<p>这些对于新手来说可能看起来有些复杂，不过请相信我——熟能生巧。</p>
<h2 id="dom">如何选中 DOM 中的元素</h2>
<p>为了操作 DOM 中的元素，需要先选中指定的元素，幸好，我们有四种常用方式可以用来选中元素。</p>
<h3 id="getelementbyid">getElementById() 方法</h3>
<p>最常见的方式是通过 id 属性来选中元素。</p>
<p>下面的示例中，<code>getElementById()</code> 方法通过 id="master" 来查找元素。</p>
<pre><code class="language-javascript">&lt;p id="master"&gt;i love javascript&lt;/p&gt;

&lt;script&gt;
	const masterEl = document.getElementById('master')
	console.log(masterEl) //&lt;p id="master"&gt;i love javascript&lt;/p&gt; 
&lt;/script&gt;
</code></pre>
<p>id 是区分大小写的，比如：“master”和“Master”就是两个完全不同的 id。</p>
<p>选中了一个元素之后，就可以给它添加一些样式、编辑它的属性、遍历它的父元素或子元素。</p>
<h3 id="getelementsbyclassname">getElementsByClassName() 方法</h3>
<p>这个方法返回文档中所有类名（class）包含指定值的元素的集合。</p>
<p>例如，下面的 HTML 页面中有三个 class="master2" 的元素，我通过“btn”这个 id 选中了其中的按钮。</p>
<p>点击这个按钮将会选中所有类名包含“master2”的元素，并改变其中第三个元素的 <code>innerHTML</code> 属性值。</p>
<pre><code class="language-javascript">&lt;p class="master2"&gt;i love javascript&lt;/p&gt;
&lt;p class="master2"&gt;i love react&lt;/p&gt;
&lt;h1 class="master2"&gt;i want a job&lt;/h1&gt;

&lt;button id="btn"&gt;click me&lt;/button&gt;

&lt;script&gt;
	const btn = document.getElementById('btn')

	btn.addEventListener('click', function master(){
	   var master = document.getElementsByClassName("master2");
	   master[2].innerHTML = 'i need a job';
	})
&lt;/script&gt;
</code></pre>
<p>点击按钮前：</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/01/22.png" alt="22" width="600" height="400" loading="lazy"></p>
<p>点击按钮后：</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/01/444.png" alt="444" width="600" height="400" loading="lazy"></p>
<blockquote>
<p>这里用到了 <code>addEventListener()</code> 方法，稍后会介绍。</p>
</blockquote>
<h3 id="getelementsbytagname">getElementsByTagName() 方法</h3>
<p>这个方法接收 html 标签名作为参数，根据在文档中出现的先后顺序返回这个标签的所有元素。</p>
<p>下面的代码展示了 <code>getElementsByTagName()</code> 的语法，获取页面中的所有 <code>p</code> 元素、改变第二个 <code>p</code> 元素的内容。</p>
<pre><code class="language-javascript">&lt;p&gt;VsCode&lt;/p&gt;
&lt;p&gt;Atom&lt;/p&gt;
&lt;p&gt;Sublime text&lt;/p&gt;
&lt;button id="btn"&gt;click me&lt;/button&gt;

&lt;script&gt;
	const btn = document.getElementById('btn')

	btn.addEventListener('click', function master(){
		let master = document.getElementsByTagName('p');
		let masterEl = master[1].innerHTML = 'Code editors';
		console.log(masterEl) //Code editors
	})

	//&lt;p&gt;Atom&lt;/p&gt; changes to &lt;p&gt;Code editors&lt;/p&gt;
&lt;/script&gt;
</code></pre>
<h3 id="css">使用 CSS 选择器</h3>
<h4 id="queryselector">.querySelector()</h4>
<p>这个方法返回第一个匹配指定选择器的元素，它可以接收所有 CSS 样式选择器作为参数，它可以通过标签名、类名或 ID 来选择元素。</p>
<pre><code class="language-javascript">&lt;div id=master&gt;i am a frontend developer&lt;/div&gt;

&lt;script&gt;
	const master = document.querySelector("#master") 
&lt;/script&gt;
</code></pre>
<p>上面的方法接收一个 CSS 选择器作为参数，返回匹配这个选择器的第一个元素。</p>
<h4 id="queryselectorall">.querySelectorAll()</h4>
<p>这个方法跟上一个方法类似，区别在于它返回的是包含所有匹配元素的节点集合。</p>
<pre><code class="language-javascript">&lt;p class="master"&gt;React&lt;/p&gt;
&lt;p class="master"&gt;Vue&lt;/p&gt;
&lt;p class="master"&gt;Angular&lt;/p&gt;

&lt;script&gt;
	const master = document.querySelectorAll(".master") 
	console.log(master[1])  //&lt;p class="master"&gt;Vue&lt;/p&gt;
&lt;/script&gt;
</code></pre>
<h3 id="dom">选择 DOM 元素的方法总结</h3>
<p>在需要选择 DOM 元素的时候，你有四种不同的方式可以选择，这四种不同方式做的都是同一件事（选择一个或多个元素）。</p>
<p>如果不记得第一种方式，可以用第二种，如果碰巧前两种都忘了，还有第三种、第四种方式可以用。是不是我或者 JavaScript 让我们的生活变得更简单了？:)</p>
<p>我个人建议尽量使用第一种方式或第四种方式中的第一个方法（queryselector）。你之前学习 HTML 的时候应该知道，文档中元素的 id 是不允许重复的，也就是说 id 是文档中元素的唯一标识符。</p>
<p>考虑到这一点，通过 id 来选择元素是“安全”的做法，因为这样你就不会把同样的操作应用在多个不同元素上了（除非你刻意要这样做——当然了，只要你想的话，也可以选择其他方式）。</p>
<h2 id="document">如何遍历 Document</h2>
<p>现在你应该会认同我的说法，即 HTML 文档中的一切都是节点。就连 HTML 元素中的文字也是文本节点。</p>
<p>在 DOM 中，可以利用前面讲到的节点关系（父节点、子节点、兄弟节点等）来遍历节点树、访问节点。</p>
<blockquote>
<p>可以创建新节点，并且所有节点都可以被修改或删除。</p>
</blockquote>
<h3 id="">回顾一下</h3>
<ul>
<li>每个节点都有且只有一个父节点，根节点除外（它没有父节点）。</li>
<li>一个节点可以有多个子节点。</li>
<li>同一个父节点下的子节点互为兄弟节点。</li>
</ul>
<p>在这个章节中，我们要学习如何获取元素的父节点、兄弟节点以及子节点。为此，我会用到下面这些节点属性。</p>
<ul>
<li>parentNode</li>
<li>childrenNodes</li>
<li>firstElementChild</li>
<li>lastElementChild</li>
<li>nextElementSibling</li>
<li>previousElementSibling</li>
</ul>
<p>我会使用下面的 HTML 页面来展示这些节点属性的用法。在第四章节中，我会演示如何操作 DOM。</p>
<p>这就是本文的目的——了解如何操作 DOM。如果不懂如何操作 DOM 的话，了解了选择元素和遍历 DOM 的方法也没什么用处。了解如何为元素添加 CSS 样式、如何创建并追加元素、如何设置 innerHTML 以及如何做事件处理至关重要。</p>
<p>这是本文的重点，请跟着我的脚步，继续前进。</p>
<pre><code class="language-javascript">&lt;div id="parent"&gt;
	&lt;div id="firstchild"&gt;i am a first child&lt;/div&gt;
	&lt;p id="secondchild"&gt;i am the second child&lt;/p&gt;
	&lt;h4&gt;i am alive&lt;/h4&gt;
	&lt;h1&gt;hello world&lt;/h1&gt;
	&lt;p&gt;i am the last child&lt;/p&gt;
&lt;/div&gt;  

const parent = document.getElementById('parent').lastElementChild
console.log(parent) //&lt;p&gt;i am the last child&lt;/p&gt;

const parent2 = document.getElementById('parent').children[3]
console.log(parent2) //&lt;h1&gt;hello world&lt;/h1&gt;

const secondchild = document.getElementById('secondchild')
console.log(secondchild) //&lt;p id="secondchild"&gt;i am the second child&lt;/p&gt;

console.log(secondchild.parentNode) //&lt;div id="parent"&gt;...&lt;/div&gt;

console.log(secondchild.nextElementSibling) //&lt;h4&gt;i am alive&lt;/h4&gt;

console.log(secondchild.previousElementSibling) //&lt;div id="firstchild"&gt;i am a first child&lt;/div&gt;
</code></pre>
<h3 id="">创建元素</h3>
<p>上面代码中的 HTML 片段中有一个包含 5 个子元素的父元素，如果想使用 JavaScript 往其中添加一个 <code>div</code> 元素，首先需要调用 <code>createElement()</code> 方法来创建一个新的元素：</p>
<pre><code class="language-javascript">const createEl = document.createElement('div')
console.log(createEl) //&lt;div&gt;&lt;/div&gt;
</code></pre>
<h3 id="innerhtml">设置 innerHTML</h3>
<p>我们成功创建了一个 <code>div</code> 标签，不过现在其中还没有文本节点，调用 <code>.innerHTML()</code> 方法来为它添加文本节点。</p>
<pre><code class="language-javascript">const innerhtml = createEl.innerHTML = 'i am a frontend developer'
console.log(createEl) //&lt;div&gt;​i am a frontend developer​&lt;/div&gt;​
</code></pre>
<h3 id="">在页面中追加元素</h3>
<p>目前已经完成了创建元素以及往其中插入文本节点的操作，但是创建出的新元素还没有插入到 DOM 树中。</p>
<p>现在演示如何将创建出的新元素追加到 HTML 页面中，代码如下：</p>
<pre><code class="language-javascript">const createEl = document.createElement('div')
const innerhtml = createEl.innerHTML = 'i am a frontend developer'
const parentEl = document.getElementById('parent')
parentEl.appendChild(createEl)
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/01/Document---Google-Chrome-16_01_2021-11_50_14-PM--2-.png" alt="Document---Google-Chrome-16_01_2021-11_50_14-PM--2-" width="600" height="400" loading="lazy"></p>
<h3 id="">在指定元素之前插入元素</h3>
<p>注意上面的控制台打印信息截图，追加的 <code>div</code> 标签子元素自动地插入到了父元素的最下面。</p>
<p>如果出于某种原因想要把元素追加到其它位置该怎么办？也许是第一个元素之前或者是第四个元素之前的位置。这些情况都是很有可能出现的。下方代码把元素插入到当前第一个子元素之前。</p>
<p>我们会用到 <code>insertBefore()</code> 方法，它接收两个参数：<code>document.insertBefore(newNode, existingNode)</code>。</p>
<pre><code class="language-javascript">const parentEl = document.getElementById('parent')
const firstchildEl = document.getElementById('firstchild')

const createEl = document.createElement('div')

const innerhtml = createEl.innerHTML = 'i am a frontend developer'

parentEl.insertBefore(createEl, firstchildEl)
console.log(parentEl)
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/01/mmm.png" alt="mmm" width="600" height="400" loading="lazy"></p>
<h3 id="">替换子元素</h3>
<p>我们使用 <code>replaceChild()</code> 方法将第一个子元素替换为新创建的元素，它接收两个参数：<code>document.replaceChild(newNode, existingNode)</code>。</p>
<pre><code class="language-javascript">const firstchildEl = document.getElementById('firstchild')
const parentEl = document.getElementById('parent')

const createEl = document.createElement('div')
const innerhtml = createEl.innerHTML = 'i am a frontend developer'

parentEl.replaceChild(createEl, firstchildEl)

console.log(parentEl)                
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/01/kkk.png" alt="kkk" width="600" height="400" loading="lazy"></p>
<h3 id="">移除子元素</h3>
<p>我们使用 <code>removeChild()</code> 方法来移除指定元素（这里指的是第一个子元素），它接收一个参数：<code>document.removeChild(element)</code>。</p>
<pre><code class="language-javascript">const firstchildEl = document.getElementById('firstchild')
const parentEl = document.getElementById('parent')

parentEl.removeChild(firstchildEl)

console.log(parentEl)
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/01/vvv.png" alt="vvv" width="600" height="400" loading="lazy"></p>
<h2 id="css">为元素添加 CSS 样式</h2>
<p>前面的示例中演示了如何创建元素，并将其追加到指定的父元素。</p>
<p>为了设定元素样式，我们需要为它添加 CSS 类，这里使用 JavaScript 来操作。</p>
<p>我将为你演示如何添加、移除以及切换 CSS 类。</p>
<p>别担心，这并不难，我会为你详细介绍。</p>
<h3 id="css">添加 CSS 类</h3>
<p>现在有一个普通的 HTML 按钮，它的 id 为“master”、没有应用额外样式，如下图：</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/01/ttt.png" alt="ttt" width="600" height="400" loading="lazy"></p>
<p>第一步，为这个按钮创建一个 CSS 样式。</p>
<p>接着，在 JavaScript 代码部分为这个按钮添加一个事件监听器：点击按钮时为这个按钮元素添加“button”类样式。</p>
<pre><code class="language-javascript">&lt;style&gt;
	body{
		 background-color: hotpink;
		 display: flex;
		 align-items: center;
	}

	.button{
		 background-color: blueviolet;
		 width: 200px;
		 border: none;
		 font-size: 2rem;
		 padding: 0.5rem;
		 border-radius: 5px;
		 cursor: pointer;
	}
&lt;/style&gt;


&lt;button id="master"&gt;Click me&lt;/button&gt;


const buttonEl = document.getElementById('master')
buttonEl.addEventListener('click', addFunction)

function addFunction(){
	buttonEl.classList.add('button')
}
</code></pre>
<p>点击按钮之后，可以看到效果如下，好看吧？</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/01/jjj.png" alt="jjj" width="600" height="400" loading="lazy"></p>
<h3 id="css">移除 CSS 类</h3>
<p>还是使用上面的示例，我们要移除 CSS 类，这次会使用 <code>classList.remove()</code> 方法。也许你已经猜到结果了，对吧？</p>
<p>没错，这个按钮会变回默认样式。</p>
<pre><code class="language-javascript">const buttonEl = document.getElementById('master')
buttonEl.addEventListener('click', addFunction)

function addFunction(){
	buttonEl.classList.remove('button')
}
</code></pre>
<h3 id="css">切换 CSS 类</h3>
<p>如果不想完全移除这个 CSS 样式，你可能希望能有一种方式让按钮的样式在默认样式和指定样式之间来回切换。</p>
<p><code>classList.toggle()</code> 方法为这种操作提供了支持。</p>
<p>像 Twitter 之类的社交媒体平台上通常会用到 <code>classList.toggle()</code> 方法，它使得用户可以通过点击按钮为一个帖子点赞，也可以随时点击这个按钮来取消点赞。</p>
<p>可以通过 JavaScript 代码来检查按钮是否包含指定的 CSS 类。</p>
<p>如果按钮包含这个类，点击按钮会将这个类移除，反之亦然。</p>
<pre><code class="language-javascript">const buttonEl = document.getElementById('master')
buttonEl.addEventListener('click', addFunction)

function addFunction(){
    buttonEl.classList.toggle('button')
}
</code></pre>
<h1 id="">事件处理</h1>
<h3 id="html">什么是 HTML 事件？</h3>
<p>它是发生在 HTML 元素上的一些“事件”，比如按钮点击、文本输入等等。当事件触发时，就会执行对应的称为事件处理程序的 JavaScript 代码。</p>
<p>这些事件处理程序都是 JavaScript 函数，当元素触发某个事件时，事件处理器函数就会执行。</p>
<h3 id="">事件监听器</h3>
<p>到目前为止，我们基本上在之前的每个示例中都用到了事件监听器，可见它在操作 DOM 的过程中是多么重要。</p>
<p>为了给一个元素或者 DOM 对象添加事件监听器，需要用到 <code>addEventListener()</code> 方法。使用这个方法要优于以前在 html 标签中绑定事件监听器的做法。</p>
<p>这样能让 JavaScript 和 html 分离，使得代码更简洁、可读性更强。</p>
<p>我很认同将各类代码分离的做法，如果你的想法和我一样，那你应该会更喜欢这种绑定事件监听器的方式。</p>
<p>事件监听器接收三个参数。</p>
<ul>
<li>第一个参数是事件类型，如“click”。</li>
<li>第二个参数是将要执行的函数。</li>
<li>第三个参数是一个布尔值，指定使用事件冒泡还是事件捕获。 <strong>此参数是可选的</strong></li>
</ul>
<blockquote>
<p>可以为一个元素添加多种事件的处理程序。</p>
</blockquote>
<blockquote>
<p>也可以为一个元素的一种事件添加多个事件处理程序，比如给“click”事件添加两个事件处理程序。</p>
</blockquote>
<h2 id="">总结</h2>
<p>学会如何使用 JavaScript 操作 DOM 是非常重要的，这是必备的知识。</p>
<p>如果你理解了文中的示例和插图，可以自己构建一个小项目试试。如果想要成为优秀的开发者，动手实践至关重要。</p>
<p><img src="https://media.giphy.com/media/mVJ5xyiYkC3Vm/source.gif" alt="kkk-1" width="600" height="400" loading="lazy"></p>
<!--kg-card-end: markdown--><p>原文：<a href="https://www.freecodecamp.org/news/how-to-manipulate-the-dom-beginners-guide/">How to Manipulate the DOM - the Ultimate Beginner's Guide</a>，作者：<a href="https://www.freecodecamp.org/news/author/chibuike-okere/">Chibuike Okere</a></p> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ JavaScript 函数式编程入门指南 ]]>
                </title>
                <description>
                    <![CDATA[ 函数式编程并不是一种新的编程方法，但是这种方法近年来越来越流行。 这是因为，一旦程序员理解了这个技术背后的基础知识（并能够使用它写出整洁可靠的代码），就能够使用函数式编程编写出更易于使用的应用。 因此，当你学完 JavaScript beginners’ handbook [https://www.freecodecamp.org/news/the-complete-javascript-handbook-f26b2c71719c/]   之后，有必要进一步学习函数式编程。 如果你经常和 JavaScript 打交道，使用这种编程方式能为你节省开发时间，还能让你的代码更易读，也许还能提升代码安全性。 在本文中，我们将会探讨函数式编程的基本原理，然后介绍一些在 JavaScript 中使用这种编程方式的关键工具。 命令式编程 vs. 函数式编程 函数式编程的起源可以追溯到 20 世纪 30 年代 Lambda 运算的发明。 这是一种寻求定义通用任务 [https://en.wikipedia.org/wiki/Lambda_calculus] 的计算方法，这种函数不修改目标数 ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/functional-programming-in-javascript-for-beginners/</link>
                <guid isPermaLink="false">6073fe9b7a8acf0586ec8c5d</guid>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ 函数式编程 ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Humilitas ]]>
                </dc:creator>
                <pubDate>Mon, 12 Apr 2021 08:00:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2021/04/photo-1482745637430-91c0bbcea3e1-1.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>函数式编程并不是一种新的编程方法，但是这种方法近年来越来越流行。</p>
<p>这是因为，一旦程序员理解了这个技术背后的基础知识（并能够使用它写出整洁可靠的代码），就能够使用函数式编程编写出更易于使用的应用。</p>
<p>因此，当你学完 <a href="https://www.freecodecamp.org/news/the-complete-javascript-handbook-f26b2c71719c/">JavaScript beginners’ handbook</a> 之后，有必要进一步学习函数式编程。</p>
<p>如果你经常和 JavaScript 打交道，使用这种编程方式能为你节省开发时间，还能让你的代码更易读，也许还能提升代码安全性。</p>
<p>在本文中，我们将会探讨函数式编程的基本原理，然后介绍一些在 JavaScript 中使用这种编程方式的关键工具。</p>
<h2 id="vs">命令式编程 vs. 函数式编程</h2>
<p>函数式编程的起源可以追溯到 20 世纪 30 年代 Lambda 运算的发明。</p>
<p>这是一种<a href="https://en.wikipedia.org/wiki/Lambda_calculus">寻求定义通用任务</a>的计算方法，这种函数不修改目标数据（如数组、列表）的结构而是在其上执行数学运算。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/02/image-144.png" alt="image-144" width="600" height="400" loading="lazy"></p>
<p><a href="https://android.jlelse.eu/how-to-wrap-your-imperative-brain-around-functional-reactive-programming-in-rxjava-91ac89a4eccf">图片来源</a></p>
<p>可能听起来很抽象，尤其是对于编程初学者而言。但事实上，函数式编程和命令式编程之间的区别可以用一个例子来简明扼要地解释。看如下示例：</p>
<h3 id="">命令式：</h3>
<pre><code class="language-js">const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];

function getOdds(arr) {
  let odds = [];
  for (let i = 0; i &lt; arr.length + 1; i++) {
    if (i % 2 !== 0) {
      odds.push(i);
    }
  }
  return odds;
}

console.log(getOdds(arr)); // logs [1, 3, 5, 7, 9]

</code></pre>
<h3 id="">函数式：</h3>
<pre><code class="language-js">function getOdds2(arr){
return arr.filter(num =&gt; num % 2 !== 0)
}
console.log(getOdds2(arr))
// logs [ 1, 3, 5, 7, 9 ]

const getOdds3 = arr =&gt; arr.filter(num =&gt; num % 2 !== 0)
console.log(getOdds3(arr))
// logs [ 1, 3, 5, 7, 9 ]
</code></pre>
<p>如你所见，两种方法工作方式完全不同。</p>
<p>命令式方法定义一个数据结构，操作数据以获得我们需要的输出。函数式方法则使用 filter 函数定义一个程序化的函数，再按需调用。</p>
<p>当然，<a href="https://www.freecodecamp.org/news/an-introduction-to-the-basic-principles-of-functional-programming-a2c2a15c84/">函数式编程运作</a>的大部分复杂性对终端用户是隐藏的，对使用前端开发框架的程序员也是隐藏的。</p>
<p>即使仅从这个例子来看，函数式编程的优势就已经很明显了——它使得代码更简洁、更易读、更易理解、更易修改。</p>
<h2 id="">为什么使用函数式编程</h2>
<p>除了这些基本的优势之外，使用函数式编程还有许多其他优势。</p>
<p>许多优势源于一个简单的事实——函数式代码比指令式代码更易读。人们可以很轻易地看出函数式程序是如何工作的，而不用通过拆分来理解代码，这使得许多测试都被简化了。</p>
<h3 id="">函数式编程通过渗透测试确保代码完整性</h3>
<p>在代码是人类可读的情况下，渗透测试会变得更加有效。这使得评估函数式代码的完整性更加容易。</p>
<p>根据 <a href="https://www.clouddefense.ai/blog/penetration-testing">Cloud Defense</a> 的软件开发者 Barbara Ericson 的说法，JavaScript 应用总是应该进行渗透测试，并且函数式编程能使其更加严谨。</p>
<p>这种易读性也简化了开发工作的管理流程。</p>
<p>使用函数式编程，合规流程也变得更加简单，因为程序员不用过多担心他们代码的执行细节，意味着程序中处理敏感数据的部分可以独立于其他部分进行运算。</p>
<h3 id="">函数式编程让代码更易读</h3>
<p>然而函数式编程的优势不局限于评估代码，它还有助于开发。</p>
<p>事实上，函数式编程利用并放大了 JavaScript 自身的<a href="https://www.freecodecamp.org/news/the-advantages-and-disadvantages-of-javascript/">优点和缺点</a>。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/02/image-145.png" alt="image-145" width="600" height="400" loading="lazy"></p>
<p><a href="https://itnext.io/why-are-we-creating-a-javascript-only-world-wide-web-db8c3a340b9">图片来源</a></p>
<p>代码更易读了，就可以让更多人加入开发流程，即使他们并未完全掌握 JavaScript。</p>
<p>这是 DevOps 方法的一个关键原则，可以帮助减少 JavaScript 代码中的<a href="https://privacycanada.net/how-to-fight-common-java-security-vulnerabilities-from-devops/">漏洞</a>，这也是函数式编程带来的好处之一。</p>
<h2 id="">函数式编程的关键工具</h2>
<p>实际应用函数式编程之前需要了解一些关键工具和概念，我们一起看看。</p>
<h3 id="1">1. 纯函数与非纯函数</h3>
<p>在最基本的层面来看，函数式方法<a href="https://www.geeksforgeeks.org/functional-programming-paradigm/">意图操作数据</a>而不改变它们，这意味着一个“函数式函数”接收数据、执行一些计算并返回结果（在此过程中不对源数据本身做任何改动）。</p>
<p>这样的函数就叫做“纯函数”，此外其它函数则叫做“非纯函数”。</p>
<pre><code class="language-js">
function getSquare(items) {
  var len = items.length;
  for (var i = 0; i &lt; len; i++) {
    items[i] = items[i] * items[i];
  }
  return items;
}
</code></pre>
<p>这里的总体思想就是完全不影响源数据。</p>
<p>如果想要合并两个数组，不应该使用 <code>Array.prototype.push()</code>（这会覆盖源数据），而应该使用 <code>Array.prototype.concat()</code> 方法，它会创建并返回一个新数组供你使用。</p>
<h3 id="2">2. 匿名函数</h3>
<p><a href="https://www.javascripttutorial.net/javascript-anonymous-functions/">匿名函数</a>也是函数式编程的重要组成部分，它也是源于 Lambda 运算。</p>
<p>匿名函数，顾名思义，没有显式定义的函数名，它们是赋值给变量的函数，并通过变量来调用。</p>
<pre><code class="language-js"> alert((function(x) {
    return !(x &gt; 1)
      ? 1
      : arguments.callee(x - 1) * x;
  })(20));
</code></pre>
<p>这样的好处是可以很容易地通过变量调用其指向的函数、通过变量在模块之间传递这些函数，为我们提供了一种强大且灵活的函数使用新方式。</p>
<h3 id="3">3. 递归函数</h3>
<p>函数式编程的另一个标志是递归函数的使用。即使是编程新手也能熟知递归的一般概念，函数式编程通过定义调用自身的函数将这一概念的运用更进一步。</p>
<pre><code class="language-js">function countDown(fromNumber) {
    console.log(fromNumber);

    let nextNumber = fromNumber - 1;

    if (nextNumber &gt; 0) {
        countDown(nextNumber);
    }
}
countDown(3);
</code></pre>
<p>这使得递归的实现更加简单——主要是由于程序员不再需要使用循环来实现。</p>
<p>然而，它也是有风险的。具体来说，定义调用自身的函数更可能意外地造成死循环，所以务必为递归函数定义严格的终止条件。</p>
<h2 id="">总结</h2>
<p>这三个都是函数式编程的典型概念，事实上这个范式的应用范围意味着它更像是一种哲学而不是一套设计好的工具和流程。</p>
<p>进入函数式编程的精彩世界，你会发现它的影响无处不在。事实上，它为如今许多<a href="https://www.freecodecamp.org/news/what-is-javascript/">常见 JavaScript 实践</a>提供了参考。</p>
<p>换句话说，虽然函数式编程表面看起来很简单，却对编码方式有着深远影响。正因如此，即使并不常用，它依然值得学习。</p>
<!--kg-card-end: markdown--><p>原文：<a href="https://www.freecodecamp.org/news/functional-programming-in-javascript-for-beginners/">Functional Programming in JavaScript for Beginners</a>，作者：<a href="https://www.freecodecamp.org/news/author/nahla/">Nahla Davies</a></p> ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
