<?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[ TechQuery - 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[ TechQuery - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/chinese/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Thu, 11 Jun 2026 04:55:02 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/chinese/news/author/techquery/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ 响应式网页设计——怎样让一个网站在手机、平板上好看 ]]>
                </title>
                <description>
                    <![CDATA[ 在联网设备快速发展的格局中，响应式网页设计在Web开发中仍然至关重要。 不久前，“响应式网页设计”一词尚不存在。但是今天，我们大多数人不得不在某种程度上采用它。 据统计 [https://www.statista.com/statistics/275814/mobile-share-of-organic-search-engine-visits/] , 截至 2019 年，61% 的 Google 搜索访问来自移动设备。在 2020 年 9 月，Google 将改变其搜索算法 [https://webmasters.googleblog.com/2020/03/announcing-mobile-first-indexing-for.html] ，让其优先展示移动端友好的网站。 在这篇文章中，我将介绍以下内容：  * 响应式网页设计是什么  * 视口元标签及其作用  * 响应式网页设计中适应手机和平板电脑的有效技术  * 帮助模拟和监控手机和平板电脑用户体验的工具 响应式网页设计是什么？（RWD） 响应式 Web 设计是一种专注于一个网站用户环境的方法，而用户环境则取决于他们 ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/responsive-web-design-how-to-make-a-website-look-good-on-phones-and-tablets/</link>
                <guid isPermaLink="false">65f11989823801041102dea6</guid>
                
                    <category>
                        <![CDATA[ 响应式设计 ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ TechQuery ]]>
                </dc:creator>
                <pubDate>Wed, 13 Mar 2024 09:57:53 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2024/03/curve-design-futuristic-lines-911738.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>原文：</strong> <a href="https://www.freecodecamp.org/news/responsive-web-design-how-to-make-a-website-look-good-on-phones-and-tablets/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">Responsive Web Design – How to Make a Website Look Good on Phones and Tablets</a>
      </p><!--kg-card-begin: markdown--><p>在联网设备快速发展的格局中，响应式网页设计在Web开发中仍然至关重要。</p>
<p>不久前，“响应式网页设计”一词尚不存在。但是今天，我们大多数人不得不在某种程度上采用它。</p>
<p><a href="https://www.statista.com/statistics/275814/mobile-share-of-organic-search-engine-visits/">据统计</a>, 截至 2019 年，61% 的 Google 搜索访问来自移动设备。在 2020 年 9 月，<a href="https://webmasters.googleblog.com/2020/03/announcing-mobile-first-indexing-for.html">Google 将改变其搜索算法</a>，让其优先展示移动端友好的网站。</p>
<p><strong>在这篇文章中，我将介绍以下内容：</strong></p>
<ul>
<li>响应式网页设计是什么</li>
<li>视口元标签及其作用</li>
<li>响应式网页设计中适应手机和平板电脑的有效技术</li>
<li>帮助模拟和监控手机和平板电脑用户体验的工具</li>
</ul>
<h2 id="rwd">响应式网页设计是什么？（RWD）</h2>
<p>响应式 Web 设计是一种专注于一个网站用户环境的方法，而用户环境则取决于他们连接互联网的设备。</p>
<p>有很多设备特性有助于实现以用户为中心，例如：</p>
<ul>
<li>网络连接</li>
<li>屏幕尺寸</li>
<li>交互类型（触屏、触控板）</li>
<li>图像分辨率</li>
</ul>
<p>在响应式 Web 设计流行前，很多公司管理着一个完全独立的网站，用于接收基于用户代理标识的流量导向。</p>
<p>但在响应式 Web 设计中，服务器总是给所有设备发送相同的 HTML 代码，而 CSS 则用于改变页面在设备上的渲染。</p>
<p>无论采用上述哪种策略，为手机或平板电脑建站的第一步是确保浏览器知道意图。这就是视口元数据标签发挥作用的地方。</p>
<h2 id="">识别一个移动端网站的视口元数据标签</h2>
<p>视口元数据标签指示浏览器如何调整页面以适应每种设备的宽度。</p>
<p>当视口元数据标签缺省，移动端浏览器将以默认桌面端设置来显示网页。 这导致了看似缩小的结果、非响应式的体验。</p>
<p>以下是一种标准实现：</p>
<pre><code class="language-html">&lt;meta name="viewport" content="width=device-width,initial-scale=1" /&gt;
</code></pre>
<p>视口元数据标签示例</p>
<p>现在浏览器知道发生了什么，我们可以利用流行的技术来让我们的网站具备响应式能力。🙌</p>
<h2 id="css">针对不同屏幕尺寸和方向的 CSS 媒体查询</h2>
<p>如果你初试响应式 Web 设计，媒体查询是要学的首要 CSS 特性。媒体查询让你能根据视口宽度设置元素样式。一种流行的 CSS 策略是优先编写移动端样式，再在它们之上构建更复杂的桌面专用样式。</p>
<p>媒体查询是响应式 Web 设计的一个重要部分，通常用于处理网格布局、字体字号、外内边距在屏幕尺寸和方向下的差异。</p>
<p>下面是一个移动优先的样式常见用例示例，其中一列在较小的设备上宽度为 100%，但在较大的视口上宽度为 50%。</p>
<pre><code class="language-css">.column {
    width: 100%;
}

@media (min-width: 600px) {
  .column {
    width: 50%;
  }
}
</code></pre>
<p>移动优先 CSS 示例</p>
<p>以上代码是一个简单示例，但它实际在做的是非常有趣的。</p>
<ol>
<li>在考虑移动优先时，<code>column</code> 元素被设置一个 100% 的宽度；</li>
<li>通过使用 <code>min-width</code> 媒体查询，我们专门为最小宽度 <code>600px</code> 的视口（视口宽于 <code>600px</code>）定义了规则。所以，宽于 <code>600px</code> 的视口，我们的 <code>column</code> 元素将有父级 50% 的宽度。</li>
</ol>
<p>尽管媒体查询对于响应式 Web 设计至关重要，很多其它新的 CSS 特性也在浏览器中被广泛采用与支持。Flexbox 便是其中之一，在响应式 Web 设计中是重要的 CSS 特性。</p>
<h2 id="flexbox">什么是 Flexbox？</h2>
<p>你可能好奇 —— “Flexbox 是干啥的？” 更好的问题是 —— “Flexbox 不能干啥？” 最简单的 CSS 垂直居中方法是啥？Flexbox。你怎样创建一个响应式网格布局？Flexbox。我们怎么实现世界和平？Flexbox。</p>
<p>Flexbox 布局（弹性盒子）模块提供了一种更有效的方式去布局、对齐和在容器项目间分配空间，即使它们的尺寸是动态的（因此就有了 “flex” 这个词）。</p>
<p>在以下示例中，我们结合如上所述的媒体查询来创建一个响应式网格。</p>
<pre><code class="language-html">&lt;style&gt;
  main {
    background: #d9d7d5;
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
  }
  div {
    background: #767775;
    flex-basis: 100%;
    height: 100px;
    margin-bottom: 0.5rem;
  }
  @media (min-width: 600px) {
    main {
      flex-wrap: nowrap;
    }
    div {
      flex-basis: 33%;
    }
  }
&lt;/style&gt;
&lt;main&gt;
  &lt;div&gt;&lt;/div&gt;
  &lt;div&gt;&lt;/div&gt;
  &lt;div&gt;&lt;/div&gt;
&lt;/main&gt;
</code></pre>
<p>CSS flexbox 示例</p>
<p>我们用这段代码完成了以下事情：</p>
<ol>
<li>在我们的 <code>main</code> 容器元素中用 <code>display: flex</code> 建立一个 flexbox 布局。</li>
<li>先设置移动端样式。我们给 <code>main</code> 元素设置 <code>flex-wrap: wrap</code>，它允许子元素在我们的 flexbox 布局中换行，如下图 1 所示。我们在我们的 <code>div</code> 元素设置 <code>flex-basis: 100%</code> 来确保它们在 flexbox 布局中占满父级宽度的 100%（图 1）。</li>
<li>再设置更大设备（如平板、桌面电脑）的样式。我们利用一条与我们上节示例类似的媒体查询来设置我们的容器 <code>main</code> 元素为 <code>flex-wrap: nowrap</code>。这样确保子元素不换行，并且它们在行类型的布局中维持一列。通过在媒体查询中设置 <code>div</code> 为 <code>flex-basis: 33%</code> —— 我们创建了一些为父级宽度 33% 的列。</li>
<li>在这个例子中，魔法将在我们结合媒体查询和 flexbox 规则的更大设备中出现。因为我们定义了 <code>display: flex</code>，并且没在媒体查询中覆盖这条规则，我们就拥有了一个适配手机、平板电脑和桌面电脑的 flexbox 布局。媒体查询 <code>flex-basis: 33%</code> 和继承的 <code>display: flex</code> 规则将给我们一个可识别的 flexbox 布局（如图 2 所示）。在过去，为了实现这种列类型布局，我们需要做一些严肃的繁重工作并写一团乱糟糟的 CSS。</li>
</ol>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2020/07/grid-mobile-1.png" alt="图 1：移动端 flexbox 网格示例容" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>图 1：移动端 flexbox 网格示例</figcaption>
</figure>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2020/07/grid-desktop.png" alt="图 2：桌面端 flexbox 网格示例" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>图 2：桌面端 flexbox 网格示例</figcaption>
</figure>
<p>Flexbox 提供了一种很棒的方法去实现多变而流动的布局。在一些实例中，我们在纵向空间中可能没有太多的自由。我们可能需要给一个元素设一个固定高度。在这种情形下，我们有另一种可用的技术 —— 水平滚动。</p>
<h2 id="overflowscroll">基于 Overflow Scroll 的水平滚动</h2>
<p>也许有一天，你有一些溢出视口的内容，并且没有优雅的处理方法。 瞧这边…… overflow scroll 能救你。🦸</p>
<p>这种技术的常见用处包括可滚动菜单和表格。以下是一个可滚动菜单的示例。</p>
<!--kg-card-end: markdown--><!--kg-card-begin: html--><menu style="margin: auto; max-width: 826px; background: #d9d7d5; padding: 0.25rem; overflow-y: scroll; white-space: nowrap;">
  <span style="background: #767775; color: #ffffff; display: inline-block; margin: 0.25rem; padding: 0.5rem;">Responsive Web Design</span>
  <span style="background: #767775; color: #ffffff; display: inline-block; margin: 0.25rem; padding: 0.5rem;">RWD</span>
  <span style="background: #767775; color: #ffffff; display: inline-block; margin: 0.25rem; padding: 0.5rem;">Responsive menu</span>
  <span style="background: #767775; color: #ffffff; display: inline-block; margin: 0.25rem; padding: 0.5rem;">Overflow scroll example</span>
  <span style="background: #767775; color: #ffffff; display: inline-block; margin: 0.25rem; padding: 0.5rem;">This is a lot of content!</span>
  <span style="background: #767775; color: #ffffff; display: inline-block; margin: 0.25rem; padding: 0.5rem;">Yes</span>
  <span style="background: #767775; color: #ffffff; display: inline-block; margin: 0.25rem; padding: 0.5rem;">we</span>
  <span style="background: #767775; color: #ffffff; display: inline-block; margin: 0.25rem; padding: 0.5rem;">have</span>
  <span style="background: #767775; color: #ffffff; display: inline-block; margin: 0.25rem; padding: 0.5rem;">another</span>
  <span style="background: #767775; color: #ffffff; display: inline-block; margin: 0.25rem; padding: 0.5rem;">item</span>
</menu><!--kg-card-end: html--><!--kg-card-begin: markdown--><pre><code class="language-html">&lt;style&gt;
  menu {
    background: #d9d7d5;
    padding: 0.25rem;
    overflow-y: scroll;
    white-space: nowrap;
  }

  span {
    background: #767775;
    color: #ffffff;
    display: inline-block;
    margin: 0.25rem;
    padding: 0.5rem;
  }
&lt;/style&gt;
&lt;menu&gt;
  &lt;span&gt;响应式 Web 设计&lt;/span&gt;
  &lt;span&gt;RWD&lt;/span&gt;
  &lt;span&gt;响应式菜单&lt;/span&gt;
  &lt;span&gt;Overflow scroll 示例&lt;/span&gt;
  &lt;span&gt;这是一大堆内容！&lt;/span&gt;
  &lt;span&gt;是的&lt;/span&gt;
  &lt;span&gt;我们&lt;/span&gt;
  &lt;span&gt;有&lt;/span&gt;
  &lt;span&gt;另一个&lt;/span&gt;
  &lt;span&gt;项目&lt;/span&gt;
&lt;/menu&gt;
</code></pre>
<p>示例：水平滚动菜单</p>
<p>你是怎么做到的？让我们深入探讨一下。</p>
<ul>
<li><code>overflow-y: scroll</code> 是这个食谱的关键成分。通过指定它的子元素将在水平坐标上以滚动行为溢出。</li>
<li>没那么快！尽管你可能认为 <code>overflow-y</code> 就够了，我们也需要告诉浏览器用 <code>white-space: nowrap</code> 不让子元素换行。🤷</li>
</ul>
<p>现在我们掌握了一些响应式网页设计（RWD）布局技巧，让我们来看看那些因其视觉特性而具有挑战的元素——图片和视频。</p>
<h2 id="">响应式图片</h2>
<p>通过使用现代图片标签属性，我们能适应各类设备和分辨率。以下是一个响应式图片的示例。</p>
<pre><code class="language-html">&lt;style&gt;
  img {
    max-width: 100%;
  }
&lt;/style&gt;

&lt;picture&gt;
  &lt;source type="image/webp" srcset="https://my-image.com/my-image-100.webp 1x, https://my-image.com/my-image-200.webp 2x"&gt;
  &lt;source type="image/png" srcset="https://my-image.com/my-image-100.png 1x, https://my-image.com/my-image-200.png 2x"&gt;
  &lt;img alt="my image" src="https://my-image.com/my-image-200.png" loading="lazy" width="100" height="100"&gt;
&lt;/picture&gt;
</code></pre>
<p>这在做很多事情。让我们来分解一下：</p>
<ol>
<li>通过设置 <code>max-width: 100%</code>，图片会根据其容器宽度来放大或缩小。</li>
<li>通过结合使用 <code>picture</code>、<code>source</code> 和 <code>img</code> 标签，我们实际上只渲染一张图片，而且是只加载那张最适合用户设备的图片。</li>
<li><strong>WebP</strong> 是一种现代图片格式，为 Web 图片提供优越的压缩性。利用 <code>source</code>，我们能为支持它的浏览器采用一张 WebP 图片，而另一个 <code>source</code> 标签则为不支持 WebP 的引用一张 PNG 版图片。</li>
<li><code>srcset</code> 是用来告诉浏览器根据分辨率应该用哪张图片。</li>
<li>我们利用 <code>loading="lazy"</code> 属性值对来启用<a href="https://web.dev/native-lazy-loading/">原生懒加载</a>。</li>
</ol>
<h2 id="">响应式视频</h2>
<p>响应式视频是另一个激发一大批文章和文档的主题。</p>
<p>一个建立响应式图片、视频、iframe 和其它元素的关键策略涉及使用 aspect-ratio。Aspect ratio 盒子不是一个新技术，而且对 Web 开发者来说是非常有用的工具。</p>
<p>关于如何实现“流动”宽度视频，<a href="https://css-tricks.com/fluid-width-video/">这篇文章提供了一个扎实的范例</a>。让我们看一下代码，再分解一下。</p>
<pre><code class="language-html">&lt;style&gt;
  .videoWrapper {
    position: relative;
    padding-bottom: 56.25%; /* 16:9 */
    height: 0;
  }

  .videoWrapper iframe {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
  }
&lt;/style&gt;

&lt;div class="videoWrapper"&gt;
  &lt;!-- 复制、粘贴自 YouTube --&gt;
  &lt;iframe width="560" height="349" src="http://www.youtube.com/embed/n_dZNLr2cME?rel=0&amp;hd=1" frameborder="0" allowfullscreen&gt;&lt;/iframe&gt;
&lt;/div&gt;
</code></pre>
<p>在这个例子中，我们有一个嵌入为 iframe 的 YouTube 视频和一个带有 <code>videoWrapper</code> 类的 <code>div</code> 容器。这段代码做了很多…… 让我们来深挖。</p>
<ul>
<li>在容器元素上的 <code>position: relative</code> 允许子元素利用相对于它的绝对定位。</li>
<li><code>height: 0</code> 结合 <code>padding-bottom: 56.25%</code> 是这里的要点，它建立了一种动态行为，强制 <code>16:9</code> 的纵横比。</li>
<li>设置在 iframe 上的 <code>position: absolute</code>、<code>top: 0</code> 和 <code>left: 0</code> 创建了一种行为，让元素相对父元素绝对定位自己…… 将其固定在左上。</li>
<li>最后，长宽的 100% 让子元素 iframe 占它父元素的 100%。父元素 <code>.videoWrapper</code> 完全掌控这个 aspect ratio 布局的建立。</li>
</ul>
<p>我知道…… 这很多。为了让视频和图片在手机和平板电脑上看起来舒服，我们还有更多可以做的。除此之外，我也鼓励对这些主题的独立研究。</p>
<p>好了，现在我们是响应式 Web 设计的大师了，我们如何测试我们做了什么？幸运的是，我们有很多工具去模拟和监控各种设备上的用户体验。</p>
<h2 id="">模拟和监控响应式网站的工具</h2>
<p>有各种有用的工具去帮助我们创建响应式 Web 设计的网站。以下是我发现的两个特别有用的。</p>
<h3 id="chromedevtools">Chrome DevTools 移动端仿真</h3>
<p>Chrome 的 DevTools 提供各类平板电脑和手机的移动端仿真。它也提供一个“responsive”选项，让你定义一个自定义视口尺寸。</p>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2020/07/Screen-Shot-2020-07-12-at-6.19.18-PM.png" alt="图 3：Chrome DevTools 手机和平板电脑设备仿真" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>图 3：Chrome DevTools 手机和平板电脑设备仿真</figcaption>
</figure>
<h3 id="foo">用 Foo 监控移动端网站性能</h3>
<p>Lighthouse 是一个开源工具，提供一种分析网站设备特定性能的方法。</p>
<p><a href="https://www.foo.software/lighthouse/">Foo 在幕后使用 Lighthouse 来监控网站性能，并提供分析反馈</a>。你可为桌面端和移动端设备同时设置监控，以获得有关你的网站响应性的持续反馈。</p>
<p>比如，一份 Lighthouse 报告将根据设备标注出未正确加载的图片。</p>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2020/07/Screen-Shot-2020-07-12-at-6.31.09-PM.png" alt="图 4：带有移动设备仿真的 Lighthouse 报告" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>图 4：带有移动设备仿真的 Lighthouse 报告</figcaption>
</figure>
<h2 id="">结论</h2>
<p>响应式 Web 设计将继续快速发展，但如果我们保持在当前趋势之上，我们能为我们的用户提供最佳体验。我希望这些工具和技术对您有所帮助！</p>
<p>不但我们网站的用户将受益于一个多功能的设计，同时搜索引擎也将提升我们网页的排名。</p>
<!--kg-card-end: markdown--> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 阿富汗最有爱的编程社区——编程周末 ]]>
                </title>
                <description>
                    <![CDATA[ 当你想到阿富汗时，你可能会想到那场持续 17 年、夺取数千人生命的战争。 但在混乱中，有着 500 万人生活的阿富汗首都喀布尔，一代软件工程师正在重新将它定义为全球技术中心。 每周末，几十个进取的开发者都会在喀布尔附近的办公室一起写代码，并向他人分享他们的知识。 走进“编程周末 CodeWeekend” —— 一个改变喀布尔技术面貌的草根组织 超过 3000 位阿富汗的开发者、准开发者都参与到了编程周末。 编程周末每周都会在喀布尔举办编程活动。 截至目前，他们已举办超过 100 场活动。 一个参会者帮助另一个编码编程周末吸纳了 freeCodeCamp 喀布尔学习小组，它现已成为非营利组织。它专注于帮助喀布尔人改善他们的编程技能，并建立他们的专业网络。 他们通过举办免费编程工作坊、提供交流机会、让技术更容易获得来实现这一点。 > “我们有两种类型的活动。我们每周的活动教新人编程，而月度活动邀请在同一水平的软件工程师和其它专业技术人员，以便他们能分享知识，并提供一个交流机会。”—— “编程周末”组织者 Jawed Mansoor “编程周末”社区竭尽全力尽可能具有包容性， ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/inside-afghanistans-friendliest-coding-club/</link>
                <guid isPermaLink="false">603384dfc354c605689ea655</guid>
                
                    <category>
                        <![CDATA[ 社区运营 ]]>
                    </category>
                
                    <category>
                        <![CDATA[ freeCodeCamp ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ TechQuery ]]>
                </dc:creator>
                <pubDate>Sat, 20 Feb 2021 07:20:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2021/02/1_R3giYmJqpnO9eD2NRrSDMQ-1.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>当你想到阿富汗时，你可能会想到那场持续 17 年、夺取数千人生命的战争。</p><p>但在混乱中，有着 500 万人生活的阿富汗首都喀布尔，一代软件工程师正在重新将它定义为全球技术中心。</p><p>每周末，几十个进取的开发者都会在喀布尔附近的办公室一起写代码，并向他人分享他们的知识。</p><h2 id="-codeweekend-">走进“编程周末 CodeWeekend” —— 一个改变喀布尔技术面貌的草根组织</h2><p>超过 3000 位阿富汗的开发者、准开发者都参与到了编程周末。</p><p>编程周末每周都会在喀布尔举办编程活动。</p><p>截至目前，他们已举办超过 100 场活动。</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://chinese.freecodecamp.org/news/content/images/2021/02/image-12.png" class="kg-image" alt="image-12" width="600" height="400" loading="lazy"><figcaption>一个参会者帮助另一个编码</figcaption></figure><p>编程周末吸纳了 freeCodeCamp 喀布尔学习小组，它现已成为非营利组织。它专注于帮助喀布尔人改善他们的编程技能，并建立他们的专业网络。</p><p>他们通过举办免费编程工作坊、提供交流机会、让技术更容易获得来实现这一点。</p><blockquote>“我们有两种类型的活动。我们每周的活动教新人编程，而月度活动邀请在同一水平的软件工程师和其它专业技术人员，以便他们能分享知识，并提供一个交流机会。”—— “编程周末”组织者 Jawed Mansoor</blockquote><p>“编程周末”社区竭尽全力尽可能具有包容性，它秉持硅谷技术大会的行为准则，鼓励如下行为：</p><ul><li>使用热情和包容的语言</li><li>尊重不同的观点和经验</li><li>优雅地接受建设性批评</li><li>专注于对社区最好的事</li><li>对其他社区成员有同情心</li></ul><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://chinese.freecodecamp.org/news/content/images/2021/02/image-13.png" class="kg-image" alt="image-13" width="600" height="400" loading="lazy"><figcaption>Milad Mehraban 在喀布尔的 Kardan 大学做技术讲座</figcaption></figure><p>“编程周末”分为街坊级的分会，每个分会都有负责人。分会负责人们与当地的公司和组织联络，以保障赞助和活动场地。</p><blockquote>“我们不在活动上花费太多。我们需要一个场地和一些茶点，可能还要一个投影仪。每当想举办活动时，我们都会联系一个私营机构，以便他们为我们提供空间和互联网。我们期望至少有个小房间作为专用场地。”—— “编程周末”导师 Mustafa Ehsan</blockquote><p>每期活动大概 3 小时。首先，老师以一个对主题的概括性介绍开场。然后，每个人开始做 freeCodeCamp.org 的课程。三四个导师会穿梭其间，帮助遇到困难或有问题的人。</p><blockquote>“有些人每周都来，热情高涨。每当我们没办活动时，总有人来问我们为啥不办。他们真的很期待。”—— “编程周末”导师 Mustafa Ehsan</blockquote><p>他们的志愿工作在产生作用，来自“编程周末”社区的人逐渐找到开发者工作。他们社区成员获得的很多工作涉及为阿富汗政府、本地/国际创业公司构建服务。</p><p>比如，他们的团队为人力资源部门和其它后台办公功能开发并维护 Web 应用，并且在此过程中，他们开始使用像 Vue.js 和 Laravel 这样令人兴奋的新工具。</p><h2 id="-freecodecamp-">“编程周末”和 freeCodeCamp 喀布尔社区背后的人们</h2><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://chinese.freecodecamp.org/news/content/images/2021/02/image-14.png" class="kg-image" alt="image-14" width="600" height="400" loading="lazy"><figcaption>freeCodeCamp 喀布尔社区的老师 Mustafa Ehsan 主持了很多“编程周末”活动</figcaption></figure><p><a href="https://twitter.com/jamshidhashimi">Jamshid Hashimi</a> 是“编程周末”创始人，他 13 岁时，在他长大的乡村创立了一个足球俱乐部。</p><p>在 Rumie 和 Alidrivers 等公司工作之前，他继续在土耳其的 Dokuz Eylul 大学学习。</p><p>他对技术和社区营造充满激情，在 2014 年创立了“编程周末 CodeWeekend”，这正是两者的完美融合。</p><p><a href="https://www.linkedin.com/in/shaheennaikpay/">Shaheen Naikpay</a> 不是一个职业开发者，他有教育心理学背景，并作为一个军事工业的项目经理。</p><p>但 Shaheen 说他喜欢贡献社会，“编程周末”是其中很大的一块，他志愿作为“编程周末”的活动管理协调员。</p><p>有些活动是 Mustafa 的雇主 Netlinks 赞助的。Netlinks 的 CEO 积极鼓励 CodeWeekend 在公司大堂办活动。</p><p><a href="https://www.linkedin.com/in/jawed-mansoor-66225b49/">Jawed Mansoor</a> 在印度完成了他的计算机科学学位，并成为一名数据库工程师，在喀布尔帮助组织 CodeWeekend 活动。</p><p><a href="https://www.linkedin.com/in/fazila-nazary-076761134/">Fazila Nazary</a> 是一名经验丰富的数据库开发者和讲师，她是阿富汗另一座城市 Mazar-i Sharif 的 CodeWeekend 理事。</p><p>该组织还拥有很多常规活动的导师，包括 <a href="https://www.linkedin.com/in/mustafaaloko/">Mustafa Ehsan</a>、<a href="https://www.linkedin.com/in/said-jalal-saidi/">Jalal Saidi</a>、<a href="https://www.linkedin.com/in/sediq-khan-738660109/">Sediq Khan</a>、Mohammad Ali Abbasi 和 <a href="https://www.linkedin.com/in/akmal-arzhang-2a226b96/?lipi=urn%3Ali%3Apage%3Ad_flagship3_search_srp_people%3BtGg0uo7MS9qrxyOTLaw4%2BA%3D%3D&amp;licu=urn%3Ali%3Acontrol%3Ad_flagship3_search_srp_people-search_srp_result&amp;lici=ZmzGWSlKQeWRCjDAH2MImw%3D%3D">Akmal Arzhang</a>。</p><p>若你正在阿富汗旅行，可以加入 <a href="https://www.facebook.com/groups/CodeWeekend">CodeWeekend 的 Facebook 群组</a>，参加他们的活动。</p><p>并且，你也可以<a href="https://www.freecodecamp.org/donate/">向 freeCodeCamp.org 做免税捐款</a>，来支持全球数以百万计正在学编程的人们。</p><p>我们可以一起创造更多免费的教育资源，支持 CodeWeekend 这样的社区，帮助他们持续影响当地社区人民的生活。</p><p>原文：<a href="https://www.freecodecamp.org/news/inside-afghanistans-friendliest-coding-club-d553719579e/">Inside Afghanistan’s friendliest coding club</a>，作者：<a href="https://www.freecodecamp.org/news/author/quincylarson/">Quincy Larson</a></p> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 内容型网站后端一把梭 ]]>
                </title>
                <description>
                    <![CDATA[ Web 全栈工程师经常遇到一些内容型网站的开发需求，比如个人博客、机构官网、新闻门户。这些虽是 Web 1.0、2.0 时代就烂大街的典型 Web-site，但面对不同的甲方，看似雷同的需求往往又有各种细节的定制化。 虽然 WordPress、Discuz!X 等建站系统早已非常成熟，但在人们更看重 UI/UX 设计的今天，它们专用的模板引擎 反而成了二次开发最大的障碍。只要是近两年做过Web 前端开发的同学就知道，不能利用基于 Node.js 的现代化工具链的项目，开发起来有多蛋疼。 Web 前后端分离催生了 Web 前端岗位，也让WordPress [https://wordpress.org/]、Ghost [https://ghost.org/] 这些老牌 CMS 系统开放了RESTful API。但有的开源项目做得更加激进 ——Headless CMS [https://github.com/search?q=Headless+CMS&ref=opensearch]，前台界面都省了，只有后端 API 和后台界面 ，普通用户看啥你们自己写~ 这其中最为优秀的便是Strap ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/cms-back-end-setting/</link>
                <guid isPermaLink="false">5ef84f38db4be8080eb70f0e</guid>
                
                    <category>
                        <![CDATA[ 网站开发 ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ TechQuery ]]>
                </dc:creator>
                <pubDate>Sun, 31 Jan 2021 07:20:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2020/06/1593331926583.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p><strong>Web 全栈工程师</strong>经常遇到一些<strong>内容型网站</strong>的开发需求，比如个人博客、机构官网、新闻门户。这些虽是 Web 1.0、2.0 时代就烂大街的典型 Web-site，但面对不同的甲方，看似雷同的需求往往又有各种细节的定制化。</p><p>虽然 WordPress、Discuz!X 等<strong>建站系统</strong>早已非常成熟，但在人们更看重 UI/UX 设计的今天，它们专用的<strong>模板引擎</strong>反而成了二次开发最大的障碍。只要是近两年做过 &nbsp;<strong>Web 前端开发</strong>的同学就知道，不能利用<strong>基于 Node.js 的现代化工具链</strong>的项目，开发起来有多蛋疼。</p><p><strong>Web 前后端分离</strong>催生了 Web 前端岗位，也让 &nbsp;<a href="https://wordpress.org/">WordPress</a>、<a href="https://ghost.org/">Ghost</a> &nbsp;这些老牌 CMS 系统开放了 &nbsp;<strong>RESTful API</strong>。但有的开源项目做得更加激进 —— &nbsp;<a href="https://github.com/search?q=Headless+CMS&amp;ref=opensearch">Headless CMS</a>，前台界面都省了，只有<strong>后端 API</strong> &nbsp;和<strong>后台界面</strong>，普通用户看啥你们自己写~</p><p>这其中最为优秀的便是 &nbsp;<a href="https://strapi.io/">Strapi</a>，因为它的后台设计不局限于传统的博客、门户，而是：</p><ol><li><strong>数据模型</strong>灵活可配置（可用 Git 管理 JSON 配置文件）</li><li>RESTful API 完全支持 &nbsp;<strong>CRUD</strong>（增删查改）和 &nbsp;<strong>RBAC</strong>（角色权限）</li><li>自身是个标准的 &nbsp;<strong>Node.js 后端项目</strong>，并有留有二次开发接口</li><li>包括 &nbsp;<a href="https://swagger.io/">Swagger</a> &nbsp;Documentation 在内的插件生态</li></ol><p>这使得它既像 BaaS 中的 &nbsp;<a href="https://leancloud.cn/">LeanCloud</a>，又像 Python 开发框架 &nbsp;<a href="https://www.djangoproject.com/">Django</a>，在<strong>简单易用</strong>的同时，又不束缚专业开发者。</p><figure class="kg-card kg-image-card"><img src="https://tech-query.me/development/headless-cms-strapi/Strapi-admin.png" class="kg-image" alt="Strapi-admin" width="1920" height="1080" loading="lazy"></figure><h2 id="-">本地开发</h2><h3 id="--1">创建项目</h3><p>以下命令会一键<strong>创建项目骨架</strong>、<strong>安装依赖包</strong>、<strong>启动开发模式</strong>：</p><pre><code class="language-shell">npm init strapi-app ~/my-project --quickstart
</code></pre><h3 id="--2">开发模式启动</h3><p>下次继续开发时，则执行以下命令：</p><pre><code class="language-shell">cd ~/my-project
npm run develop
</code></pre><p>如果执行 &nbsp;<code>npm start</code>，启动的后台系统只能添加数据，不能修改数据结构，是<strong>线上生产</strong>模式。</p><h3 id="--3">创建代码库</h3><p><a href="https://github.com/new">在 GitHub 上创建代码库</a>后，把生成的代码传上去：</p><pre><code class="language-shell">git init
git remote add origin https://github.com/my-id/my-project.git
git add .
git commit -m "[add] Strapi framework"
git push
</code></pre><h2 id="--4">服务器部署</h2><p>下面以 Linux (Ubuntu 18.04) 为例，简介线上部署。</p><h3 id="--5">安装容器环境</h3><pre><code class="language-shell"># 安装 Docker
curl -fsSL https://get.docker.com | sh
# 安装 Python pip
apt install python-pip
# 安装 Docker Compose
pip install docker-compose
</code></pre><h3 id="-git">初始化 Git</h3><p>为了后面<strong>自动化部署</strong>的方便，需要生成好 &nbsp;<strong>SSH 密钥对</strong>：</p><pre><code class="language-shell"># 生成 SSH Key
ssh-keygen -t rsa -b 4096 -C "my_email@example.com"

cd ~/.ssh
添加 SSH Key 私钥
ssh-add id_rsa
信任 SSH Key 公钥

</code></pre><p>并将上述公钥添加到 &nbsp;<code>[https://github.com/my-id/my-project/settings/keys][16]</code> &nbsp;页面。</p><p>如此，便可无需用户名、密码，直接克隆代码库：</p><pre><code class="language-shell">git clone git@github.com:my-id/my-project.git /var/www
</code></pre><h3 id="--6">部署配置文件</h3><p>将以下两个文件放在代码库相应路径中，并提交：</p><h4 id="docker-">Docker 服务编排</h4><p><code>~/my-project/docker-compose.yml</code></p><pre><code class="language-yaml">version: '3'

</code></pre><p>以上配置的是最简单的 &nbsp;<a href="https://www.sqlite.org/">SQLite</a> &nbsp;数据库，小型网站完全足够。</p><h4 id="github-actions-">GitHub Actions 持续部署</h4><p>以下配置让我们无需每次更新 Strapi 配置、代码时，登录服务器手动部署新版本：</p><p><code>~/my-project/.github/workflows/main.yml~</code></p><pre><code class="language-yaml">name: Deploy Server
on:
  push:
    branches:
      - master
jobs:
  SSH:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@master
      - name: Deploy
        uses: appleboy/ssh-action@master
        with:
          host: ${{ secrets.HOST }}
          username: root
          key: ${{ secrets.SSH_KEY }}
          script: |
            cd /var/www
            git fetch --all
            git reset --hard
            git pull
            docker-compose down
            docker-compose up -d
</code></pre><p>【注意】在推送代码前，务必去 &nbsp;<code>[https://github.com/my-id/my-project/settings/secrets][22]</code> &nbsp;页面创建好上述配置中的变量：</p><p>名称内容<code>HOST</code>服务器 &nbsp;<strong>IP 地址</strong><code>SSH_KEYssh-keygen</code> &nbsp;命令生成的<strong>私钥</strong>，通常存在 &nbsp;<code>/.ssh/id_rsa</code></p><h2 id="--7">总结</h2><p>经过前面的一顿折腾，<strong>开发者</strong>只需在本机浏览器中点点鼠标、轻敲键盘，就能实现<strong>网站数据结构</strong>的设计；推送代码到 GitHub，就能实现网站后台的更新。而<strong>运营专员</strong>访问的线上后台锁定了数据结构，他们只能在现有数据表中添加具体数据。这样一来，面对单纯的数据存取，<strong>后端 API</strong> 和<strong>后台 UI</strong> 都不用开发了~</p><p>欢迎访问我的<a href="https://tech-query.me/">更多文章</a>。</p> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 更强大的表单控件 ]]>
                </title>
                <description>
                    <![CDATA[ 有了一个新事件，再加上一些自定义元素 API，表单的使用变得更加容易。 很多开发者构建自定义表单元素，提供浏览器未内置的控件，或自定义超越内置表单控件的外观与体验。 然而，复制 HTML 内置表单控件的特性很难。想一下当你把一个 <input> 元素加进一个表单时，它自动获得的一些特性：  * 输入框自动加进表单的控件列表  * 输入框中的值自动与表单一起提交  * 输入框参与表单校验    [https://developer.mozilla.org/zh-CN/docs/Learn/HTML/Forms/Data_form_validation]    ，你可以用 :valid 和 :invalid 伪类为输入框设置样式  * 表单重置、重加载时，或浏览器尝试自动填充表单项时，输入框都会被通知 自定义表单组件通常没有这些特性。开发者能用 JavaScript 解决一些限制，比如向一个表单添加一个隐藏的 <input>  以参与表单提交。但其它特性无法单单用 JavaScript 复制。 两个 Web 新特性让构建自定义表单控件更简单，解决了现有自定义控件的限制：  *  ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/better-form-controls/</link>
                <guid isPermaLink="false">5ff71d2739641a0517d5352f</guid>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ 表单 ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Web开发 ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ TechQuery ]]>
                </dc:creator>
                <pubDate>Thu, 07 Jan 2021 09:20:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2021/01/ratapan-anantawat-8EiFzjvt_U0-unsplash--1-.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>有了一个新事件，再加上一些自定义元素 API，表单的使用变得更加容易。</p><p>很多开发者构建自定义表单元素，提供浏览器未内置的控件，或自定义超越内置表单控件的外观与体验。</p><p>然而，复制 HTML 内置表单控件的特性很难。想一下当你把一个 <code>&lt;input&gt;</code> 元素加进一个表单时，它自动获得的一些特性：</p><ul><li>输入框自动加进表单的控件列表</li><li>输入框中的值自动与表单一起提交</li><li>输入框参与<a href="https://developer.mozilla.org/zh-CN/docs/Learn/HTML/Forms/Data_form_validation" rel="noopener">表单校验</a>，你可以用 <code>:valid</code> 和 <code>:invalid</code> 伪类为输入框设置样式</li><li>表单重置、重加载时，或浏览器尝试自动填充表单项时，输入框都会被通知</li></ul><p>自定义表单组件通常没有这些特性。开发者能用 JavaScript 解决一些限制，比如向一个表单添加一个隐藏的 <code>&lt;input&gt;</code> 以参与表单提交。但其它特性无法单单用 JavaScript 复制。</p><p>两个 Web 新特性让构建自定义表单控件更简单，解决了现有自定义控件的限制：</p><ul><li><code>formdata</code> 时间让一个任意的 JavaScript 对象参与到表单提交中，所以你可以无需一个隐藏的 <code>&lt;input&gt;</code> 就能添加表单数据</li><li><strong><strong>Form-associated Custom Elements API</strong></strong> 让自定义元素表现得更像内置表单控件</li></ul><p>这两个特性可用于创建效果更好的新型控件。</p><p>构建自定义表单组件是个高级话题。本文假定您对表单和表单控件有一定的了解。当构建一个自定义表单控件时，有很多因素要考虑，特别是确定你的控件是对所有用户无障碍的。学习更多表单知识，前往 <a href="https://developer.mozilla.org/zh-CN/docs/Learn/HTML/Forms" rel="noopener">MDN 表单指南</a>。</p><h2 id="-api"><strong>基于事件的 API</strong></h2><p><code>formdata</code> 事件是一个让任何 JavaScript 代码参与表单提交的底层 API。该机制是这样的：</p><ol><li>你添加一个 <code>formdata</code> 事件监听器到你想交互的表单</li><li>当一个用户点击提交按钮，该表单触发一个 <code>formdata</code> 事件，它包含一个持有所有待提交数据的 <a href="https://developer.mozilla.org/zh-CN/docs/Web/API/FormData" rel="noopener"><code>FormData</code></a> 对象</li><li>每个 <code>formdata</code> 监听器都有机会在表单提交前添加或修改数据</li></ol><p>这里是一个在 <code>formdata</code> 事件监听器中发送一个单值的例子：</p><pre><code class="language-javascript">const form = document.querySelector('form');
// FormData 事件在 &lt;form&gt; 提交时、传输前触发
// 该事件有个 formData 属性
form.addEventListener('formdata', ({ formData }) =&gt; {
  // https://developer.mozilla.org/zh-CN/docs/Web/API/FormData
  formData.append('my-input', myInputValue);
});</code></pre><p>用我们在 Glitch 的示例来尝试。务必在 Chrome 77 或更高版本上运行，以查看该 API 的运行情况：<a href="https://glitch.com/~nosy-scandalous-king">https://glitch.com/~nosy-scandalous-king</a>。</p><h2 id="-"><strong>表单关联的自定义元素</strong></h2><p>你可以把基于事件的 API 用于任何类型的组件，但它只允许你与提交过程交互。</p><p>标准化的表单控件除了提交外，还参与了表单生命周期的许多部分。表单关联的自定义元素旨在填补自定义控件和内置控件的鸿沟，并匹配了很多标准表单元素的特性：</p><ul><li>当你把一个表单关联的自定义元素放进一个 <code>&lt;form&gt;</code>，它就像一个浏览器提供的控件，自动与该表单关联。</li><li>该元素可被一个 <code>&lt;label&gt;</code> 元素标记</li><li>该元素可设置一个与表单一起自动提交的值</li><li>该元素可设置一个标记，指示它是否取得有效输入。如果其中一个表单元素有无效输入，该表单则不能被提交。</li><li>该元素可提供一些用于表单生命周期多个部分的回调 —— 比如当该表单被禁用或重置到它的默认状态</li><li>该元素支持标准的 CSS 表单控件伪类，如 <code>:disabled</code> 和 <code>:invalid</code></li></ul><p>这么多功能！本文不会涉及所有内容，但将阐述把自定义元素与表单集成所需的基础知识。</p><p>本节假设你对自定义元素有基本的了解。有关自定义元素的介绍，参见 Web Fundamentals 上的<a href="https://developers.google.com/web/fundamentals/web-components/customelements" rel="noopener">《Custom Elements v1：可复用的 Web 组件》</a>。</p><h3 id="--1"><strong>定义一个表单关联的自定义元素</strong></h3><p>把一个自定义元素转变成一个表单关联的自定义元素，需要几个额外步骤:</p><ul><li>添加一个静态 <code>formAssociated</code> 属性到你的自定义元素类，这告诉浏览器把该元素看作一个表单控件</li><li>在该元素上调用 <code>attachInternals()</code> 方法，以访问表单控件的其它方法和属性，如 <code>setFormValue()</code> 和 <code>setValidity()</code></li><li>添加表单控件支持的通用属性和方法，如 <code>name</code>、<code>value</code> 和 <code>validity</code></li></ul><p>来看看如何把这些项目融入一个基础的自定义元素定义：</p><pre><code class="language-javascript">// 表单关联的自定义元素必须是独立的自定义元素 ——
// 意味着它们必须继承自 HTMLElement，而非它的一个子类。
class MyCounter extends HTMLElement {
  // 把该元素标记为一个表单关联的自定义元素
  static formAssociated = true;

  constructor() {
    super();
    // 获得访问内部表单控件 API 的能力
    this.internals_ = this.attachInternals();
    // 该控件的内部值
    this.value_ = 0;
  }
  // 表单控件通常暴露一个“value”属性
  get value() {
    return this.value_;
  }
  set value(v) {
    this.value_ = v;
  }
  // 以下属性和方法并非必须，但浏览器级表单控件提供了它们。
  // 提供它们有助于确保与浏览器提供的控件保持一致。
  get form() {
    return this.internals_.form;
  }
  get name() {
    return this.getAttribute('name');
  }
  get type() {
    return this.localName;
  }
  get validity() {
    return this.internals_.validity;
  }
  get validationMessage() {
    return this.internals_.validationMessage;
  }
  get willValidate() {
    return this.internals_.willValidate;
  }

  checkValidity() {
    return this.internals_.checkValidity();
  }
  reportValidity() {
    return this.internals_.reportValidity();
  }
  // ...
}

customElements.define('my-counter', MyCounter);</code></pre><p>一旦注册，你就可以在你使用浏览器表单控件的任何地方使用这个控件：</p><pre><code class="language-html">&lt;form&gt;
  &lt;label&gt;兔子的数量: &lt;my-counter&gt;&lt;/my-counter&gt;&lt;/label&gt;
  &lt;button type="submit"&gt;提交&lt;/button&gt;
&lt;/form&gt;</code></pre><h3 id="--2"><strong>设置一个值</strong></h3><p><code>attachInternals()</code> 方法返回一个可访问表单控件 API 的 <code>ElementInternals</code> 对象。这些 API 中最基础的是 <code>setFormValue()</code> 方法，用来设置该控件的当前值。<code>setFormValue()</code> 方法可接受三种类型之一的值：</p><ul><li>一个字符串值</li><li>一个 <a href="https://developer.mozilla.org/zh-CN/docs/Web/API/File" rel="noopener"><code>File</code></a> 对象</li><li>一个 <a href="https://developer.mozilla.org/zh-CN/docs/Web/API/FormData" rel="noopener"><code>FormData</code></a> 对象，你可以用一个 <code>FormData</code> 对象来传多值（例如一个信用卡输入控件可能会传一个卡号、过期日期和验证码）</li></ul><p>设置一个简单值：</p><pre><code class="language-javascript">this.internals_.setFormValue(this.value_);</code></pre><p>设置多值，你可以执行以下操作：</p><pre><code class="language-javascript">// 用该控件名作为提交数据的基础名
const n = this.getAttribute('name');
const entries = new FormData();

entries.append(n + '-first-name', this.firstName_);
entries.append(n + '-last-name', this.lastName_);
this.internals_.setFormValue(entries);</code></pre><p><code>setFormValue()</code> 方法接受一个可选的第二参数 <code>state</code>，用来存储该控件的内部状态。<br>详情参见本文「恢复表单状态」一节</p><h3 id="--3"><strong>输入校验</strong></h3><p>你的控件也可以通过调用内部对象上的 <code>setValidity()</code> 方法参与表单校验。</p><pre><code class="language-javascript">// 假设在内部值更新时调用此方法
onUpdateValue() {
    if (
        !this.matches(':disabled') &amp;&amp;
        this.hasAttribute('required') &amp;&amp;
        this.value_ &lt; 0
    ) {
        this.internals_.setValidity(
            { customError: true }, 'Value cannot be negative.'
        );
    } else {
        this.internals_.setValidity({});
    }
    this.internals.setFormValue(this.value_);
}</code></pre><p>你可以用 <code>:valid</code> 和 <code>:invalid</code> 伪类给一个表单关联的自定义元素设置样式，就像一个内置表单控件一样。</p><p>尽管你可以设置一个校验消息，但 Chrome 目前不能显示表单关联自定义元素的校验消息。</p><h3 id="--4"><strong>生命周期回调</strong></h3><p>Form-associated Custom Elements API 包含一组额外的生命周期回调，以绑定到表单生命周期。</p><p>这些回调是可选的：仅在你的元素需要在生命周期的某一刻做某事时才实现一个回调。</p><h4 id="void-formassociatedcallback-form-"><strong><code>void formAssociatedCallback(form)</code></strong></h4><p>当浏览器将一个表单元素关联到该元素时调用，或从一个表单元素解除关联该元素时。</p><h4 id="void-formdisabledcallback-disabled-"><strong><code>void formDisabledCallback(disabled)</code></strong></h4><p>该元素的 <code>disabled</code> 状态改变后，无论是因为该元素的 <code>disabled</code> 属性被添加或移除；还是因为该元素的一个祖先 <code>&lt;fieldset&gt;</code> 的 <code>disabled</code> 状态被改变。<code>disabled</code> 参数代表该元素的新<a href="https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#concept-fe-disabled" rel="noopener">禁用状态</a>。例如，当该元素被禁用时，它可能要禁用它影子 DOM 中的元素。</p><h4 id="void-formresetcallback-"><strong><code>void formResetCallback()</code></strong></h4><p>该表单被重置后调用。该元素应该将其自身重置回某种默认状态。对于 <code>&lt;input /&gt;</code> 元素，这通常涉及设置 <code>value</code> 属性以匹配标签中设置的 <code>value</code> 属性（或在复选框的案例中，设置 <code>checked</code> 对象属性以匹配 <code>checked</code> 标签属性）。</p><h4 id="void-formstaterestorecallback-state-mode-"><strong><code>void formStateRestoreCallback(state, mode)</code></strong></h4><p>在两种情形之一被调用：</p><ul><li>当浏览器恢复该元素状态时（例如，一次导航后，或当浏览器重启时），在此情形下 <code>mode</code> 参数是 <code>"restore"</code></li><li>当浏览器的输入助手特性（诸如表单自动填充）设置一个值时，在此情形下 <code>mode</code> 参数是 <code>"autocomplete"</code></li></ul><p>第一个参数的类型则取决于 <code>setFormValue()</code> 方法被怎样调用。详情参见本文「恢复表单状态」一节。</p><h3 id="--5"><strong>恢复表单状态</strong></h3><p>在某些情形下 —— 如当导航返回一个页面时，或重启浏览器时，浏览器可能尝试恢复该表单到用户保留的状态。</p><p>对于一个表单关联的自定义元素，被恢复的状态来自你传到 <code>setFormValue()</code> 方法的值。就像早前示例中展示的那样，你可以用一个单值参数或两个参数调用该方法：</p><pre><code class="language-javascript">this.internals_.setFormValue(value, state);</code></pre><p>此处的 <code>value</code> 代表该控件的可提交参数。此处的可选 <code>state</code> 参数是一个该控件状态的 <em><em>内部</em></em> 表示，可包含不发送到服务器的数据。该 <code>state</code> 参数具有与 <code>value</code> 参数相同的类型 —— 它可以是一个字符串、<code>File</code> 或 <code>FormData</code> 对象。</p><p><code>state</code> 参数在你无法只基于值去恢复一个控件状态时很有用。例如，假设你创建了一个多模的颜色选择器：一个调色板或 RGB 色轮。可提交的 <em><em>值</em></em> 会是规范形式的选中颜色，如 <code>"#7fff00"</code>。但恢复该控件到一个特定状态，你也需要知道它之前处在哪种模式，所以 <em><em>状态</em></em> 可能形如 <code>"palette/#7fff00"</code>。</p><pre><code class="language-javascript">this.internals_.setFormValue(this.value_, this.mode_ + '/' + this.value_);</code></pre><p>你的代码可能需要基于存储的状态值来恢复自身状态。</p><pre><code class="language-javascript">formStateRestoreCallback(state, mode) {
    if (mode == 'restore') {
        // 预期一个形如 'controlMode/value' 的状态参数
        [controlMode, value]= state.split('/');
        this.mode_ = controlMode;
        this.value_ = value;
    }
    // Chrome 目前不处理表单关联自定义元素的自动填充。
    // 在自动填充案例中，你可能需要处理一个原始值。
}</code></pre><p>在一个更简单的控件案例中（例如一个数字输入框），值可能足够该控件恢复之前的状态。当调用 <code>setFormValue()</code> 时，若你忽略 <code>state</code> ，值会被直接传给 <code>formStateRestoreCallback()</code>。</p><pre><code class="language-javascript">formStateRestoreCallback(state, mode) {
    // Simple case, restore the saved value
    this.value_ = state;
}</code></pre><h3 id="--6"><strong>一个实际的例子</strong></h3><p>这个<a href="https://glitch.com/~form-associated-ce">示例</a>整合了表单关联自定义元素的很多特性。务必在 Chrome 77 或更高版本运行它，以查看 API 的运行情况。</p><h2 id="--7"><strong>特性检测</strong></h2><p>你可以用特性检测去确定 <code>formdata</code> 事件和表单关联的自定义元素是可用的。目前每个特性都没有补丁发布。针对这两种情况，你可以回退到添加一个隐藏的表单元素来把该控件值传给表单。很多更高级的表单关联自定义元素特性将可能很难或无法打补丁。</p><pre><code class="language-javascript">if ('FormDataEvent' in window) {
  // 支持 formdata 事件
}
if (
  'ElementInternals' in window &amp;&amp;
  'setFormValue' in window.ElementInternals.prototype
) {
  // 支持表单关联的自定义元素
}</code></pre><p><strong><strong>译注</strong></strong>：原文发表一季度后，社区开发者发布了一个 <a href="https://github.com/calebdwilliams/element-internals-polyfill" rel="noopener">ElementInternals polyfill</a>，而 Web Components 标准团队官方 polyfill 也在<a href="https://github.com/webcomponents/polyfills#roadmap" rel="noopener">计划实现本文所述特性</a>。</p><h2 id="--8"><strong>结论</strong></h2><p><code>formdata</code> 事件和表单关联自定义元素为创建自定义表单控件提供了新工具。</p><p><code>formdata</code> 事件没有给你任何新能力，但它给你一个接口，让你无须一个隐藏的 <code>&lt;input /&gt;</code> 元素，即可添加表单数据到提交流程。</p><p>Form-associated Custom Elements API 通过一组新能力让自定义表单控件像内置表单控件一样工作。</p><h2 id="--9"><strong>译者后记</strong></h2><p>Custom Elements v1 标准刚发布时，自定义表单元素的构建可以通过<strong><strong>扩展原生标签</strong></strong>特性来实现：</p><pre><code class="language-html">&lt;template&gt;
  &lt;div contenteditable="true"&gt;&lt;/div&gt;
&lt;/template&gt;

&lt;script&gt;
  const { content } = document.currentScript.previousElementSibling;

  class MyInput extends HTMLInputElement {
    constructor() {
      super().attachShadow({ mode: 'open' }).append(content.cloneNode(true));
    }
  }
  customElements.define('my-input', MyInput, { extends: 'input' });
&lt;/script&gt;

&lt;input is="my-input" /&gt;</code></pre><p>虽然基于 <strong><strong>ES 6 class 继承</strong></strong>语法可以重写表单元素的各种属性、方法，在外部代码操作 DOM 时执行自定义逻辑，但用户直接与浏览器交互不会调用这些重写的接口。因此我们的自定义元素只能单纯地依赖内置表单元素的能力，而无法介入表单的工作流程。</p><p>于是，浏览器进一步暴露自己的内部机制，便有了 Element Internals API。它除了包含本文所述 Form-associated Custom Element API 的很多接口，还涉及另一项新标准 —— <a href="https://w3c.github.io/aria/#ARIAMixin" rel="noopener">AOM</a>（可访问性/无障碍化对象模型），让开发者更轻松地构造更好用的组件。</p><figure class="kg-card kg-image-card"><img src="https://web-cell.dev/WebCell-1.fb612fdb.png" class="kg-image" alt="WebCell-1.fb612fdb" width="600" height="400" loading="lazy"></figure><p>同时，译者自研的 <a href="https://web-cell.dev/" rel="noopener">Web Components 组件框架 WebCell</a> 在原文的帮助下<a href="https://github.com/calebdwilliams/element-internals-polyfill/network/dependents" rel="noopener">率先支持新标准</a>，使前文那段最长的示例代码变得非常简单：</p><pre><code class="language-typescript">import { component, mixinForm } from 'web-cell';

@component({
  tagName: 'my-counter'
})
export class MyCounter extends mixinForm() {}</code></pre><p>最后，欢迎大家持续关注译者对 Web 组件相关标准的后续译文和开源项目更新~</p><p>本文译自 <a href="https://web.dev/more-capable-form-controls/">More capable form controls</a>。访问译者的<a href="https://tech-query.me/">更多文章</a>。</p> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 为何新手可用 TypeScript？ ]]>
                </title>
                <description>
                    <![CDATA[ TypeScript 官网 [https://www.typescriptlang.org/zh/]开宗明义 —— > TypeScript 是适用于任何规模应用的 JavaScript 其中奥妙，且看我一个边干边学的 JS 码农娓娓道来~ 安装简单 因为 TypeScript 官方支持 ECMAScript 最新标准 [https://tc39.es/ecma262/]和进入 Stage 3 的提案 [https://jscig.github.io/#proposals?stage=3]，绝大多数项目的代码可以直接编译，polyfill 和运行时工具函数全内置，不像 Babel 那样需要自己找运行时补丁库或提案编译插件。 TypeScript npm install typescript -D Babel npm install @babel/core @babel/preset-env @babel/cli -D npm install @babel/polyfill import '@babel/polyfill'; 配置方便 TypeScript TypeScrip ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/why-should-you-use-typescript/</link>
                <guid isPermaLink="false">5fa14b6c5f583f0565090d88</guid>
                
                    <category>
                        <![CDATA[ TypeScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Web开发 ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ TechQuery ]]>
                </dc:creator>
                <pubDate>Tue, 03 Nov 2020 07:21:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2021/04/photo-1593642532454-e138e28a63f4.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p><a href="https://www.typescriptlang.org/zh/" rel="noopener">TypeScript 官网</a>开宗明义 ——</p><blockquote>TypeScript 是适用于任何规模应用的 JavaScript</blockquote><p>其中奥妙，且看我一个边干边学的 JS 码农娓娓道来~</p><h2 id="-"><strong>安装简单</strong></h2><p>因为 TypeScript 官方支持 <a href="https://tc39.es/ecma262/" rel="noopener">ECMAScript 最新标准</a>和<a href="https://jscig.github.io/#proposals?stage=3" rel="noopener">进入 Stage 3 的提案</a>，绝大多数项目的代码可以直接编译，polyfill 和运行时工具函数全内置，不像 Babel 那样需要自己找<strong><strong>运行时补丁库</strong></strong>或<strong><strong>提案编译插件</strong></strong>。</p><h3 id="typescript"><strong>TypeScript</strong></h3><pre><code class="language-shell">npm install typescript -D</code></pre><h3 id="babel"><strong>Babel</strong></h3><pre><code class="language-shell">npm install @babel/core @babel/preset-env @babel/cli -D
npm install @babel/polyfill</code></pre><pre><code class="language-javascript">import '@babel/polyfill';</code></pre><h2 id="--1"><strong>配置方便</strong></h2><h3 id="typescript-1"><strong>TypeScript</strong></h3><p>TypeScript 默认不需要配置，在写 <code>.ts(x)</code> 文件的过程中，TS 编译器会在缺少配置的地方提示你需要添加什么配置，此时在项目根目录新建一个 <code>tsconfig.json</code> 文件，支持 <strong><strong>TS 语言服务协议</strong></strong>的代码编辑器还能提示配置文件的选项，全程按指导操作即可，官方文档都很少需要看~</p><h3 id="babel-1"><strong>Babel</strong></h3><p>Babel 虽然编译标准语法只需一个 preset 包，但更多的<strong><strong>常用提案语法</strong></strong>、<strong><strong>框架语法糖</strong></strong>就需要一大堆插件了，而且所有配置选项都没有代码提示，必须各种查文档，有时文档也不甚详细，容易踩坑……</p><pre><code class="language-json">{
  "presets": ["@babel/preset-env"]
}</code></pre><h2 id="--2"><strong>类型可选</strong></h2><p>TypeScript 的核心卖点是<strong><strong>类型系统</strong></strong>，这让很多从 JavaScript、PHP、Python 等<strong><strong>脚本语言</strong></strong>入门编程的程序员非常畏惧，认为是非常大的<strong><strong>学习成本</strong></strong>。</p><p>但 TS 的类型系统与 C/C++、Java、C# 等传统<strong><strong>强类型语言</strong></strong>从设计理念上非常不同 ——</p><blockquote>TS 代码不需要<em><em>处处声明类型</em></em>，而只需程序员在编译器推导无效的少许地方<strong><strong>标注类型</strong></strong></blockquote><p>而 TS <strong><strong>类型推导</strong></strong>的基础数据则是 JS、DOM、Node.js 等标准/技术中的 <strong><strong>API 类型定义</strong></strong>，编译器循着<strong><strong>语法结构</strong></strong>的上下文，自然能推导出大多数变/常量的类型，以便帮程序员<strong><strong>检查潜在错误</strong></strong>、<strong><strong>显示代码提示</strong></strong>。</p><h2 id="--3"><strong>开发轻松</strong></h2><h3 id="node-js-"><strong>Node.js 运行时</strong></h3><p>虽然 Node.js 创始人又另起炉灶搞了第一个<strong><strong>全功能 TypeScript 运行时环境</strong></strong> <a href="https://www.denojs.cn/" rel="noopener">Deno</a>，但并不意味着 Node.js 不能方便地运行 TS 代码，因为我们有 <a href="https://github.com/TypeStrong/ts-node" rel="noopener"><code>ts-node</code></a> ——</p><pre><code class="language-shell">// 全局安装运行时
npm install ts-node -g
// 执行一个 TS 脚本
ts-node index.ts</code></pre><h3 id="parcel-"><strong>Parcel 零配置打包</strong></h3><p>如果你用 <a href="https://zh.parceljs.org/" rel="noopener">Parcel</a> 这种<strong><strong>零配置打包器</strong></strong>开发 Web 前端项目，你甚至完全不用安装包括 TypeScript、LESS、SCSS 在内的任何常见<strong><strong>预编译语言</strong></strong>的编译器。在项目首次启动时，Parcel 会自动帮你安装好项目依赖的所有代码文件类型对应的编译器！你只需在启动项目时装好 Parcel，然后就按 <strong><strong>HTML、CSS、JS 原生语法</strong></strong>写逻辑、引用外置资源即可 ——</p><pre><code class="language-shell">npm install parcel-bundler -D</code></pre><h2 id="-ts-"><strong>总结：TS，真香！</strong></h2><p>我的 JavaScript 生涯从 ES 3 入门，后来用 ES 5 结合 JSDoc 开发小框架，再到 ES 6+ 配合 ESDoc 开发 <a href="https://github.com/EasyWebApp/WebCell/tree/master" rel="noopener">WebCell v1</a>，最终用 TS + TSDoc 重写出 <a href="https://web-cell.dev/" rel="noopener">WebCell v2</a>。在充分利用 <a href="https://tech-query.me/programming/javascript-in-one-word/">JavaScript 这门“无所不能”的语言</a>强大灵活性的同时，<strong><strong>更简洁的 ECMAScript 新语法</strong></strong>和<strong><strong>更规范的 TypeScript 类型系统</strong></strong>，为开源库和日常项目开发带来突飞猛进的效率提升 —— TS，真香！</p><h2 id="--4"><strong>彩蛋</strong></h2><p>再分享一个 TypeScript <strong><strong>JSX 类型推导</strong></strong>的新玩法：</p><blockquote><a href="https://github.com/TechQuery/CommanderJSX" rel="noopener">CommanderJSX</a> —— 用 JSX 声明<strong><strong>命令行参数</strong></strong>，并支持<strong><strong>回调传参类型推导</strong></strong></blockquote><p>欢迎访问我的<a href="https://tech-query.me/">更多文章</a>。</p> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 重构高考志愿填报公益网站的 Web 前端代码（含视频） ]]>
                </title>
                <description>
                    <![CDATA[ 2020 年高考是一场非常不容易的高考，毕业生和社会各界都历经艰辛，希望努力拼搏的我们都能有更加美好的明天。 说起填志愿，回想起我高考时正值汶川大地震。那时我们获取可靠、有效、全面的志愿参考信息，基本只能通过学校发的一本大部头参考书，里面汇集了 全国招生的院校、专业和往年录取数据。但这样一本像过去每个城市的电话黄页一样的书，反复查阅它的“低效”痛苦就不用多说了。 进入大学 IT 技术社团 [https://www.fyscu.com/]之后，就一直想用自己学到的编程技术，来为学弟学妹解决我当年那般的痛苦。但彼时，我国 政府部门信息公开政策的保守和技术的落后，让我们很难获得好用的数据，商业平台又有巨大的封闭壁垒，遂作罢…… 直到前一阵子，FCC 一位城市社区组织者在群里分享了黄希彤 [https://cloud.tencent.com/tvp/member/157]老师的填教授 [https://professortian.net/]公益项目，看到他把多年收集的公开数据用统计学方法进一步处理 ，免费为广大考生做参考，内心十分激动！于是想尽自己现在的专长所能，帮黄老师优化一下前端界面 ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/web-development-for-college-application-tool/</link>
                <guid isPermaLink="false">5f3df437c8da7105cbc14cc3</guid>
                
                    <category>
                        <![CDATA[ Web开发 ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ TechQuery ]]>
                </dc:creator>
                <pubDate>Thu, 20 Aug 2020 04:08:40 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2020/08/1597896276412.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>2020 年高考是一场非常不容易的高考，毕业生和社会各界都历经艰辛，希望努力拼搏的我们都能有更加美好的明天。</p><p>说起<strong>填志愿</strong>，回想起我高考时正值汶川大地震。那时我们获取<strong>可靠、有效、全面的志愿参考信息</strong>，基本只能通过学校发的一本大部头参考书，里面汇集了<strong>全国招生的院校、专业和往年录取数据</strong>。但这样一本像过去每个城市的电话黄页一样的书，<strong>反复查阅它的“低效”痛苦</strong>就不用多说了。</p><p>进入<a href="https://www.fyscu.com/" rel="noopener">大学 IT 技术社团</a>之后，就一直想用自己学到的编程技术，来为学弟学妹解决我当年那般的痛苦。但彼时，我国<strong>政府部门信息公开</strong>政策的保守和技术的落后，让我们很难获得好用的数据，商业平台又有巨大的封闭壁垒，遂作罢……</p><p>直到前一阵子，FCC 一位城市社区组织者在群里分享了<a href="https://cloud.tencent.com/tvp/member/157" rel="noopener">黄希彤</a>老师的<a href="https://professortian.net/" rel="noopener">填教授</a>公益项目，看到他<strong>把多年收集的公开数据用统计学方法进一步处理</strong>，免费为广大考生做参考，内心十分激动！于是想尽自己现在的专长所能，帮黄老师优化一下前端界面，让广大的考生和家长用起来更方便。</p><p>同时黄老师也欣然接受了我一直以来的<strong>开源直播</strong>方式，于是便有了 <a href="https://live.bilibili.com/22218677" rel="noopener">FCC 中文社区 B 站直播间</a>长期栏目<a href="https://github.com/freeCodeCamp-China/activity/issues/5" rel="noopener">《水歌酱的开源日常》</a>的这期特别节目。</p><p>大家可以在下方观看<a href="https://www.bilibili.com/video/BV1NK411n7B7">视频</a>。也欢迎大家关注 <a href="https://space.bilibili.com/335505768/" rel="noopener">FCC B 站账号</a>以接收日常节目的开播提醒。</p><!--kg-card-begin: html--><figure class="kg-card kg-embed-card" data-test-label="fitted">
        <div class="fluid-width-video-container">
          <div style="padding-top: 56.25%;" class="fluid-width-video-wrapper">
            <iframe src="//player.bilibili.com/player.html?aid=499036284&amp;bvid=BV1NK411n7B7&amp;cid=218671402&amp;page=1" scrolling="no" border="0" frameborder="no" framespacing="0" allowfullscreen="true" width="256" height="144" name="fitvid0"> </iframe>
          </div>
        </div>
      </figure><!--kg-card-end: html--><h2 id="-">使用入门</h2><h3 id="--1">基本操作</h3><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/news/content/images/2020/08/image-5.png" class="kg-image" alt="image-5" width="600" height="400" loading="lazy"></figure><ol><li>选择考生所在<strong>省份</strong>、<strong>分科</strong>后，再填入<strong>高考成绩</strong>，点击查询按钮即可查询到<strong>根据往年录取情况有可能考上的学校和专业</strong></li><li>可以下拉选择不同的<strong>上线概率</strong>后重新查询，来筛选相对保险（高概率、保底）的专业和相对有挑战（低概率）的专业</li><li>对于公布<strong>位次</strong>的省份，可以选择按照位次来查询筛选专业</li><li>筛选后页面出现过滤按钮，可以输入感兴趣的学校和专业<strong>关键字</strong>在结果中进行进一步的筛选</li><li>每年招生情况都会发生变化，因此考生在查询后可点击<strong>学校、专业名称的链接</strong>，进一步了解相关专业当年的招生信息</li></ol><h3 id="-app">安装为 App</h3><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://chinese.freecodecamp.org/news/content/images/2020/08/image-6.png" class="kg-image" alt="image-6" width="600" height="400" loading="lazy"><figcaption>现代浏览器首次访问自动提示安装</figcaption></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://chinese.freecodecamp.org/news/content/images/2020/08/image-7.png" class="kg-image" alt="image-7" width="600" height="400" loading="lazy"><figcaption>确认安装到桌面</figcaption></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://chinese.freecodecamp.org/news/content/images/2020/08/image-8.png" class="kg-image" alt="image-8" width="600" height="400" loading="lazy"><figcaption>已安装到桌面<span class="-mobiledoc-kit__atom">‌‌</span></figcaption></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://chinese.freecodecamp.org/news/content/images/2020/08/image-9.png" class="kg-image" alt="image-9" width="600" height="400" loading="lazy"><figcaption>App 在系统中独立运行</figcaption></figure><h2 id="--2">技术知识</h2><p>为了满足黄老师提出的“除 CSS 样式外，简单业务不依赖任何库”要求，<a href="https://github.com/TechQuery" rel="noopener">水歌</a>本次对<a href="https://github.com/stonelf/China-college-application" rel="noopener">填教授 Web 前端代码</a>的重构采用了最新版 <strong>BootStrap</strong>、<strong>DOM API</strong>、<strong>JavaScript 标准</strong>，刚学完 <strong>freeCodeCamp HTML、CSS、JS 基础课程</strong>的菜鸟也能快速上手~</p><p>以下是这些<strong>易学、易用的标准、通用技术</strong>的入门文档：</p><h3 id="html-">HTML 标签、属性</h3><ol><li><a href="https://developer.mozilla.org/zh-CN/docs/Web/HTML/Global_attributes/tabindex" rel="noopener"><code>tabindex</code> 全局属性</a></li><li><a href="https://developer.mozilla.org/zh-CN/docs/Web/HTML/Global_attributes/hidden" rel="noopener"><code>hidden</code> 全局属性</a></li><li><a href="https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/iframe#%E5%B1%9E%E6%80%A7" rel="noopener"><code>iframe</code> 标签的 <code>name</code> 属性</a></li><li><a href="https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/template" rel="noopener"><code>template</code> 标签</a></li></ol><h3 id="css">CSS</h3><ol><li><a href="https://developer.mozilla.org/zh-CN/docs/Web/CSS/:empty" rel="noopener"><code>:empty</code> 伪类选择符</a></li></ol><h3 id="dom-api">DOM API</h3><ol><li><a href="https://developer.mozilla.org/zh-CN/docs/Web/API/Document_Object_Model/Locating_DOM_elements_using_selectors" rel="noopener">CSS 选择器 API</a></li><li><a href="https://developer.mozilla.org/zh-CN/docs/Web/API/Document/forms" rel="noopener"><code>document.forms</code></a></li><li><a href="https://developer.mozilla.org/zh-CN/docs/Web/API/ParentNode" rel="noopener"><code>ParentNode</code> 接口</a></li><li><a href="https://developer.mozilla.org/zh-CN/docs/Web/API/ChildNode" rel="noopener"><code>ChildNode</code> 接口</a></li><li><a href="https://developer.mozilla.org/zh-CN/docs/Web/API/Element/classList" rel="noopener"><code>Element.prototype.classList</code></a></li><li><a href="https://developer.mozilla.org/zh-CN/docs/Web/API/HTMLFormElement/elements" rel="noopener"><code>HTMLFormElement.prototype.elements</code></a></li></ol><h3 id="bom-api">BOM API</h3><ol><li><a href="https://developer.mozilla.org/zh-CN/docs/Web/API/Fetch_API/Using_Fetch" rel="noopener"><code>fetch()</code></a></li><li><a href="https://developer.mozilla.org/zh-CN/docs/Web/API/URL/URL" rel="noopener"><code>URL()</code></a></li></ol><h3 id="ecmascript-api-">ECMAScript API、语法</h3><ol><li><a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/map" rel="noopener"><code>Array.prototype.map()</code></a></li><li><a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/from" rel="noopener"><code>Array.from()</code></a></li><li><a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/template_strings" rel="noopener">ECMAScript 6 模板字符串</a></li><li><a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/with" rel="noopener"><code>with</code> 语句的利弊</a></li><li><a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/async_function" rel="noopener"><code>async</code> 函数</a></li></ol><h3 id="--3">第三方开源库、云服务</h3><ol><li><a href="https://getbootstrap.com/" rel="noopener">BootStrap 4.5</a></li><li><a href="https://polyfill.io/" rel="noopener">API Polyfill 自动补丁服务</a></li><li><a href="https://codesandbox.io/" rel="noopener">CodeSandbox 在线 Web 前端项目沙盒</a></li><li><a href="https://parceljs.org/" rel="noopener">Parcel 零配置打包器</a></li></ol><h3 id="--4">参考文档</h3><p>以上知识点在 <a href="https://fcc-cd.dev/">FCC 成都社区</a>之前的技术博文有介绍：</p><ol><li><a href="https://mp.weixin.qq.com/s?__biz=MzU5NTY3Njk2Mw==&amp;mid=2247485350&amp;idx=1&amp;sn=cec088a25cf6a7fb93317930889014eb&amp;scene=21#wechat_redirect" rel="noopener">《ECMAScript + DOM 骚操作》</a></li><li><a href="https://mp.weixin.qq.com/s?__biz=MzU5NTY3Njk2Mw==&amp;mid=2247485453&amp;idx=1&amp;sn=2e3157201fb3880a1d4af7970baa71ec&amp;scene=21#wechat_redirect" rel="noopener">《JavaScript 效率工具》</a></li><li><a href="https://mp.weixin.qq.com/s?__biz=MzU5NTY3Njk2Mw==&amp;mid=2247485283&amp;idx=1&amp;sn=c18238b894443f4fdc95020121a0535d&amp;scene=21#wechat_redirect" rel="noopener">《如何用开源软件办一场技术大会？》</a></li></ol> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 网站秒变 App —— PWA 即刻上手 ]]>
                </title>
                <description>
                    <![CDATA[ 近两年国内前端界很流行小程序，但大多数人都不知道小程序很多概念是鹅厂“借鉴”自 Google 主导的 Progressive Web App（渐进式网页应用）标准 [https://developers.google.cn/web/progressive-web-apps/] ——  1. “独立”运行：在“系统正在运行的应用”列表中独立显示  2. 本地缓存：页面秒开、离线运行  3. 安装到主屏幕：桌面图标、启动首屏、全屏显示等，看起来和原生 App 别无二致 以上特性已经很香了，更甭说开放的 Web 还有更丰富的新标准 [https://tech-query.me/development/web-chrome-update-at-google-io-2019/slide.html] 来拓展 Web App 的疆域了~ 那这么香的新标准怎么上手呢？会像小程序开发环境那样难用吗？我们老规矩 —— 抄起键盘就是一把梭！ 应用元数据 一个渐进式 Web 应用首先要声明一下自己的基本信息： index.html <head>   <link rel="manifest" hre ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/turn-your-website-to-pwa/</link>
                <guid isPermaLink="false">5f3361c9c8da7105cbc14aca</guid>
                
                <dc:creator>
                    <![CDATA[ TechQuery ]]>
                </dc:creator>
                <pubDate>Wed, 12 Aug 2020 03:36:41 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2020/08/WechatIMG2705.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>近两年国内前端界很流行<em><em>小程序</em></em>，但大多数人都不知道小程序很多概念是鹅厂“借鉴”自 Google 主导的 <a href="https://developers.google.cn/web/progressive-web-apps/" rel="noopener">Progressive Web App（渐进式网页应用）标准</a> ——</p><ol><li><strong><strong>“独立”运行</strong></strong>：在“系统正在运行的应用”列表中独立显示</li><li><strong><strong>本地缓存</strong></strong>：页面秒开、离线运行</li><li><strong><strong>安装到主屏幕</strong></strong>：桌面图标、启动首屏、全屏显示等，看起来和原生 App 别无二致</li></ol><p>以上特性已经很香了，更甭说开放的 Web 还有<a href="https://tech-query.me/development/web-chrome-update-at-google-io-2019/slide.html">更丰富的新标准</a>来拓展 Web App 的疆域了~</p><p>那这么香的新标准怎么上手呢？会像小程序开发环境那样难用吗？我们老规矩 —— <strong><strong>抄起键盘就是一把梭！</strong></strong></p><h2 id="-"><strong>应用元数据</strong></h2><p>一个<strong><strong>渐进式 Web 应用</strong></strong>首先要声明一下自己的基本信息：</p><p><code>index.html</code></p><pre><code class="language-html">&lt;head&gt;
  &lt;link rel="manifest" href="index.webmanifest" /&gt;
  &lt;!-- 老浏览器私有 meta 标签兼容 --&gt;
  &lt;script src="https://cdn.jsdelivr.net/npm/pwacompat@2.0.15/pwacompat.min.js"&gt;&lt;/script&gt;
&lt;/head&gt;</code></pre><p><code>index.webmanifest</code></p><pre><code class="language-json">{
  "name": "应用全名（启动页）",
  "short_name": "应用短名（桌面图标）",
  "start_url": "https://example.dev/",
  "description": "应用简介",
  "scope": "/",
  "display": "standalone",
  "orientation": "any",
  "lang": "zh-CN",
  "dir": "ltr",
  "theme_color": "rgba(0,0,0,0.5)",
  "background_color": "transparent",
  "icons": [
    {
      "src": "logo.png",
      "type": "image/png",
      "sizes": "144x144"
    }
  ]
}</code></pre><h2 id="--1"><strong>后台服务</strong></h2><p>一个 PWA 能真正安装到用户系统中，还需要一个 <strong><strong>Service Worker</strong></strong>。但它的工作原理对初学者直接手写有些困难，亲爹 Google 已经做好了开发框架 <a href="https://developers.google.cn/web/tools/workbox" rel="noopener">Workbox</a>，还配了 <a href="https://developers.google.cn/web/tools/workbox/guides/generate-service-worker/cli" rel="noopener">Workbox CLI</a>，几行命令就能生成一个基本的 ServiceWorker，简直不要太贴心：</p><pre><code class="language-shell">npm install workbox-cli --global

cd your_project/

npm run build  # 先执行你项目的构建脚本，生成生产环境目录

workbox wizard</code></pre><p>跟着向导执行完上述命令，会生成一个名为 <code>workbox-config.js</code> 的配置文件，但须稍加修改：</p><pre><code class="language-javascript">module.exports = {
  globDirectory: 'dist/',
  globPatterns: ['**/*.{html,css,js,json,ico,gif,jpg,jpeg,png,webp}'],
  swDest: 'dist/sw.js',
  // 禁用 Google Cloud CDN
  importWorkboxFrom: 'disabled',
  // 启用 第三方 CDN
  importScripts: [
    'https://cdn.jsdelivr.net/npm/workbox-sw@4.3.1/build/workbox-sw.min.js'
  ],
  // 首次打开页面后尽快接管网络请求
  clientsClaim: true,
  cleanupOutdatedCaches: true
};</code></pre><p>然后就可以一键生成 ServiceWorker 了：</p><pre><code class="language-json">workbox generateSW</code></pre><p>最后在 <code>index.html</code> 里注册一下即可：</p><pre><code class="language-html">&lt;head&gt;
  &lt;link rel="manifest" href="index.webmanifest" /&gt;
  &lt;!-- 老浏览器私有 meta 标签兼容 --&gt;
  &lt;script src="https://cdn.jsdelivr.net/npm/pwacompat@2.0.15/pwacompat.min.js"&gt;&lt;/script&gt;
  &lt;script&gt;
    navigator.serviceWorker?.register('sw.js');
  &lt;/script&gt;
&lt;/head&gt;</code></pre><h2 id="--2"><strong>工具链兼容性</strong></h2><h3 id="parcel"><strong>Parcel</strong></h3><p><strong><strong>0 配置、一键化</strong></strong>的 <a href="https://parceljs.org/" rel="noopener">Parcel</a> 原生支持 <strong><strong>Web Manifest</strong></strong>，不像 webpack 和 Rollup，还需要配置插件，只需将 <code>package.json</code> 中的构建脚本修改如下：</p><pre><code class="language-json">{
  "scripts": {
    "start": "workbox generateSW  &amp;&amp;  parcel source/index.html --open",
    "pack": "parcel build source/index.html --public-url .",
    "build": "rm -rf dist/  &amp;&amp;  npm run pack-dist  &amp;&amp;  workbox generateSW"
  }
}</code></pre><p>每次构建运行 <code>npm run build</code> 即可生成可缓存整个 Web App 的 Service Worker 代码。</p><h3 id="webpack"><strong>webpack</strong></h3><p>因为我不是 _webpack 配置工程师_，所以我查了好多技术博文才总结出一套最简单的 webpack PWA 配置：</p><pre><code class="language-shell">npm install webpack-pwa-manifest workbox-webpack-plugin -D</code></pre><p>安装好上述 webpack 插件才能开始配置，而且需要注意的是，<code>webpack-pwa-manifest</code> 这个第三方插件对 <a href="https://developer.mozilla.org/zh-CN/docs/Web/Manifest" rel="noopener">PWA Manifest 字段</a>的支持不完整，如遇报错就删除相应字段。</p><pre><code class="language-javascript">// webpack.config.js
const { resolve } = require('path');

const PWA_Manifest = require('webpack-pwa-manifest'),
  Workbox = require('workbox-webpack-plugin');

const PWA_manifest = require('./src/manifest.json'),
  Workbox_config = require('./workbox.config');

for (const icon of PWA_manifest.icons) icon.src = resolve('src', icon.src);

module.exports = {
  plugins: [
    new PWA_Manifest(PWA_manifest),
    new WorkboxPlugin.GenerateSW(Workbox_config)
  ]
};</code></pre><p>对比之后大家会发现，Parcel 这种<strong><strong>面向资源的打包器</strong></strong>自动根据<strong><strong>各种语言的标准资源路径语法</strong></strong>查找依赖项，用应用开发者“想当然”的思路搜集各种资源文件，既不需要遵守尚未成为标准的“约定”，也不需要研究复杂的“配置”，全凭“肌肉记忆”撸代码，轻快舒爽~</p><h2 id="--3"><strong>应用更新</strong></h2><p>PWA 核心技术 <strong><strong>Service Worker</strong></strong> 的强大之处在于，它<strong><strong>运行在 HTTP 协议与 Web 页面之间</strong></strong>，可<strong><strong>拦截、缓存</strong></strong>它所在 <strong><strong>URL scope 下的所有 HTTP 请求</strong></strong>。但 <strong><strong>Service Worker 只会在它管理的所有页面都关闭后才会更新服务器端的新版本</strong></strong>，若它缓存了 HTML 文件，用户刷新时拿到的依然是缓存的旧版本，HTML 引用的其它所有外置资源的 URL 也不会改变。</p><p>所以，我们需要一个<strong><strong>安全又用户友好的更新逻辑</strong></strong>，遂改 JS 入口模块如下：</p><pre><code class="language-javascript">import { serviceWorkerUpdate } from 'web-utility';

const { serviceWorker } = window.navigator;

serviceWorker
  ?.register('sw.js')
  .then(serviceWorkerUpdate)
  .then(worker =&gt; {
    if (window.confirm('检测到新版本，是否立即启用？'))
      // 触发 Workbox CLI 生成的 Service Worker 中监听的消息回调
      worker.postMessage({ type: 'SKIP_WAITING' });
  });

serviceWorker?.addEventListener('controllerchange', () =&gt;
  window.location.reload()
);</code></pre><hr><p>好了，马上重新部署你的 Web 应用，在 Chrome、Opera、Edge、Firefox 上试试把它<strong><strong>添加到桌面</strong></strong>吧！</p><figure class="kg-card kg-image-card"><img src="https://www.atyantik.com/wp-content/uploads/2017/10/PWA-States.png" class="kg-image" alt="PWA-States" width="600" height="400" loading="lazy"></figure><h2 id="--4"><strong>范例项目</strong></h2><ol><li><a href="https://github.com/EasyWebApp/scaffold" rel="noopener">WebCell 项目脚手架</a></li><li><a href="https://web-cell.dev/" rel="noopener">WebCell 框架官网</a></li><li><a href="https://web-conf.dev/" rel="noopener">成都 Web 开发者大会官网</a></li></ol><h2 id="--5"><strong>参考文档</strong></h2><ul><li><a href="https://developers.google.cn/web/tools/workbox/guides/generate-service-worker/cli" rel="noopener">《用 Workbox CLI 生成一个完整的 Service Worker》</a></li><li><a href="https://developers.google.cn/web/fundamentals/primers/service-workers/lifecycle" rel="noopener">《Service Worker 生命周期》</a></li><li><a href="https://juejin.im/post/6844903792522035208" rel="noopener">《谨慎处理 Service Worker 的更新》</a></li><li><a href="https://lavas-project.github.io/pwa-book/chapter04/3-service-worker-dive.html" rel="noopener">《Service Worker 工作原理》</a></li></ul><p>欢迎访问我的<a href="https://tech-query.me/">更多文章</a>。</p> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ JavaScript 学习教程之效率工具 ]]>
                </title>
                <description>
                    <![CDATA[ 每当看到发在 FCC 成都社区 [https://fcc-cd.dev/]群里的技术文章，水歌 [https://github.com/TechQuery] 都忍不住去指出它的不足。 今天评注的文章题为《一批提升你工作效率的 JavaScript 工具方法》 [https://mp.weixin.qq.com/s/4oQc_SYxK4vIKCWWOKwoCw]，文中的 60 个方法不够简洁、优雅，与最新 ECMAScript、DOM 标准有些差距，有些“复制粘贴老文章片段”的感觉。 接下来，我就按功能类别来对一些有必要优化的工具方法一一重构。 数据校验 完全基于正则表达式 [https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Regular_Expressions] 的检验规则其实可以不用封装成函数，全放在独立的模块中，导入后直接 /regexp/.test(data) 即可。 电邮地址 export const Email = /^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$/ ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/javascript-tools-to-improve-efficiency/</link>
                <guid isPermaLink="false">5eeae843db4be8080eb70e9f</guid>
                
                <dc:creator>
                    <![CDATA[ TechQuery ]]>
                </dc:creator>
                <pubDate>Thu, 18 Jun 2020 05:07:32 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2020/06/thumb.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>每当看到发在 <a href="https://fcc-cd.dev/">FCC 成都社区</a>群里的技术文章，<a href="https://github.com/TechQuery" rel="noopener">水歌</a>都忍不住去指出它的不足。</p><p>今天评注的文章题为<a href="https://mp.weixin.qq.com/s/4oQc_SYxK4vIKCWWOKwoCw" rel="noopener">《一批提升你工作效率的 JavaScript 工具方法》</a>，文中的 60 个方法不够<strong><strong>简洁</strong></strong>、<strong><strong>优雅</strong></strong>，与<strong><strong>最新 ECMAScript、DOM 标准</strong></strong>有些差距，有些“复制粘贴老文章片段”的感觉。</p><p>接下来，我就按功能类别来对一些有必要优化的工具方法一一重构。</p><h2 id="-">数据校验</h2><p>完全基于<a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Regular_Expressions" rel="noopener">正则表达式</a>的检验规则其实可以不用封装成函数，全放在独立的模块中，导入后直接 <code>/regexp/.test(data)</code> 即可。</p><h3 id="--1">电邮地址</h3><pre><code class="language-javascript">export const Email = /^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$/
// 原文：/^([a-zA-Z0-9_-])+@([a-zA-Z0-9_-])+((.[a-zA-Z0-9_-]{2,3}){1,2})$/</code></pre><p>「注解」</p><ul><li><code>\w</code> 即为 <code>[a-zA-Z0-9_]</code></li><li><code>[]</code> 表示一个<strong><strong>字符范围</strong></strong>，就是一个整体，无需 <code>()</code> 包围</li><li>Gmail 等服务商还支持形如 <code>name.filter@gmail.com</code> 这样的用户别名邮箱</li><li><code>(.[a-zA-Z0-9_-]{2,3}){1,2}</code> 只适用于前些年常见的 <code>.cn</code>、<code>.com.cn</code> 一类根域名，近几年新增的 <code>.name</code>、<code>.info</code>、<code>.club</code>、<code>.camp</code> 等域名就失效了，形如 <code>vip.xxmail.com</code> 的多级域名也不适用</li></ul><h3 id="--2">手机号码</h3><p>其实以下只适用于中国大陆手机号，其它国家手机号似乎与固定电话号之间没有明显的区分。</p><pre><code class="language-javascript">export const Mobile = /^1[3-9]\d{9}$/;
// 原文：/^1[0-9]{10}$/</code></pre><p>「注解」</p><ul><li><code>\d</code> 即为 <code>[0-9]</code></li><li>中国大陆手机号第二位目前没有 1、2</li></ul><h3 id="--3">固话号码</h3><p>中国大陆固定电话号码“区号 + 机号”始终为 11 位。</p><pre><code class="language-javascript">export const Phone = /^((0\d{2}-)?\d{8}|(0\d{3}-)?\d{7})$/;
// 原文：/^([0-9]{3,4}-)?[0-9]{7,8}$/</code></pre><h3 id="--4">网址</h3><pre><code class="language-javascript">export const URL = /^\w+:\/\/\S+$/;
// 原文：/^http[s]?:\/\/.*/</code></pre><p>「注解」</p><ul><li>URL 协议不仅包括 <code>http</code>、<code>https</code>，还有 <code>ftp</code>（文件传输）、<code>file</code>（本机文件系统）、<code>ed2k</code>（电驴 2000）等各种各样的<strong><strong>网络协议</strong></strong></li><li>URL 主机名、路径可以是 <a href="https://home.unicode.org/" rel="noopener">Unicode</a> 中各种<strong><strong>可见字符</strong></strong>，但遇到空白符就结束</li></ul><h3 id="--5">日期格式</h3><p>判断是否为合法的日期格式除了用正则之外，还可利用 <a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Date/Date" rel="noopener"><code>Date</code> 构造函数</a>内部的算法：</p><pre><code class="language-javascript">export const isDate = raw =&gt; !isNaN(+new Date(raw))</code></pre><p>对于无法解析为日期的数据，<code>date.toString()</code> 会返回“Invalid Date”，<code>date.getTime()</code> 对应的返回值则是 <code>NaN</code>。而<strong><strong>算数运算符</strong></strong>会调用对象的 <code>valueOf()</code> 方法，<code>date.valueOf()</code> 的返回值又与 <code>date.getTime()</code> 相同。</p><h3 id="--6">汉字</h3><p>“汉字”在计算机领域的学名叫<strong><strong>中日韩统一表意文字</strong></strong>（俗称 CJK），在 2017 年 6 月发布的 Unicode 10 标准中，它有了代码级明确的指代：</p><pre><code class="language-javascript">export const HanZi = /\p{Unified_Ideograph}/u;</code></pre><p><a href="https://zhuanlan.zhihu.com/p/33335629" rel="noopener">【详情参考】</a></p><h3 id="--7">最佳实践</h3><ul><li>学习：正则分析器 <a href="https://regexr.com/" rel="noopener">RegExr</a>、<a href="https://regex101.com/" rel="noopener">Regex101</a></li><li>前端：<a href="https://developer.mozilla.org/zh-CN/docs/Learn/HTML/Forms/Data_form_validation" rel="noopener">HTML 5 表单校验 API</a></li><li>后端：<a href="https://github.com/typestack/class-validator" rel="noopener">基于装饰器的数据校验</a></li></ul><h2 id="--8">数据转换</h2><h3 id="--9">阿拉伯数字转中文</h3><p>ECMA-402 标准（<a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Intl" rel="noopener">ECMAScript 国际化 API</a>）把各语言之间的<strong><strong>数据格式转换算法</strong></strong>都封装好了，我们引入 polyfill 就可以直接用：</p><pre><code class="language-javascript">export const toChineseNumber = raw =&gt;
    new Intl.NumberFormat('zh-Hans-u-nu-hanidec').format(raw)</code></pre><h2 id="--10">数据类型</h2><p>判断一个值的类型，用比较<strong><strong>构造函数名</strong></strong>或<strong><strong>类名</strong></strong>的方式兼容性比较差，因为线上环境通常是压缩后的代码，自定义的函数名、类名不再是原名，应用开发者一般也不会实现 <a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Symbol/toStringTag" rel="noopener"><code>Symbol.toStringTag</code> getter 类成员</a>，导致 <code>Object.prototype.toString.call()</code> 只会返回默认值 <code>[object Object]</code>。</p><h3 id="javascript">JavaScript</h3><p>综上，我们应该利用 <a href="https://developer.mozilla.org/zh-CN/docs/Learn/JavaScript/Objects/Inheritance" rel="noopener">JavaScript 原型继承</a>，来统一判断“值的类型归属”：</p><pre><code class="language-javascript">export const isType = (value, constructor) =&gt;
    Object(value) instanceof constructor;</code></pre><p>「注解」</p><ul><li><code>Object</code> 构造函数会返回所有基本值的包装对象</li></ul><h3 id="typescript">TypeScript</h3><p>下面，我再给出一个 TypeScript 的实现，让<strong><strong>类型推断</strong></strong>更加准确：</p><pre><code class="language-typescript">export function isType(
    value: T, constructor: { new(...data: any[]): T }
): value is T {
    return Object(value) instanceof constructor;
}</code></pre><pre><code class="language-typescript">import { isType } from './utility';

let test;

if (isType(test, Number)) console.log(test!.toFixed(2));</code></pre><p><a href="https://www.typescriptlang.org/play/index.html#code/GYVwdgxgLglg9mABDAzgFQJ4AcCmAeNAPgAoA3AQwBsQcAuRNAGkQgRSgCcRo4P6BvRGBwB3YgDpJAE3JRy9cmAwBtALoBKemkQBfTYgrUcyFA0T8AUImuIOOKCA5IA8gCMAVjmhkqNdcjB2RQgcOGAWNk5uKF4AbgsdCwtKe0QoHHZ4ixhw4lRMXGJ09mYAORAAW1ccDnV-VkC4FPFKOABzIoyoAEJxGIAxGAAPHCliACY62KA" rel="noopener">【在编辑器中体验 TS 类型提示】</a></p><h2 id="--11">浏览器检测</h2><p>以下使用 <code>globalThis</code> 是为了兼容浏览器主线程、<a href="https://developer.mozilla.org/zh-CN/docs/Web/API/WorkerGlobalScope" rel="noopener">Web Worker</a>、<a href="https://nodejs.org/dist/latest-v12.x/docs/api/globals.html#globals_global" rel="noopener">Node.js</a>、<a href="https://doc.deno.land/https/github.com/denoland/deno/releases/latest/download/lib.deno.d.ts" rel="noopener">Deno</a> 等不同 JavaScript 运行时环境。</p><h3 id="--12">品牌</h3><pre><code class="language-javascript">export const isBrowserVendor = (name, UA = globalThis.navigator?.userAgent || '') =&gt;
    UA.toLowerCase().includes(name);</code></pre><h3 id="--13">爬虫</h3><pre><code class="language-javascript">export const isRobot = (UA = globalThis.navigator?.userAgent || '') =&gt;
    /bot|spider|crawler/i.test(UA);</code></pre><h2 id="-html-">去除 HTML 标签</h2><h3 id="--14">正则表达式</h3><p>以下使用了 <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions/Cheatsheet" rel="noopener">non-greedy（非贪婪模式）</a>来提升性能，并规避正文中可能出现的示例代码没完全转译尖括号，导致删除错误。</p><pre><code class="language-javascript">export const removeHtmlTag = raw =&gt; raw.replace(/&lt;[\s\S]+?&gt;/g, '');</code></pre><h3 id="dom-api">DOM API</h3><p>下面再提供一种借助 DOM 引擎的实现：</p><pre><code class="language-javascript">const box = document.createElement('template');

export function removeHtmlTag(raw) {
    box.innerHTML = raw;

    return box.content.textContent;
}</code></pre><h2 id="url-">URL 参数追加</h2><p><a href="https://developer.mozilla.org/zh-CN/docs/Web/API/URL" rel="noopener"><code>URL()</code></a>、<a href="https://developer.mozilla.org/zh-CN/docs/Web/API/URLSearchParams" rel="noopener"><code>URLSearchParams()</code></a> 在浏览器主线程、Web Worker、Node.js 10+、Deno 均全局可用。</p><pre><code class="language-javascript">export function appendQuery(path, data, base = globalThis.location.href) {
    const URI = new URL(path, base);
    const { searchParams } = URI;

    for (const key in data) searchParams.append(key, data[key]);

    return URI + '';
}</code></pre><h2 id="w3c-ecma-">W3C、ECMA 标准</h2><p>还有一些可以用新标准（部分为提案）直接实现的特性，集中罗列如下：</p><ul><li><a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/String/Trim" rel="noopener"><code>.trim()</code></a>、<a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/String/TrimLeft" rel="noopener"><code>.trimStart()</code></a>、<a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/String/TrimRight" rel="noopener"><code>.trimEnd()</code></a>（原文第 53 条）</li><li><a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/includes" rel="noopener"><code>.includes()</code></a>（原文第 42 条）</li><li><a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/from" rel="noopener"><code>Array.from()</code></a>（原文第 48 条）</li><li><a href="https://github.com/TechQuery/array-unique-proposal" rel="noopener">数组去重</a>（原文第 44 条）</li><li><a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/import#%E5%8A%A8%E6%80%81import" rel="noopener">动态 <code>import</code></a>（原文第 27 条）</li><li><a href="https://developer.mozilla.org/zh-CN/docs/Web/API/Element/classList" rel="noopener"><code>element.classList</code></a>（原文第 29 ~ 31 条）</li><li><a href="https://github.com/eligrey/FileSaver.js" rel="noopener"><code>saveAs()</code></a>（原文第 28 条）</li><li><a href="https://developer.mozilla.org/zh-CN/docs/Web/CSS/text-transform" rel="noopener"><code>text-transform</code></a>（原文第 54 条）</li></ul><h2 id="--15">开源库</h2><figure class="kg-card kg-image-card"><img src="https://web-cell.dev/WebCell-1.fb612fdb.png" class="kg-image" alt="WebCell" width="600" height="400" loading="lazy"></figure><p>水歌把日常开发中积累的各种工具方法，用 TypeScript 写成一个 <strong><strong>Web 开源工具库</strong></strong> —— <a href="https://web-cell.dev/web-utility/" rel="noopener">https://web-cell.dev/web-utility/</a> ，欢迎大家使用、改进！~</p><p>欢迎访问我的<a href="https://tech-query.me/">更多文章</a>。</p> ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
