<?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[ Node.js - 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[ Node.js - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/chinese/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Wed, 10 Jun 2026 20:41:08 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/chinese/news/tag/nodejs/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ 扩展 Node.js REST API 的最佳实践 ]]>
                </title>
                <description>
                    <![CDATA[ 除了使用集群模式，还有各种各样扩展API的方式。在这篇教程中，我们将学习10种扩展Node.js API的方式。 我们经常会在处理项目的时候获取一些零散的知识以提高技能，必须通过不断地复习，才能将习得的技能应用到下次项目中。 这个方法一直奏效吗？我甚至不记得我昨天做了些什么。所以我写下了这篇教程，也是对自己知识的复盘。 我尝试记录下这些不常被提起扩展Node.js的方法。 本文提及的方法不一定是你最后的救命稻草，你可以在Node.js项目的任何阶段应用这些方法。 让我们来看看本文的内容：  1.  🚦使用节流  2.  🐢 优化数据库查询  3.  ䷪ 使用断路器快速故障  4.  🔍 记录检查点  5.  🌠 使用Kafka而非HTTP请求  6.  🪝 小心内存泄露  7.  🐇 使用缓存  8.  🎏 使用连接池  9.  🕋 ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/nodejs-api-best-practices-for-scaling/</link>
                <guid isPermaLink="false">632d7b0345d5bb07655499cc</guid>
                
                    <category>
                        <![CDATA[ Node.js ]]>
                    </category>
                
                    <category>
                        <![CDATA[ API ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ PapayaHUANG ]]>
                </dc:creator>
                <pubDate>Fri, 23 Sep 2022 09:18:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2022/09/linux-shell-utilities-cover-1.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>原文：</strong> <a href="https://www.freecodecamp.org/news/nodejs-api-best-practices-for-scaling/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">Best Practices for Scaling Your Node.js REST APIs</a>
      </p><!--kg-card-begin: markdown--><p>除了使用集群模式，还有各种各样扩展API的方式。在这篇教程中，我们将学习10种扩展Node.js API的方式。</p>
<p>我们经常会在处理项目的时候获取一些零散的知识以提高技能，必须通过不断地复习，才能将习得的技能应用到下次项目中。</p>
<p>这个方法一直奏效吗？我甚至不记得我昨天做了些什么。所以我写下了这篇教程，也是对自己知识的复盘。</p>
<p>我尝试记录下这些不常被提起扩展Node.js的方法。</p>
<p>本文提及的方法不一定是你最后的救命稻草，你可以在Node.js项目的任何阶段应用这些方法。</p>
<p>让我们来看看本文的内容：</p>
<ol>
<li>🚦使用节流</li>
<li>🐢 优化数据库查询</li>
<li>䷪ 使用断路器快速故障</li>
<li>🔍 记录检查点</li>
<li>🌠 使用Kafka而非HTTP请求</li>
<li>🪝 小心内存泄露</li>
<li>🐇 使用缓存</li>
<li>🎏 使用连接池</li>
<li>🕋 无缝扩展</li>
<li>💎 OpenAPI兼容文档</li>
</ol>
<h2 id="">使用节流</h2>
<p>节流可以限制对服务器的访问，以防止请求过量。使用节流的好处非常明显：可以保护应用免受大量用户爆发的困扰，也可以防止<a href="https://en.wikipedia.org/wiki/Denial-of-service_attack">拒绝服务攻击（Dos攻击）</a>。</p>
<p>输入和输出的速率不匹配的时候，通常是应用节流机制的时候。特别是当入站流量远超过服务器可以（或者希望）处理的流量。</p>
<p>让我们通过图像来理解：</p>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2022/09/throttling-nodejs-best-practices-nodejs.drawio--5-.png" alt="你的应用程序正在限制来自新闻推送服务的请求" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>你的应用程序正在限制来自新闻推送服务的请求</figcaption>
</figure>
<p>在应用程序和新闻推送服务器之间的第一个节点应用了节流：</p>
<ol>
<li>新闻推送服务（NFS）订阅了你的应用以发送通知。</li>
<li>每秒向你的应用发送1000个请求。</li>
<li>根据NFS的订阅计费计划，你的应用仅处理500个请求/秒。</li>
<li>为前500个请求发送通知。</li>
</ol>
<p>必须要注意的是，所有超过500个请求/秒以外的请求都会失败，需要NFS再次尝试发送请求。</p>
<p><strong>当可以排队的时候，为什么要拒绝额外的请求？</strong> 有以下理由：</p>
<ol>
<li>接受所有请求会使应用开始累积请求，这可能导致所有订阅你的应用的客户端出现单点故障（通过RAM/磁盘耗尽），包括NFS。</li>
<li>你不应该接受超出客户订阅计划范围的请求（在我们的例子中是NFS）。</li>
</ol>
<p>对于应用程序级别的速率限制，你可以使用Express.js API的中间件——<a href="https://www.npmjs.com/package/express-rate-limit">express-rate-limit</a>。对于网络级别的节流，你可以使用类似<a href="https://aws.amazon.com/waf/">WAF</a>的解决方案。</p>
<p>如果你使用的是发布-订阅机制，也可以限制消费者和订阅者。例如，你可以通过设置<a href="https://kafka.js.org/docs/consuming#a-name-options-a-options">maxBytes选项</a>来限制消费Kafka标签（topic）的字节数据。</p>
<h2 id="">优化数据库查询</h2>
<p>有时你可能没有缓存数据，或者数据已经过期，查询数据成了唯一的选择。</p>
<p>发生这种情况时，请确保你的数据库做好了准备：第一步是拥有足够的RAM和磁盘IOPS（每秒输入输出量）。</p>
<p>其次，尽可能优化你的查询。对于初学者来说，做对这几件事很关键：</p>
<ol>
<li>查询时尽量使用索引字段，但不要过度索引。<a href="https://www.mongodb.com/blog/post/performance-best-practices-indexing#:~:text=Eliminate%20Unnecessary%20Indexes">索引也有开销</a>。</li>
<li>对于删除，坚持软删除。如果需要永久删除，请推迟。（<a href="https://httpie.io/blog/stardust">一个有趣的故事</a>）</li>
<li>在读取数据的时候，仅使用投影（projection）获取需要的字段。如果可以的话，去掉没有必要的元数据和方法。（例如，Mongoose提供<a href="https://mongoosejs.com/docs/tutorials/lean.html">lean</a>)。</li>
<li>尝试将数据库性能和用户体验分离。如果数据库上的CRUD可以在后台发生（即非阻塞），请执行此操作。不要让用户等待。</li>
<li>使用更新查询直接更新所需字段。不要获取文档，更新字段，然后将整个文档保存回数据库。这会造成网络和数据库开销。</li>
</ol>
<h2 id="">使用断路器快速故障</h2>
<p>想象一下，你的Node.js应用程序出现突发流量，并且满足请求所需的外部服务器之一已关闭。你是否想在此后的每个请求中一直走死胡同？当然不！我们不想在注定要失败的请求上浪费时间和资源。</p>
<p>这就是使用断路器的核心思想：<strong>尽早失败</strong>、<strong>快速失败</strong>。</p>
<p>例如，如果100个请求中有50个失败，则在接下来的X秒内不允许对该外部服务器发出任何请求。它可以防止触发必然会失败的请求。</p>
<p>一旦线路复位，就允许请求通过。如果它们再次失败，则线路断开并重复循环。</p>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2022/09/circuit-breaker-nodejs-best-practices-for-scale.drawio--1-.png" alt="Node.js Opposum断路器状态" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>Node.js Opposum断路器状态</figcaption>
</figure>
<p>可以查看<a href="https://github.com/nodeshift/opossum">Opposum</a>以了解更多添加断路器的信息。你也可以在<a href="https://en.wikipedia.org/wiki/Circuit_breaker_design_pattern">这里</a>阅读更多关断路器的信息。</p>
<h2 id="">记录检查点</h2>
<p>良好的日志记录设置可以帮助你快速发现错误。你可以创建可视化日志来了解应用程序的行为、设置警报和有效地调试。</p>
<p>你可以使用<a href="https://www.elastic.co/what-is/elk-stack">ELK stack</a>来设置良好的日志记录和警报管道。</p>
<p>虽然日志记录是必不可少的工具，但很容易过度使用。如果记录所有内容，最终可能会耗尽磁盘IOPS，从而导致你的应用程序受到影响。</p>
<p><strong>一个值得借鉴的经验是只记录检查点。</strong></p>
<p>检查点可以是：</p>
<ol>
<li>当进入应用程序中的主控制流时以及在它们经过验证和清理之后的请求</li>
<li>与外部服务/SDK/API交互时的请求和响应。</li>
<li>对该请求的最终响应。</li>
<li>为catch处理程序提供有用的错误消息（错误消息具有合理的默认值）。</li>
</ol>
<p><strong>另外：</strong> 如果一个请求在生命周期中经过多个服务器，你可以在日志中传递一个唯一ID，以跨服务器捕获特定请求。</p>
<h2 id="kafkahttp">使用Kafka而非HTTP请求</h2>
<p>虽然存在HTTP请求的用例，但容易使用过度，请在不必要的时候避免使用HTTP请求。</p>
<p>让我们通过这个例子来理解：</p>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2022/09/kafka-over-http-nodejs-best-practices-for-scale.drawio.png" alt="Kafka主从结构使用标签（topic）模式" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>Kafka主从结构使用标签（topic）模式</figcaption>
</figure>
<p>假设我们要创建一个如Amazon一样的产品，这个产品包含两大服务：</p>
<ol>
<li>供应商服务</li>
<li>库存服务</li>
</ol>
<p>每当你收到来自供应商服务的新库存，就推送一个库存详情到<a href="https://kafka.apache.org/intro">Kafka</a>标签。库存服务监听到这个标签，并且更新数据库以确认有新的补货。</p>
<p>请注意，你将库存数据推送到管道（pipeline）然后程序流，就可以继续其他的操作。程序会自动按照一定节奏消费库存服务。<strong>Kafka使服务解耦。</strong></p>
<p>现在，如果你的库存服务出现了故障怎么办？如果是用HTTP请求就不容易处理。但如果使用Kafka，就可以重播预期的消息（例如使用<a href="https://github.com/edenhill/kcat">kcat</a>）。<strong>使用Kafka的话，数据被消费后不会丢失。</strong></p>
<p>当某个商品重新回到库存，你可能希望向愿望清单包含这个商品的顾客推送消息。要实现这个功能，可以让通知服务和库存服务监听同样的标签。通过这种方式，<strong>就可以实现在不同的地方使用单个消息总线，并没有HTTP开销</strong>。</p>
<p>KafkaJS的<a href="https://kafka.js.org/docs/getting-started">开始页面</a>分享了从Node.js应用的基础设置到上述功能的代码片段，我强烈推荐你查看，有很多内容值得研究。</p>
<h2 id="">小心内存泄露</h2>
<p>如果你不编写保护内存安全的代码，并且不经常<a href="https://nodejs.org/en/docs/guides/simple-profiling/">分析</a>你的应用，很有可能造成服务器崩溃。</p>
<p>你可不希望自己的分析结果如下：</p>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2022/09/Image-Pasted-at-2022-9-6-14-58.png" alt="执行结束后setTimeout还保留98%的内存" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>执行结束后setTimeout还保留98%的内存</figcaption>
</figure>
<p>对于初学者，我建议：</p>
<ol>
<li>使用<code>--inspect</code>标志来执行 Node.js API。</li>
<li>在Chrome浏览器打开<code>chrome://inspect/#devices</code>。</li>
<li>点击 inspect &gt; <code>Memory</code> tab &gt; <code>Allocation instrumentation on timeline</code>。</li>
<li>在执行一些应用功能。可以使用macOS的apache bench来同时发出多个请求，在终端执行 <code>curl cheat.sh/ab</code>查看怎么使用apache bench。</li>
<li>停止记录并分析内存保持器。</li>
</ol>
<p>如果发现任何大块的保留内存，请尝试将其最小化。这个话题相关的资源很多，你可以从谷歌搜索“如何防止 Node.js 中的内存泄漏”开始探索。</p>
<p>分析你的 Node.js 应用程序并寻找内存使用模式应该是常规做法。让我们把“分析驱动重构”（PDR）提上日程？</p>
<h2 id="">使用缓存避免过多的数据库查找</h2>
<p>这么做的目的是不要每一次应用发出请求都连接一次数据库，使用缓存存储结果可以减少数据库的负载，提高应用性能。</p>
<p>有两种使用缓存的策略。</p>
<p>通过<strong>缓存写入</strong>可确保在发生写入操作时将数据插入数据库和缓存中。这可以使缓存保持相关，并带来更好的性能。缺点是因为不经常使用的数据也被存储到缓存中，所以缓存开销昂贵。</p>
<p>而在<strong>延迟加载</strong>中，数据仅在第一次读取时才写入缓存。第一次请求提供来自数据库的数据，但随后的请求使用缓存。它具有较小的成本，但增加了第一次请求的响应时间。</p>
<p>要决定缓存数据的TTL（或生存时间），请问自己：</p>
<ol>
<li>基础数据要多久更改一次？</li>
<li>将过期数据返回给最终用户的风险是什么？</li>
</ol>
<p>在允许的情况下，<strong>更长的TTL意味着更好的应用表现</strong>。</p>
<p>重要的是，为你的TTL<strong>添加一点增量</strong>。如果应用程序一时间收到大量流量，并且你的所有缓存数据立即过期，则可能导致数据库无法承受负载，从而影响用户体验。</p>
<pre><code>最终TTL = TTL预估值 + 一点随机增量
</code></pre>
<p>TTL的计算</p>
<p>有许多<a href="https://redis.io/docs/manual/eviction/">缓存逐出</a>的策略，保留默认值是一种有效且可接受的方法。</p>
<h2 id="">使用连接池</h2>
<p>打开一个与数据库的独立连接开销很高。它涉及TCP握手、SSL、身份验证和授权检查等。</p>
<p>你可以利用连接池来取代独立连接。</p>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2022/09/conection-pooling-nodejs-best-practices-for-scale.drawio--1-.png" alt="数据连接池" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>数据连接池</figcaption>
</figure>
<p>一个连接池在任何给定时间都拥有多个连接。每当需要它时，池管理器都会分配任何可用/空闲连接。你可以跳过建立全新连接的冷启动阶段。</p>
<p>那么，为什么不最大化池中的连接数呢？因为它高度依赖于硬件资源。如果忽略这一点，可能会造成巨大的性能损失。</p>
<p>连接越多，每个连接的RAM越少，利用RAM的查询越慢（例如排序）。同样的原则也适用于磁盘和CPU。每个新连接都将被分配到资源。</p>
<p>你可以根据自己的需求调节连接数，对于初学者来说你可以在<a href="https://www.cybertec-postgresql.com/en/tuning-max_connections-in-postgresql/">这里</a>评估连接池的大小。</p>
<p>你可以在<a href="https://www.mongodb.com/docs/manual/administration/connection-pool-overview/">这里</a>阅读连接池相关的内容。若你使用的是PostgreSQL，你可以使用<code>node-postgres</code>包。这是一个<a href="https://node-postgres.com/features/pooling">连接池</a>的内置支持。</p>
<h2 id="">无缝扩展</h2>
<p>当应用程序的用户群逐渐增长并且已经达到垂直扩展的上限时，你会怎么做？水平缩放。</p>
<blockquote>
<p>垂直扩展意味着增加节点（CPU、内存等）资源，而水平扩展意味着增加更多的节点以平衡每个节点的负载。</p>
</blockquote>
<p>如果你使用的是AWS，则可以利用自动扩展组（ASG），它根据预定义的规则（例如，当CPU利用率超过50% 时水平扩展服务器数量。</p>
<p>你可以通过<a href="https://docs.aws.amazon.com/autoscaling/application/userguide/examples-scheduled-actions.html">提前规划行为</a>来提前规划扩展和缩小的计划，以此来应对可以遇见的流量模式（如在世界杯期间的流媒体服务）。</p>
<p>准备好ASG后，在最前面添加负载均衡器将确保流量根据所选策略路由到所有实例（如<a href="https://en.wikipedia.org/wiki/Round-robin_scheduling">round robin</a>）。</p>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2022/09/alb-nodejs-best-practices-for-scale.drawio--2-.png" alt="基于预定义规则对多个目标进行负载均衡" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>基于预定义规则对多个目标进行负载均衡</figcaption>
</figure>
<p><strong>PS：</strong> 预估单个服务器可以处理的请求（CPU、内存、磁盘等）并分配至少 30%以上的容量总不会错。</p>
<h2 id="openapi">OpenAPI兼容文档</h2>
<p>它可能不会直接影响扩展Node.js应用程序的能力，但我必须将其包含在列表中。如果你曾经做过API集成，你就知道为什么了。</p>
<p>在向前迈出一步之前，了解有关 API 的所有信息至关重要。它使设计的集成、迭代和推理变得容易，更不用说对开发速度的帮助。</p>
<p>确保<strong>为你的 Node.js API 创建 OpenAPI 规范（OAS）</strong>。</p>
<p>这使得你以行业标准的方式创建 API 文档。OAS充当单一的事实来源。如果定义得当，它会使与 API 的交互更加高效。</p>
<p>我在<a href="https://app.swaggerhub.com/apis/Rishabh570/test-API/0.1">这里</a>创建了一个API文档样本，你可以使用<a href="https://swagger.io/tools/swagger-inspector/">swagger inspector</a>来监测任意API。</p>
<p>你可以在<a href="https://app.swaggerhub.com/home">Swagger Hub dashboard</a>找到所有API文档，以及创建一个新的文档。</p>
<h2 id="">行动起来吧！</h2>
<p>我们研究了十个鲜为人知的扩展Node.js的最佳实践，以及如何开启每一个最佳时间。</p>
<p>现在轮到你对照这个清单并探索发现你的Node.js应用程序缺少了什么。</p>
<p>获取你的检查清单✨</p>
<p>希望这篇文章对你有所帮助，并在可扩展性方面为你提供了一些指导。这并不是所有最佳实践的详尽清单——我只是列出了这些我认为不常被提及的。</p>
<p>欢迎在<a href="https://twitter.com/Rishabh570">Twitter</a>上联系我。我很乐意听取你在使用其他最佳实践的经历和心得。</p>
<p>喜欢这篇文章吗？<a href="https://buttondown.email/rishabh570">在这里获取更多后端提升小建议</a> 💌。</p>
<!--kg-card-end: markdown--> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 如何使用 Render 免费部署你的 Node.js 应用程序 ]]>
                </title>
                <description>
                    <![CDATA[ 长久以来，Heroku都是托管全栈应用的优秀平台。freeCodeCamp早期大量使用Heroku，其他各式各样的开源项目也是如此。 但由于Heroku决定停止免费提供服务，这一现象可能会发生改变。 你可能已经收到了Heroku的邮件，通知你从2022年11月28日开始，在该平台托管应用不再免费，你可以购买付费计划。 如果你想免费托管静态页面或者web应用可以使用Netlify [https://www.netlify.com/]，具体操作可以查看这篇文章 [https://www.freecodecamp.org/news/how-to-deploy-react-router-based-app-to-netlify/] ，但对于后端应用来说，并不存在太多如Heroku一样部署体验比较好的免费网站。 在这篇文章中，我们将学习如何使用Render [https://render.com/]来部署搭配Express服务器的Node.js应用。 它的部署过程和Heroku一样简单，并且免费。 让我们开始吧。 在部署应用之前需要做什么？ 根据使用Heroku的经验来看，每一个 ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/how-to-deploy-nodejs-application-with-render/</link>
                <guid isPermaLink="false">632aceb55ef0a407fd63024d</guid>
                
                    <category>
                        <![CDATA[ Node.js ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ PapayaHUANG ]]>
                </dc:creator>
                <pubDate>Tue, 20 Sep 2022 07:18:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2022/09/pexels-pixabay-163235--1-.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>原文：</strong> <a href="https://www.freecodecamp.org/news/how-to-deploy-nodejs-application-with-render/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">How to Deploy Your Node.js Application for Free with Render</a>
      </p><!--kg-card-begin: markdown--><p>长久以来，Heroku都是托管全栈应用的优秀平台。freeCodeCamp早期大量使用Heroku，其他各式各样的开源项目也是如此。</p>
<p>但由于Heroku决定停止免费提供服务，这一现象可能会发生改变。</p>
<p>你可能已经收到了Heroku的邮件，通知你从2022年11月28日开始，在该平台托管应用不再免费，你可以购买付费计划。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/08/no_free_heroku.png" alt="no_free_heroku" width="600" height="400" loading="lazy"></p>
<p>如果你想免费托管静态页面或者web应用可以使用<a href="https://www.netlify.com/">Netlify</a>，具体操作可以<a href="https://www.freecodecamp.org/news/how-to-deploy-react-router-based-app-to-netlify/">查看这篇文章</a>，但对于后端应用来说，并不存在太多如Heroku一样部署体验比较好的免费网站。</p>
<p>在这篇文章中，我们将学习如何使用<a href="https://render.com/">Render</a>来部署搭配Express服务器的Node.js应用。 它的部署过程和Heroku一样简单，并且免费。</p>
<p>让我们开始吧。</p>
<h2 id="">在部署应用之前需要做什么？</h2>
<p>根据使用Heroku的经验来看，每一个应用都部署在一个由Heroku随机分配的端点，可以使用<code>process.env.PORT</code>变量访问。</p>
<p>使用Render平台也是一样的。</p>
<p>所以你必须确保自己不是通过硬编码端点值来开启Express服务器，而是使用<code>process.env.PORT</code>变量：</p>
<pre><code class="language-js">const express = require("express");
const app = express();
const PORT = process.env.PORT || 3030;

// 你的代码

app.listen(PORT, () =&gt; {
  console.log(`server started on port ${PORT}`);
});
</code></pre>
<h2 id="rendergithub">如何使用Render部署一个GitHub仓库</h2>
<p>改好端点之后，就可以开始部署你的应用了。</p>
<p>我准备好了<a href="https://github.com/myogeshchavan97/github-repos-nodejs-api">这个GitHub仓库</a>来使用Render部署。这个GitHub仓库展示了一个排名前几位的仓库清单，以及每一个仓库获得的星星数量（以JSON的格式）。</p>
<p>让我们开始吧！</p>
<p><a href="https://render.com/">Render</a>提供如下图各种注册方式：</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/08/sign_up_render.png" alt="sign_up_render" width="600" height="400" loading="lazy"></p>
<p>一旦注册并登陆账号之后，你会看到控制面板：</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/08/dashboard.png" alt="dashboard" width="600" height="400" loading="lazy"></p>
<p>点击<code>Web Services</code>选项卡下的 <code>New Web Service</code>按钮来部署Node.js应用。</p>
<p>你也可以通过点击网页头部你头像旁边的 <code>New +</code> 按钮来选择 <code>Web Service</code>选择卡。</p>
<p>点击之后，会看到以下画面：</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/08/new_web_service.png" alt="new_web_service" width="600" height="400" loading="lazy"></p>
<p>点击GitHub菜单下方的<code>Connect account</code>按钮，会看到以下画面：</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/08/install_render.png" alt="install_render" width="600" height="400" loading="lazy"></p>
<p>点击<code>Configure</code>链接，就可以允许Render选取你所有或者选择的GitHub仓库。</p>
<p>我希望只访问选择的仓库，也就是我需要部署的。所以我选择<code>Only select repositories</code>选项。</p>
<p>然后，点击在选择卡下方的<code>Select repositories</code>按钮，选择你想要部署的仓库。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/08/connect_github.png" alt="connect_github" width="600" height="400" loading="lazy"></p>
<p>选择完毕后，你会看到如下画面：</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/08/selected_repository.png" alt="selected_repository" width="600" height="400" loading="lazy"></p>
<p>点击绿色按钮<code>Install</code>，使得Render有权访问你选择的仓库。</p>
<p>点击完毕后，你会被重定向到控制台，看到如下画面：</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/08/connected_repository.png" alt="connected_repository" width="600" height="400" loading="lazy"></p>
<p>现在，点击<code>Connect</code>按钮，看到如下画面：</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/08/deploy_details.png" alt="deploy_details" width="600" height="400" loading="lazy"></p>
<p>在<code>Name</code>框中，输入一个简短的名字来标识你的网站。</p>
<p><strong>注意：</strong> 请保持<code>Name</code>值的简单，因为部署完毕后，它将成为应用的URL。 所以如果我将<code>github-repos</code>设置为<code>Name</code>的值，我的应用URL会成为<a href="https://github-repos.onrender.com"><code>https://github-repos.onrender.com</code></a>。</p>
<p>所以请填写一个简单有意义的<code>Name</code>值。</p>
<p>如图填写剩下的部分：</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/08/details.png" alt="details" width="600" height="400" loading="lazy"></p>
<p>在<code>Build Command</code>中填写<code>yarn</code>就等同于<code>yarn install</code>命令。Yarn是一个类似于npm的包管理工具，但是比npm要快。</p>
<p>如果你的入口文件是 <code>index.js</code>，在<code>Start Command</code>中填写<code>node index.js</code>。</p>
<p>填写完所有细节之后，向下滚动页面，会看到<code>Plans</code>区域，自动勾选的是免费计划。如果没有被勾选，请勾选好，因为我们的目的是免费部署应用。</p>
<p>再向下滑动页面会看到<code>Advanced</code>按钮。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/08/advanced_options.png" alt="advanced_options" width="600" height="400" loading="lazy"></p>
<p>如果你的应用使用了环境变量，你可以在<code>Advanced</code>设置中输入。也可以在这里添加 <code>.env</code>文件，这样就不用你手动一个一个地添加。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/08/env_vars-1.png" alt="env_vars-1" width="600" height="400" loading="lazy"></p>
<p>注意<code>Auto-Deploy</code>的默认值是<code>Yes</code>——所以一旦你向GitHub仓库推送了更新，它们就会被自动部署到Render。</p>
<p>如果你并不想每次更改仓库的时候都自动部署，你可以在 <code>Auto-Deploy</code>下拉菜单中选择<code>No</code>。</p>
<p>现在，点击<code>Create Web Service</code>按钮来开始部署过程。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/08/10-1.png" alt="10-1" width="600" height="400" loading="lazy"></p>
<p>部署可能需要等待一段时间，有时候如果页面卡在了“in progress”阶段，可以尝试刷新一下页面。</p>
<p>一旦部署完成，你可以看到如图，应用被部署，有一个<code>Live</code> 小标：</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/08/11.png" alt="11" width="600" height="400" loading="lazy"></p>
<p>你可以点击在顶部的应用URL，在我的例子中，这个URL是<a href="https://github-repos.onrender.com/">https://github-repos.onrender.com/</a>。</p>
<p>如果是首次部署应用，可能在访问网站的时候会遇到<code>Page is not working</code>报错。</p>
<p>等待一段时间，并且使用<code>Ctrl + R</code>或<code>Cmd + R（Mac）</code>来刷新页面。因为免费服务的硬件有限，所以Render平台需要一些时间来启动一个项目。</p>
<p>部署成功后，可以如下图一样看到你的应用：</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/08/deployed_live.png" alt="deployed_live" width="600" height="400" loading="lazy"></p>
<p><strong>提示:</strong> 想要JSON有格式地显示，你需要安装Chrome插件：<a href="https://chrome.google.com/webstore/detail/json-formatter/bcjindcccaagfpapjjmafapmmgkkhgoa?hl=en">JSON Formatter</a>。</p>
<p>你或许知道，在使用Heroku的免费账户的时候，没如果没有新的请求进来，你的应用会在30分钟后进入到休眠模式。也就是如果再次请求需要花上一点时间。</p>
<p>同样的，使用Render的话，如果没有新的请求，应用会在15分钟后进入休眠模式。</p>
<h3 id=""><strong>感谢阅读！</strong></h3>
<p>你可以在<a href="https://github.com/myogeshchavan97/github-repos-nodejs-api">这个仓库中</a>找到完整的源代码。</p>
<p><strong><strong>你可以在<a href="https://github-repos.onrender.com/">这里</a>查看实时demo</strong></strong>。</p>
<p>如果你想从零开始学Redux，并且完成三个应用，以及<a href="https://www.youtube.com/watch?v=2zaPDfCKAvM">一个完整的食物点单应用</a>，可以查看我的<a href="https://master-redux.yogeshchavan.dev/">Mastering Redux</a>课程。</p>
<p>在这篇课程中，你将学习：</p>
<ul>
<li>基础和高阶的Redux</li>
<li>如何管理复杂的数据和对象状态</li>
<li>如何使用多个reducer来管理复杂的redux状态</li>
<li>如果排除Redux应用的错误</li>
<li>如果通过使用react-redux库来在React中使用Redux并使得你的应用是响应式的</li>
<li>如何使用redux-thunk库来处理异步API调用</li>
<li>使用Redux编写三个不同的app</li>
</ul>
<p>以及更多。</p>
<p>最后我们将从零开始搭建一个<a href="https://www.youtube.com/watch?v=2zaPDfCKAvM">食物点单应用</a> ，包括付费模块和应用的部署。</p>
<p><strong><strong>想要了解JavaScript、React或者Node.js最新的消息吗？ 请在<a href="https://www.linkedin.com/in/yogesh-chavan97/">LinkedIn上关注我</a>。</strong></strong></p>
<!--kg-card-end: markdown--> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 如何优化你的 Node.js API ]]>
                </title>
                <description>
                    <![CDATA[ 在这篇文章中，我将讲解如何优化使用 Node.js 编写的 API。 前提条件 想要充分了解本文内容，你必须了解以下概念：  * Node.js 的设置与安装  * 如何使用 Node 创建 API  * 如何使用 Postman  * JavaScript 的 async/await 工作原理  * Redis 的基础操作 API 优化到底指的是什么 优化包含了改善 API 的响应时间。响应时间越短，API 的速度越快。 我将在本文分享一些技巧，帮助你缩短响应时间、降低延迟、管理错误和吞吐量，并且最大限度地减少 CPU 和内存的使用。 如何优化 Node.js 的 API 1. 始终使用异步函数 异步函数就像 JavaScript 的心脏。因此，优化 CPU 使用率的最佳方法就是编写异步函数来执行非阻塞 ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/how-to-optimize-nodejs-apis/</link>
                <guid isPermaLink="false">6310ab26aeb66407f67d18c3</guid>
                
                    <category>
                        <![CDATA[ Node.js ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ PapayaHUANG ]]>
                </dc:creator>
                <pubDate>Thu, 01 Sep 2022 06:20:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2022/09/pexels-ann-marie-kennon-1296000.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>原文：</strong> <a href="https://www.freecodecamp.org/news/how-to-optimize-nodejs-apis/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">How to Optimize Your Node.js API</a>
      </p><!--kg-card-begin: markdown--><p>在这篇文章中，我将讲解如何优化使用 Node.js 编写的 API。</p>
<h3 id="">前提条件</h3>
<p>想要充分了解本文内容，你必须了解以下概念：</p>
<ul>
<li>Node.js 的设置与安装</li>
<li>如何使用 Node 创建 API</li>
<li>如何使用 Postman</li>
<li>JavaScript 的 async/await 工作原理</li>
<li>Redis 的基础操作</li>
</ul>
<h2 id="api">API 优化到底指的是什么</h2>
<p>优化包含了改善 API 的响应时间。响应时间越短，API 的速度越快。</p>
<p>我将在本文分享一些技巧，帮助你缩短响应时间、降低延迟、管理错误和吞吐量，并且最大限度地减少 CPU 和内存的使用。</p>
<h1 id="nodejsapi">如何优化 Node.js 的 API</h1>
<h2 id="1">1. 始终使用异步函数</h2>
<p>异步函数就像 JavaScript 的心脏。因此，优化 CPU 使用率的最佳方法就是编写异步函数来执行非阻塞 I/O 操作。</p>
<p>I/O 操作包括对数据的读和写。它可以在数据库、云存储或者任何本地磁盘上进行。</p>
<p>在大量使用 I/O 操作的应用使用异步函数可以提高效率。因为由于没有阻塞 I/O，当一个请求在做输入/输出操作的时候，CPU 可以同时处理多个请求。</p>
<p>举例如下：</p>
<pre><code class="language-js">var fs = require('fs');
// 执行阻塞I/O
var file = fs.readFileSync('/etc/passwd');
console.log(file);
// 执行非阻塞I/O
fs.readFile('/etc/passwd', function(err, file) {
    if (err) return err;
    console.log(file);
});
</code></pre>
<ul>
<li>使用 Node 包 <strong>fs</strong> 来处理文件</li>
<li><strong>readFileSync()</strong> 是同步函数，会在执行完成前阻塞线程</li>
<li><strong>readFile()</strong> 是异步函数，会立刻返回并在后台运行</li>
</ul>
<h2 id="2apisessioncookieapi">2. 避免在 API 中使用 session 和 cookie，仅在 API 响应中发送数据</h2>
<p>当我们使用 cookie 或者 session 来存储临时状态的时候，会占用非常多的服务器内存。</p>
<p>现在通用无状态 API，并且也有 JWT、OAuth 等验证机制。验证令牌保存在客户端以便服务器管理状态。</p>
<p>JWT 是基于 JSON 的用于 API 验证的安全令牌。JWT 可以被看到，但一旦发送就无法修改。JWT 只是一个序列并没有加密。OAuth 不是 API 或服务——相反，它是授权的开放标准。OAuth 是一组用于获取令牌的标准步骤。</p>
<p>同时，也不要把时间浪费在使用 Node.js 来服务静态文件。这方面 NGINX 和 Apache 做得更好。</p>
<p>使用 Node 搭建 API 的时候，不要在响应中发送完整的 HTML 页面。当仅有数据通过 API 发送的时候，Node 服务得会更好。大部分 Node 应用都使用 JSON 数据。</p>
<h2 id="3">3. 优化数据库查询</h2>
<p>优化 Node API 的重要一环是优化查询。特别是对于大型应用来说，我们需要多次查询数据库，所以一个糟糕的查询会降低应用的整体性能。</p>
<p>索引是一种优化数据库性能的方法，通过最小化处理查询时所需的磁盘访问次数来实现。它是一种数据结构技术，用于快速定位和访问数据库中的数据。索引是使用几个数据库列创建的。</p>
<p>假设我们有一个没有索引的数据库模式，并且数据库包含 100 万条记录。与带有索引的模式相比，使用没有索引的模式做一个简单的 find（查找）查询将扫描更多的记录来找到匹配的记录。</p>
<ul>
<li>没有索引的查询</li>
</ul>
<pre><code class="language-js">&gt; db.user.find({email: 'ofan@skyshi.com'}).explain("executionStats")
</code></pre>
<ul>
<li>有索引的查询</li>
</ul>
<pre><code class="language-js">&gt; db.getCollection("user").createIndex({ "email": 1 }, { "name": "email_1", "unique": true })
{
 "createdCollectionAutomatically" : false,
 "numIndexesBefore" : 1,
 "numIndexesAfter" : 2,
 "ok" : 1
}
</code></pre>
<p>两者之间扫描文件的数量相差巨大 ~ 1038：</p>
<table>
<thead>
<tr>
<th>方法</th>
<th>扫描文件</th>
</tr>
</thead>
<tbody>
<tr>
<td>没有索引</td>
<td>1039</td>
</tr>
<tr>
<td>有索引</td>
<td>1</td>
</tr>
</tbody>
</table>
<h2 id="4pm2api"><strong>4. 使用 PM2 集群模式优化 API</strong></h2>
<p>PM2 是为 Node.js 应用程序设计的生产流程管理器。它内置了负载平衡器，允许应用程序在不修改代码的情况下，作为多个进程运行。</p>
<p>使用 PM2 时的应用停机时间几乎为零。总体来说，PM2 确实可以提升 API 性能和并发性。</p>
<p>在生产环境中部署代码并运行以下命令以查看 PM2 集群如何在所有可用 CPU 上进行扩展：</p>
<pre><code class="language-js">pm2 start  app.js -i 0
</code></pre>
<h2 id="5ttfb"><strong>5. 减少 TTFB（第一字节时间）</strong></h2>
<p>第一字节时间是一种测量方式，用作表示 Web 服务器或者其他网络资源的响应时间。TTFB 测量从用户或客户发出 HTTP 请求到客户的浏览器收到页面的第一个字节的时间。</p>
<p>所有用户访问浏览器的同一页面加载速度不可能在 100 毫秒之内，这仅仅是因为服务器和用户之间的物理距离。</p>
<p>我们可以通过使用 CDN 和全球本地数据中心缓存内容来减少第一个字节的时间。这有助于用户以最小的延迟访问内容。你可以从 Cloudflare 提供的 CDN 解决方案开始着手。</p>
<h2 id="6"><strong>6. 使用带日志的错误脚本</strong></h2>
<p>监视 API 是否正常工作最好的办法是记录行为，于是记录日志就派上用场。</p>
<p>一个常见的办法是将记录打印在控制台上（使用<code>console.log()</code>）。</p>
<p>比<code>console.log()</code>更高效的方法是使用 Morgan、Buyan 和 Winston。我将在这里以 Winston 为例。</p>
<h3 id="winston">如何使用 Winston 记录 – 功能</h3>
<ul>
<li>支持 4 个可以自由选择的日志等级，如：info、error、verbose、debug、silly 和 warn</li>
<li>支持查询日志</li>
<li>简单的分析</li>
<li>可以使用相同的类型进行多个 transports 输出</li>
<li>捕获并记录 uncaughtException</li>
</ul>
<p>可以使用以下命令行设置 Winston：</p>
<pre><code class="language-js">npm install winston --save
</code></pre>
<p>这里是使用 Winston 记录的基本配置：</p>
<pre><code class="language-js">const winston = require('winston');

let logger = new winston.Logger({
  transports: [
    new winston.transports.File({
      level: 'verbose',
      timestamp: new Date(),
      filename: 'filelog-verbose.log',
      json: false,
    }),
    new winston.transports.File({
      level: 'error',
      timestamp: new Date(),
      filename: 'filelog-error.log',
      json: false,
    })
  ]
});

logger.stream = {
  write: function(message, encoding) {
    logger.info(message);
  }
};
</code></pre>
<h2 id="7http2http"><strong>7. 使用 HTTP/2 而不是 HTTP</strong></h2>
<p>除了上述使用的这些技巧，我们还可以使用 HTTP/2 而不是 HTTP，因为它具备以下优势：</p>
<ul>
<li>多路复用</li>
<li>头部压缩</li>
<li>服务器推送</li>
<li>二进制格式</li>
</ul>
<p>它专注提高性能，并解决 HTTP 的问题。它使网页浏览更快、更容易，并且消耗更少的带宽。</p>
<h2 id="8"><strong>8. 并行任务</strong></h2>
<p>使用 <a href="https://caolan.github.io/async/v3/">async.js</a> 来运行任务。并行任务对 API 的性能有很大改善，它减少了延迟并最大限度地减少了阻塞操作。</p>
<p>并行意味着同时运行多个任务。当你并行任务的时候，不需要控制程序的执行顺序。</p>
<p>以下是一个数组异步并行的简单例子：</p>
<pre><code class="language-js">const async = require("async");
// 使用对象而不是数组
async.parallel({
  task1: function(callback) {
    setTimeout(function() {
      console.log('Task One');
      callback(null, 1);
    }, 200);
  },
  task2: function(callback) {
    setTimeout(function() {
      console.log('Task Two');
      callback(null, 2);
    }, 100);
    }
}, function(err, results) {
  console.log(results);
  // 结果相当于: {task2: 2, task1: 1}
});
</code></pre>
<p>在以上例子中，我们使用了 <a href="https://caolan.github.io/async/v3/">async.js</a> 以异步的形式执行了两个任务。task 1 需要 200 毫秒完成，但是 task 2 不需要等待 task 1 完成后再执行 – 它在设定的 100 毫秒后执行。</p>
<p>并行任务对 API 的性能有很大的影响。它减少了延迟并最大限度地减少了阻塞操作。</p>
<h2 id="9redis"><strong>9. 使用 Redis 缓存应用</strong></h2>
<p>Redis 是 Memcached 的高级版本。它通过在服务器的主内存中存储和检索数据来优化 API 响应时间。它提高了数据库查询的性能，也减少了访问延迟。</p>
<p>在下面的代码片段中，我们分别调用了不使用 Redis 和使用 Redis 的 API，并比较了响应时间。</p>
<p>响应时间差异巨大~ 899.37 毫秒：</p>
<table>
<thead>
<tr>
<th>方法</th>
<th>响应时间</th>
</tr>
</thead>
<tbody>
<tr>
<td>不使用 Redis</td>
<td>900ms</td>
</tr>
<tr>
<td>使用 Redis</td>
<td>0.621ms</td>
</tr>
</tbody>
</table>
<p>以下是不使用 Redis 的 Node：</p>
<pre><code class="language-js">'use strict';

//定义需要的所有依赖项
const express = require('express');
const responseTime = require('response-time')
const axios = require('axios');

//加载 Express 框架
var app = express();

//创建在响应头中添加 X-Response-Time 的中间件
app.use(responseTime());

const getBook = (req, res) =&gt; {
  let isbn = req.query.isbn;
  let url = `https://www.googleapis.com/books/v1/volumes?q=isbn:${isbn}`;
  axios.get(url)
    .then(response =&gt; {
      let book = response.data.items
      res.send(book);
    })
    .catch(err =&gt; {
      res.send('The book you are looking for is not found !!!');
    });
};

app.get('/book', getBook);

app.listen(3000, function() {
  console.log('Your node is running on port 3000 !!!')
});
</code></pre>
<p>以下是使用 Redis 的 Node：</p>
<pre><code class="language-js">'use strict';

//定义需要的所有依赖项
const express = require('express');
const responseTime = require('response-time')
const axios = require('axios');
const redis = require('redis');
const client = redis.createClient();

//加载 Express 框架
var app = express();

//创建在响应头中添加 X-Response-Time 的中间件
app.use(responseTime());

const getBook = (req, res) =&gt; {
  let isbn = req.query.isbn;
  let url = `https://www.googleapis.com/books/v1/volumes?q=isbn:${isbn}`;
  return axios.get(url)
    .then(response =&gt; {
      let book = response.data.items;
      //设置string-key:缓存中的 isbn。以及缓存的内容: title
      // 设置缓存的过期时间为 1 个小时（60分钟）
      client.setex(isbn, 3600, JSON.stringify(book));

      res.send(book);
    })
    .catch(err =&gt; {
      res.send('The book you are looking for is not found !!!');
    });
};

const getCache = (req, res) =&gt; {
  let isbn = req.query.isbn;
  //对照服务器的 redis 检查缓存数据
  client.get(isbn, (err, result) =&gt; {
    if (result) {
      res.send(result);
    } else {
      getBook(req, res);
    }
  });
}
app.get('/book', getCache);

app.listen(3000, function() {
  console.log('Your node is running on port 3000 !!!')
)};
</code></pre>
<h2 id="">总结</h2>
<p>在本指南中，我们了解了如何优化 Node.js API 的响应时间。</p>
<p>JavaScript 重度依赖函数，因此，使用异步函数可以使脚本运行得更快并且不阻塞。</p>
<p>除此之外，我们还可以使用缓存记忆（Redis）、数据库索引、TTFB 和 PM2 集群来提高响应速度。</p>
<p>最后请记住，注意路由的安全性并尽可能优化路由也很重要。我们不能为了提高 API 响应速度而妥协掉安全性。因此，在 Node.js 中构建优化的 API 时，应该保留所有标准安全检查。</p>
<p>你可以在 <a href="https://www.linkedin.com/in/kadeniyi/">LinkedIn</a> 上联系我。</p>
<p>后会有期！</p>
<!--kg-card-end: markdown--> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 如何在 Windows 10 中下载和安装 NVM ]]>
                </title>
                <description>
                    <![CDATA[ 不同的软件开发工具可能需要特定版本的 Node.js 和 NPM（Node 包管理器）。NPM 是一个用于管理从 NPM 注册表安装的包的工具。 此外，如果你正在制作 NPM 包，你可能需要使用不同版本的 Node.js 对其进行测试。这就是你应该安装 NVM 的原因。 NVM 是 Node Version Manager（Node 版本管理工具）的缩写，是一个命令行工具，用于管理和切换到不同版本的 Node.js。 在本文中，我将向你展示如何在 Windows 10 上下载和安装 NVM。 我还将向你展示如何在你的 Windows 计算机上设置和使用不同版本的 Node.js 和 NPM。 我们将涵盖的内容  * 如何在 Windows 10 中下载和安装 NVM  * 按照步骤下载 nvm-windows  * 如何在 Windows 10 ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/nvm-for-windows-how-to-download-and-install-node-version-manager-in-windows-10/</link>
                <guid isPermaLink="false">63089e37aeb66407f67d0fe4</guid>
                
                    <category>
                        <![CDATA[ Windows ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Node.js ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Miya Liu ]]>
                </dc:creator>
                <pubDate>Fri, 26 Aug 2022 03:20:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2022/08/patrick-fore-okjrirj2dGY-unsplash.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>原文：</strong> <a href="https://www.freecodecamp.org/news/nvm-for-windows-how-to-download-and-install-node-version-manager-in-windows-10/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">https://www.freecodecamp.org/news/nvm-for-windows-how-to-download-and-install-node-version-manager-in-windows-10/</a>
      </p><p>不同的软件开发工具可能需要特定版本的 Node.js 和 NPM（Node 包管理器）。NPM 是一个用于管理从 NPM 注册表安装的包的工具。</p><p>此外，如果你正在制作 NPM 包，你可能需要使用不同版本的 Node.js 对其进行测试。这就是你应该安装 NVM 的原因。</p><p>NVM 是 Node Version Manager（Node 版本管理工具）的缩写，是一个命令行工具，用于管理和切换到不同版本的 Node.js。</p><p>在本文中，我将向你展示如何在 Windows 10 上下载和安装 NVM。</p><p>我还将向你展示如何在你的 Windows 计算机上设置和使用不同版本的 Node.js 和 NPM。</p><h2 id="-">我们将涵盖的内容</h2><ul><li>如何在 Windows 10 中下载和安装 NVM</li><li>按照步骤下载 nvm-windows</li><li>如何在 Windows 10 上使用 NVM</li><li>如何使用 NVM 安装不同版本的 Node.js 和 NPM</li><li>总结</li></ul><h2 id="-windows-10-nvm">如何在 Windows 10 中下载和安装 NVM</h2><p>正如我之前提到的，Windows 不支持 NVM，因为 NVM 仅在 Linux 和 Mac 上受支持。</p><p>你将在 Windows 机器上使用的是 “nvm-windows”。 nvm-windows 类似于 NVM，但不完全相同。</p><p><strong>注意：</strong>如果你已经安装了 Node.js，你需要卸载它，这样在使用不同版本的 Node 和从 NPM 注册表安装包时不会导致错误。</p><p>之后重新启动你的电脑，打开命令提示符或 PowerShell，然后运行 ​​<code>node -v</code> 以确认 Node 已被卸载。</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2022/08/ss1-2.png" class="kg-image" alt="ss1-2" width="600" height="400" loading="lazy"></figure><p>另外，如果你安装了 yarn，卸载它，安装 NVM 后重新安装它。你不想在安装和使用 NPM 注册表中的包时遇到奇怪的错误。</p><h2 id="-nvm-windows">按照步骤下载 nvm-windows</h2><p><strong>第 1 步：</strong>前往 <a href="https://github.com/coreybutler/nvm-windows#installation--upgrades">nvm-windows 仓库</a>，然后单击立即下载</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2022/08/ss2-2.png" class="kg-image" alt="ss2-2" width="600" height="400" loading="lazy"></figure><p>你将看到一个包含不同版本的 nvm-windows 的页面。</p><ul><li><strong>第 2 步：</strong>点击最新版本下载。目前，它是 2022 年 4 月 28 日的版本。</li></ul><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/news/content/images/2022/08/image-5.png" class="kg-image" alt="image-5" width="600" height="400" loading="lazy"></figure><ul><li><strong>第 3 步：</strong>在你的计算机上找到安装程序并打开它，按照安装向导进行安装。</li></ul><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2022/08/ss4-2.png" class="kg-image" alt="ss4-2" width="600" height="400" loading="lazy"></figure><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/news/content/images/2022/08/image-6.png" class="kg-image" alt="image-6" width="600" height="400" loading="lazy"></figure><ul><li><strong>第 4 步：</strong>打开 PowerShell 或命令提示符并运行 <code>nvm -v</code> 以确认安装。</li></ul><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2022/08/ss6-2.png" class="kg-image" alt="ss6-2" width="600" height="400" loading="lazy"></figure><p>如果你收到与我上面相同的消息，则 nvm-windows 已成功安装，恭喜！</p><h2 id="-windows-10-nvm-1">如何在 Windows 10 上使用 NVM</h2><p>要使用 NVM，你需要以管理员身份打开 PowerShell 或命令提示符。你也可以使用 Git bash。</p><ul><li>要以管理员身份打开 PowerShell，请右键单击开始并选择 “PowerShell（Admin）”。</li></ul><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/news/content/images/2022/08/image-7.png" class="kg-image" alt="image-7" width="600" height="400" loading="lazy"></figure><ul><li>要以管理员身份打开命令提示符，请搜索 “cmd” 并选择右侧的“以管理员身份打开”。</li></ul><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/news/content/images/2022/08/image-8.png" class="kg-image" alt="image-8" width="600" height="400" loading="lazy"></figure><h2 id="-nvm-node-js-npm">如何使用 NVM 安装不同版本的 Node.js 和 NPM</h2><p>超级强大的 NVM 让你能够在你的机器上安装多个版本的 Node.js。</p><p>要安装最新版本的 Node，请运行 <code>nvm install latest</code>。</p><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/news/content/images/2022/08/image-9.png" class="kg-image" alt="image-9" width="600" height="400" loading="lazy"></figure><p>安装 Node 的长期支持（LTS）版本更好，因为它的 bug 更少。</p><p>要安装 LTS 版本的 Node，请运行 <code>nvm install lts</code>。</p><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/news/content/images/2022/08/image-10.png" class="kg-image" alt="image-10" width="600" height="400" loading="lazy"></figure><p>要安装特定版本的 Node，你需要先运行 <code>nvm list available</code>，以便查看可用的 Node 版本。</p><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/news/content/images/2022/08/image-11.png" class="kg-image" alt="image-11" width="600" height="400" loading="lazy"></figure><p>要安装该特定版本，请运行 <code>nvm install node-version-number</code>，例如，<code>nvm install 14.20.0</code>。</p><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/news/content/images/2022/08/image-12.png" class="kg-image" alt="image-12" width="600" height="400" loading="lazy"></figure><p><strong>注意：</strong>一旦你安装了一个版本的 Node，就会为你安装相应版本的 NPM，所以你不需要单独安装 NPM。</p><p>如果你要使用的 NPM 版本不可用，请运行 <code>npm install @npm version-number -g</code> 进行安装。</p><p>现在，要查看你在 Windows 机器上安装的 Node 版本列表，请运行 <code>nvm list</code>。</p><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/news/content/images/2022/08/image-13.png" class="kg-image" alt="image-13" width="600" height="400" loading="lazy"></figure><p>要使用特定版本的 Node，请运行：</p><ul><li><code>nvm use latest</code> 使用最新版本</li><li><code>nvm use lts</code> 使用长期支持版本</li><li><code>nvm use version-number</code> 使用你已安装的任何其他版本</li></ul><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/news/content/images/2022/08/image-14.png" class="kg-image" alt="image-14" width="600" height="400" loading="lazy"></figure><h2 id="--1"><strong>总结</strong></h2><p>本文向你展示了如何在 Windows 10（nvm-windows）上安装 NVM，以及如何使用它来安装和管理不同版本的 Node.js。</p><p>提醒一下，以下是你将与 nvm-windows 一起使用的常用命令：</p><ul><li><code>nvm install node-version</code> – 安装一个版本的 Node</li><li><code>nvm list</code> – 查看你机器上安装的 Node 版本</li><li><code>nvm use node-version</code> – 使用特定版本的 Node</li></ul><p>感谢你阅读本文，继续编码吧:)</p> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 如何将 Node 和 NPM 更新到最新版本 ]]>
                </title>
                <description>
                    <![CDATA[ Node 是一个运行时环境，允许开发人员在服务器端在浏览器之外执行 JavaScript 代码。 而 NPM 是一个包管理器，用于将 JavaScript 包（也叫作 Node 模块）发布到 NPM 注册表 [https://www.npmjs.com/] 。你还可以使用它来将软件包安装到你的应用程序中。 要安装 Node，你必须去 Nodejs 网站下载安装程序。下载后，你可以运行安装程序，按照步骤操作，同意条款和条件，然后在你的设备上安装程序。 安装 Node 时，你还会获得 npm CLI，你可以使用它来管理应用程序中的包。 但是，Node 和 NPM 可以单独更新到它们的最新版本，我将在下文中向你展示如何操作。 如何更新 Node 1. 使用 NPM 更新你的 Node 版本 要使用 NPM 更新 Node，你将安装 n [https://www.npmjs.com/package/n] 包，该包将用于交互式管理你设备上的 Node 版本。 以下是步骤： 清除 NPM 缓存 安装依赖时，会缓存一些模块，以提高后续下载的安装速度。所以首先你要清除 ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/how-to-update-node-and-npm-to-the-latest-version/</link>
                <guid isPermaLink="false">62f8bbc9376dd807bffc42a6</guid>
                
                    <category>
                        <![CDATA[ Node.js ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Chengjun.L ]]>
                </dc:creator>
                <pubDate>Tue, 09 Aug 2022 09:09:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2022/08/pexels-markus-winkler-4052195.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>原文：</strong> <a href="https://www.freecodecamp.org/news/how-to-update-node-and-npm-to-the-latest-version/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">How to Update Node and NPM to the Latest Version</a>
      </p><p>Node 是一个运行时环境，允许开发人员在服务器端在浏览器之外执行 JavaScript 代码。</p><p>而 NPM 是一个包管理器，用于将 JavaScript 包（也叫作 Node 模块）发布到 <a href="https://www.npmjs.com/">NPM 注册表</a>。你还可以使用它来将软件包安装到你的应用程序中。</p><p>要安装 Node，你必须去 Nodejs 网站下载安装程序。下载后，你可以运行安装程序，按照步骤操作，同意条款和条件，然后在你的设备上安装程序。</p><p>安装 Node 时，你还会获得 <code>npm</code> CLI，你可以使用它来管理应用程序中的包。</p><p>但是，Node 和 NPM 可以单独更新到它们的最新版本，我将在下文中向你展示如何操作。</p><h2 id="-node">如何更新 Node</h2><h3 id="1-npm-node-">1. 使用 NPM 更新你的 Node 版本</h3><p>要使用 NPM 更新 Node，你将安装 <a href="https://www.npmjs.com/package/n">n</a> 包，该包将用于交互式管理你设备上的 Node 版本。</p><p>以下是步骤：</p><p>清除 NPM 缓存</p><p>安装依赖时，会缓存一些模块，以提高后续下载的安装速度。所以首先你要清除 NPM 缓存。</p><p><strong>安装 n 包</strong></p><pre><code class="language-shell">npm install -g n
</code></pre><p>你需要全局安装此包，因为它在根目录管理 Node 版本。</p><p>安装新版本的 Node</p><pre><code class="language-shell">n lts
n latest
</code></pre><p>上面两个命令安装长期支持和最新版本的 Node.js。</p><p><strong>删除以前安装的版本</strong></p><pre><code class="language-shell">n prune
</code></pre><p>此命令会删除以前安装的版本的缓存版本，只保留最新安装的版本。</p><h3 id="2-nvm-node-">2. 使用 NVM 更新你的 Node 版本</h3><p>NVM 代表 Node 版本管理器，顾名思义，它可以帮助你管理节点版本。使用 NVM，你可以安装 Node 版本并指定项目使用的 Node 版本。</p><p>NVM 使得跨各种 Node 版本测试项目变得容易。</p><p>要使用 NVM 更新 Node 版本，你必须先安装 NVM。</p><p>这是 NVM 的<a href="https://github.com/nvm-sh/nvm#installing-and-updating">安装指南</a>。</p><p>安装后，你可以使用以下方式安装软件包：</p><pre><code class="language-shell">nvm install [version]
</code></pre><p>你可以使用以下命令安装最新版本：</p><pre><code class="language-shell">nvm install node
</code></pre><p>并使用以下命令卸载其他版本：</p><pre><code class="language-shell">nvm uninstall [version]
</code></pre><p>安装了许多版本后，你可能还想指定在特定时间使用的版本。一种方法是设置一个默认别名，如下所示：</p><pre><code class="language-shell">nvm alias default [version]
</code></pre><p>这样，Node 执行将以指定的版本运行。</p><h3 id="3-node-"><strong>3. </strong>下载更新的 Node 二进制文件</h3><p>你还可以从 <a href="https://nodejs.org/en/">Node.js</a> 网站获取最新版本。在网站上面，你可以找到适用于你设备的最新和长期支持版本。</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://www.freecodecamp.org/news/content/images/2022/04/image-7.png" class="kg-image" alt="image-7" width="600" height="400" loading="lazy"><figcaption>Node.js 下载页面</figcaption></figure><p>下载最新版本还会为你提供最新版本的 NPM。</p><h2 id="-npm">如何更新 NPM</h2><p>就像你使用 NPM 更新包一样，你也可以使用 NPM 来更新它自己。这是实现此目的的命令：</p><pre><code class="language-shell">npm install -g npm@latest
</code></pre><p>此命令将全局安装最新版本的 NPM。</p><p>在 Mac 上，你可能必须在 NPM 之前使用 <code>sudo</code> 命令，因为这会将 NPM 安装在设备的根目录中，并且你需要权限才能执行此操作。</p><h2 id="-"><strong>小结</strong></h2><p>在本文中，我们了解了如何将 Node 和 NPM 更新到最新版本。</p><p>回顾一下，当你安装 Node 时，你会自动获得 NPM。如果你还通过从网站安装二进制文件来更新 Node，你将获得更新的 NPM。</p><p>我们还看到了在你的设备上全局更新 Node 和 NPM 的其他方法。</p> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ module.exports——如何在 Node.js 和 JavaScript 中导出 ]]>
                </title>
                <description>
                    <![CDATA[ 原文：module.exports – How to Export in Node.js and JavaScript [https://www.freecodecamp.org/news/module-exports-how-to-export-in-node-js-and-javascript/] ，作者：Dillion Megida [https://www.freecodecamp.org/news/author/dillionmegida/] 在编程中，模块是一个程序的组成部分，具有一个或多个功能或值。 这些值也可以在整个程序中共享，并可以以不同的方式使用。 在这篇文章中，我将告诉你如何在 Node.js 中通过导出和导入模块来共享函数和值。 为什么要导出模块 你想导出模块，这样你就可以在你的应用程序的其他部分使用它们。 模块可以有不同的用途。它们可以提供简单的实用程序来修改字符串，可以提供 API 请求的方法，或者甚至可以提供常量和原始值。 当你导出一个模块时，你可以把它导入到你的应用程序的其他部分并使用它。 Node.js 支持 CommonJS 模块 [h ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/module-exports-how-to-export-in-node-js-and-javascript/</link>
                <guid isPermaLink="false">62747eb2395ec5063718b7e2</guid>
                
                    <category>
                        <![CDATA[ Node.js ]]>
                    </category>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Chengjun.L ]]>
                </dc:creator>
                <pubDate>Fri, 06 May 2022 01:50:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2022/05/pexels-conrad-marshall-615670.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>原文：<a href="https://www.freecodecamp.org/news/module-exports-how-to-export-in-node-js-and-javascript/">module.exports – How to Export in Node.js and JavaScript</a>，作者：<a href="https://www.freecodecamp.org/news/author/dillionmegida/">Dillion Megida</a></p><p>在编程中，模块是一个程序的组成部分，具有一个或多个功能或值。</p><p>这些值也可以在整个程序中共享，并可以以不同的方式使用。</p><p>在这篇文章中，我将告诉你如何在 Node.js 中通过导出和导入模块来共享函数和值。</p><h2 id="-">为什么要导出模块</h2><p>你想导出模块，这样你就可以在你的应用程序的其他部分使用它们。</p><p>模块可以有不同的用途。它们可以提供简单的实用程序来修改字符串，可以提供 API 请求的方法，或者甚至可以提供常量和原始值。</p><p>当你导出一个模块时，你可以把它导入到你的应用程序的其他部分并使用它。</p><p>Node.js 支持 <a href="https://nodejs.org/api/modules.html">CommonJS 模块</a>和 <a href="https://nodejs.org/api/esm.html">ECMAScript 模块</a>。</p><p>在本文的其余部分，我们将专注于 CommonJS 模块，这是 Node.js 中打包模块的原始方法。</p><p>如果你想了解更多关于 ES 模块（以及 CommonJS 模块）的信息，你可以<a href="https://chinese.freecodecamp.org/news/modules-in-javascript/">查看这份详细指南</a>。</p><h2 id="-node-">如何在 Node 中导出模块</h2><p>Node.js 已经导出了内置的模块，例如 <a href="https://nodejs.dev/learn/the-nodejs-fs-module">fs</a>、<a href="https://nodejs.dev/learn/the-nodejs-path-module">path</a> 和 <a href="https://nodejs.dev/learn/the-nodejs-http-module">http</a>。但你也可以创建你自己的模块。</p><p>Node.js 将 Node 项目中的每个文件视为一个模块，可以从文件中导出值和功能。</p><p>例如，你有一个 utility 文件 <code>utility.js</code>，代码如下：</p><pre><code class="language-js">// utility.js

const replaceStr = (str, char, replacer) =&gt; {
  const regex = new RegExp(char, "g")
  const replaced = str.replace(regex, replacer)
  return replaced
}
</code></pre><p><code>utility.js</code> 是一个模块，其他文件可以从中导入东西。但是 <code>utility.js</code> 目前并没有输出任何东西。</p><p>你可以通过检查每个文件中的全局 <code>module</code> 对象来验证这一点。当你打印这个 utility 文件中的 <code>module</code> 全局对象时，你有：</p><pre><code class="language-js">console.log(module)

// {
//   id: ".",
//   path: "...",
//   exports: {},
//   parent: null,
//   filename: "...",
//   loaded: false,
//   children: [],
//   paths: [
//     ...
//   ],
// }
</code></pre><p><code>module</code> 对象有一个 <code>exports</code> 属性，正如你所看到的，它是一个空对象。</p><p>因此，任何试图从这个文件中导入任何东西的行为都会产生错误。</p><p><code>utility.js</code> 文件有一个 <code>replaceStr</code> 方法，可以用一些其他字符替换字符串中的字符。我们可以从这个模块导出这个函数，供其他文件使用。</p><p>方法如下：</p><pre><code class="language-js">// utility.js

const replaceStr = (str, char, replacer) =&gt; {
  const regex = new RegExp(char, "g")
  const replaced = str.replace(regex, replacer)
  return replaced
}

module.exports = { replaceStr }
// or
exports.replaceStr = replaceStr
</code></pre><p>现在， <code>replaceStr</code> 可以在应用程序的其他部分使用。要使用它，你要像这样导入它：</p><pre><code class="language-js">const { replaceStr } = require('./utility.js')

// then use the function anywhere
</code></pre><h2 id="module-exports-vs-exports-in-node"><strong>module.exports vs exports in Node</strong></h2><p>你可以使用 <code>module.exports</code> 从一个模块中导出函数和值：</p><pre><code class="language-js">module.exports = { value1, function1 }
</code></pre><p>或者使用 <code>exports</code>：</p><pre><code class="language-js">exports.value1 = value1
exports.function1 = function1
</code></pre><p>区别是什么？</p><p>这些方法是相同的。基本上，<code>exports</code> 是对 <code>module.exports</code> 的引用。为了更好地理解这一点，让我们通过使用两种导出值的方式来填充 <code>exports</code> 对象：</p><pre><code class="language-js">const value1 = 50
exports.value1 = value1

console.log(module)
// {
//   id: ".",
//   path: "...",
//   exports: { value1: 50 },
//   parent: null,
//   filename: "...",
//   loaded: false,
//   children: [],
//   paths: [
//     ...
//   ],
// }

const function1 = function() {
  console.log("I am a function")
}
module.exports = { function1, ...module.exports }

console.log(module)

// {
//   id: ".",
//   path: "...",
//   exports: { function1: [Function: function1] },
//   parent: null,
//   filename: "...",
//   loaded: false,
//   children: [],
//   paths: [
//     ...
//   ],
// }
</code></pre><p>这里有两件事需要注意：</p><ul><li><code>exports</code> 关键字是对 <code>modules</code> 对象中 <code>exports</code> 对象的引用。通过 <code>exports.value1 = value1</code>，它将 <code>value1</code> 属性添加到 <code>module.exports</code> 对象中，正如你在第一个日志中看到的那样。</li><li>第二条日志不再包含 <code>value1</code> 的导出。它只有使用 <code>module.exports</code> 导出的函数。为什么会这样呢？</li></ul><p><code>module.exports = ...</code> 是将一个新对象重新分配给 <code>exports</code> 属性的一种方式。这个新对象只包含函数，所以 <code>value1</code> 不再被导出。</p><p>那么有什么区别呢？</p><p>只用 <code>exports</code> 关键字导出数值是一种快速导出模块中数值的方法。你可以在顶部或底部使用这个关键字，它所做的只是填充 <code>module.exports</code> 对象。但如果你在一个文件中使用 <code>exports</code>，请坚持在整个文件中使用它。</p><p>使用 <code>module.exports</code> 是一种明确指定模块导出的方式。在一个文件中最好只存在一次。如果它存在两次，第二次声明就会重新分配 <code>module.exports</code> 属性，而模块只输出第二次声明中的内容。</p><p>因此，作为前面代码的解决方案，你可以像这样导出：</p><pre><code class="language-js">// ...
exports.value1 = value1

// ...
exports.function1 = function1
</code></pre><p>或者像这样：</p><pre><code class="language-js">// ...
module.exports = { value1, function1 }
</code></pre><h2 id="--1"><strong>总结</strong></h2><p>Node.js 项目中的每个文件都被视为一个模块，可以导出值供其他模块使用。</p><p><code>module.exports</code> 是 Node.js 文件中的一个对象，用于保存该模块导出的值和函数。</p><p>在一个文件中声明一个 <code>module.exports</code> 对象，指定从该文件中导出的值。导出后，另一个模块可以通过 <code>require</code> 全局方法导入这些值。</p> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Node.js 完全手册 ]]>
                </title>
                <description>
                    <![CDATA[ 原文：The definitive Node.js handbook [https://www.freecodecamp.org/news/the-definitive-node-js-handbook-6912378afc6e/] ，作者：Flavio Copes [https://www.freecodecamp.org/news/author/flavio/] 注意：你可以得到这本手册的 PDF、ePub或Mobi [https://pages.convertkit.com/5fdd03a386/1b1a570eeb]  版本，以方便参考，或在你的 Kindle 或平板电脑上阅读。 Node.js 简介 本手册是 Node.js 的入门指南，它是服务器端 JavaScript 运行环境。 概况 Node.js 是一个 服务器 上的 JavaScript 运行环境。 Node.js 是开源的、跨平台的，自从2009年推出以来，它大受欢迎，现在在 Web 开发领域发挥着重要作用。如果 GitHub 的星星是一个流行的指示因素，那么拥有58000多颗星星就意味着非常流行。 N ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/the-definitive-node-js-handbook/</link>
                <guid isPermaLink="false">6255306499ec7406219e64cf</guid>
                
                    <category>
                        <![CDATA[ Node.js ]]>
                    </category>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ luojiyin ]]>
                </dc:creator>
                <pubDate>Mon, 18 Apr 2022 10:00:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2022/04/1_7F50Qc-ysFgy6tCjUyruTA.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>原文：<a href="https://www.freecodecamp.org/news/the-definitive-node-js-handbook-6912378afc6e/">The definitive Node.js handbook</a>，作者：<a href="https://www.freecodecamp.org/news/author/flavio/">Flavio Copes</a></p><!--kg-card-begin: markdown--><p>注意：你可以得到这本手册的 <a href="https://pages.convertkit.com/5fdd03a386/1b1a570eeb">PDF、ePub或Mobi</a> 版本，以方便参考，或在你的 Kindle 或平板电脑上阅读。</p>
<h3 id="nodejs">Node.js 简介</h3>
<p>本手册是 Node.js 的入门指南，它是服务器端 JavaScript 运行环境。</p>
<h4 id="">概况</h4>
<p>Node.js 是一个 <strong>服务器</strong> 上的 <strong>JavaScript 运行环境</strong>。</p>
<p>Node.js 是开源的、跨平台的，自从2009年推出以来，它大受欢迎，现在在 Web 开发领域发挥着重要作用。如果 GitHub 的星星是一个流行的指示因素，那么拥有58000多颗星星就意味着非常流行。</p>
<p>Node.js 在浏览器之外运行 V8 JavaScript 引擎，这是 Chrome 浏览器的核心。Node.js 能够利用那些使 Chrome 浏览器的 JavaScript 运行变得非常快的成果，这使得 Node.js 能够从 V8 执行的巨大性能改进和即时编译中受益。得益于此，在 Node.js 中运行的 JavaScript 代码可以变得非常有性能。</p>
<p>一个 Node.js 应用程序是由一个单一的进程运行的（single process），不需要为每个请求创建一个新的线程（new thread）。Node在其标准库中提供了一套异步 I/O 原生语法，这将防止JavaScript 代码被阻塞，一般来说，Node.js 中的库是使用非阻塞范式编写的，使阻塞行为成为例外，而不是正常的。</p>
<p>当 Node.js 需要执行 I/O 操作时，比如从网络中读取数据、访问数据库或文件系统，而不是阻塞线程，Node.js 会在响应回来时恢复操作，而不是浪费 CPU 来等待。</p>
<p>这使得 Node.js 可以用一台服务器处理成千上万的并发连接，而不需要引入管理线程并发的负担，这将是错误的主要来源。</p>
<p>Node.js 有一个独特的优势，因为数百万为浏览器编写 JavaScript 的前端开发人员现在能够运行服务器端代码和前端代码，而不需要学习一种完全不同的语言。</p>
<p>在 Node.js 中，新的 ECMAScript 标准可以顺利使用，因为你不必等待所有用户更新他们的浏览器--你通过改变Node.js的版本来决定使用哪个 ECMAScript 版本，你还可以通过运行带有标志（flags）的 Node 来启用特定的实验性功能。</p>
<h4 id="nodejs">Node.js 有大量的库</h4>
<p>凭借其简单的结构，Node包管理器（<a href="https://flaviocopes.com/npm/">NPM</a>）帮助 Node.js 的生态系统激增。现在，<a href="https://www.npmjs.com/">NPM registry</a> 托管了近50万个开源包，你可以自由使用。</p>
<h3 id="nodejs">一个Node.js应用程序的例子</h3>
<p>Node.js 最常见的例子 Hello World 是一个网络服务器：</p>
<pre><code class="language-js">const http = require('http')
const hostname = '127.0.0.1'
const port = 3000

const server = http.createServer((req, res) =&gt; {  
    res.statusCode = 200  
    res.setHeader('Content-Type', 'text/plain')  
    res.end('Hello World\n')
})

server.listen(port, hostname, () =&gt; {  
  console.log(`Server running at http://${hostname}:${port}/`)
})
</code></pre>
<p>要运行这个片段，将其保存为 <code>server.js</code> 文件，并在终端运行 <code>node server.js</code>。</p>
<p>这段代码首先包括 Node.js <a href="https://nodejs.org/api/http.html">http模块</a>。</p>
<p>Node.js有一个惊艳的 <a href="https://nodejs.org/api/">标准库</a>，包括对网络的一流支持。</p>
<p><code>http</code> 的 <code>createServer()</code> 方法创建一个新的HTTP服务器并返回。</p>
<p>该服务器被设置为在指定的端口和主机名上监听。当服务器准备好时，回调函数被调用，在这种情况下，通知我们服务器正在运行。</p>
<p>每当收到一个新的请求，<a href="https://nodejs.org/api/http.html#http_event_request">request event</a> 被调用，提供两个对象：一个请求（一个 <a href="https://nodejs.org/api/http.html#http_class_http_incomingmessage">http.IncomingMessage</a> 对象）和一个响应（一个 <a href="https://nodejs.org/api/http.html#http_class_http_serverresponse">http.ServerResponse</a> 对象）。</p>
<p>这两个对象对于处理HTTP调用是必不可少的。</p>
<p>第一个对象提供请求的细节。在这个简单的例子中，这个没有被使用，但是你可以访问请求头和请求数据。</p>
<p>第二个对象是用来返回数据给调用者的。</p>
<p>在这种情况下:</p>
<pre><code class="language-js">res.statusCode = 200
</code></pre>
<p>我们将 "statusCode" 属性设置为 "200"，以表示成功响应。</p>
<p>我们设置 Content-Type 头:</p>
<pre><code class="language-js">res.setHeader('Content-Type', 'text/plain')
</code></pre>
<p>……我们最后关闭响应，将内容作为一个参数添加到 <code>end()</code>：</p>
<pre><code class="language-js">res.end('Hello World\n')
</code></pre>
<h3 id="nodejs">Node.js 框架和工具</h3>
<p>Node.js 是一个低代码（low-level）平台。为了让开发者更容易、更有趣，成千上万的库被建立在Node.js 之上。</p>
<p>其中许多人随着时间的推移成为了流行的选择。这里有一个不全面的列表，列出了我认为非常相关和值得学习的那些：</p>
<ul>
<li><a href="https://expressjs.com/"><strong>Express</strong></a><br>
创建一个网络服务器的最简单而强大的方法之一。它的极简方法和对服务器核心功能的无偏见关注是其成功的关键。</li>
<li><a href="https://flaviocopes.com/meteor/"><strong>Meteor</strong></a><br>
一个令人难以置信的强大的全栈框架，赋予你用JavaScript构建应用程序的同构方法，在客户端和服务器上共享代码。曾经是一个提供一切的现成工具，现在它与前端库如<a href="https://flaviocopes.com/react/">React</a>、<a href="https://flaviocopes.com/vue-introduction/">Vue</a> 和 <a href="https://angularjs.org/">Angular</a> 集成。Meteor也可以用来创建移动应用程序。</li>
<li><a href="http://koajs.com/"><strong>Koa</strong></a><br>
由Express背后的同一个团队建立，Koa旨在更简单和更小，建立在多年的知识之上。这个新项目的诞生是由于需要在不破坏现有社区的情况下，创造不兼容的变化。</li>
<li><a href="https://flaviocopes.com/nextjs/"><strong>Next.js</strong></a><br>
这是一个用于渲染服务器端的 <a href="https://reactjs.org/">React</a> 应用程序的框架。</li>
<li><a href="https://github.com/zeit/micro"><strong>Micro</strong></a><br>
这是一个非常轻量级的服务器，用于创建异步的HTTP微服务。</li>
<li><a href="https://socket.io/"><strong>Socket.io</strong></a><br>
这是一个实时通信引擎，用于构建网络应用。</li>
</ul>
<h3 id="nodejs">Node.js 的简史</h3>
<h4 id="2009nodejs">回顾 2009 年到今天的 Node.js 的历史</h4>
<p>信不信由你，Node.js 只有 9 年的历史。</p>
<p>相比之下，JavaScript 有 23 年的历史，而我们所知的网络（在引入Mosaic之后）有25年的历史。</p>
<p>对于一项技术来说，9年的时间实在是太短了，但Node.js似乎已经存在了很久。</p>
<p>我有幸从 Node.js 的早期就开始工作，当时它只有2年的历史，尽管信息很少，但你已经可以感觉到它是一个巨大的东西。</p>
<p>在这一节中，我想画出 Node.js 在历史上的大图景，把事情看清楚。</p>
<h4 id="">一段小的历史</h4>
<p>JavaScript 是一种编程语言，是在网景公司创建的，作为一种脚本工具，在他们的浏览器 <a href="https://en.wikipedia.org/wiki/Netscape_Navigator">Netscape Navigator</a> 中操作网页。</p>
<p>网景公司的部分商业模式是销售网络服务器，其中包括一个名为 “Netscape LiveWire” 的环境，它可以使用服务器端的 JavaScript 创建动态页面。因此，服务器端JavaScript的想法并不是由 Node.js引入的，它就像 JavaScript 一样古老--但在当时它并不成功。</p>
<p>导致 Node.js 崛起的一个关键因素是时机。几年前，JavaScript 开始被认为是一种严肃的语言，这要归功于 “Web 2.0” 应用程序，它们向世界展示了网络上的现代体验是什么样的（想想谷歌地图或GMail）。</p>
<p>由于浏览器的竞争，JavaScript 引擎的性能标准大大提高了，这种竞争仍在继续。每个主要浏览器背后的开发团队每天都在努力工作，为我们提供更好的性能，这对 JavaScript 这个平台来说是一个巨大的胜利。Chrome V8，即 Node.js 背后使用的引擎，就是其中之一，特别是它的 Chrome JavaScript 引擎。</p>
<p>但当然，Node.js 的流行并不只是因为纯粹的运气或时机。它引入了许多关于如何在服务器上用 JavaScript 编程的创新思维。</p>
<h4 id="2009">2009</h4>
<p>Node.js的诞生</p>
<p>第一种形式的 <a href="https://flaviocopes.com/npm/">npm</a> 的诞生</p>
<h4 id="2010">2010</h4>
<p><a href="https://flaviocopes.com/express/">Express</a> 诞生</p>
<p><a href="https://socket.io/">Socket.io</a> 诞生</p>
<h4 id="2011">2011</h4>
<p>npm达到1.0版本</p>
<p>公司开始采用 Node。<a href="https://www.linkedin.com/">LinkedIn</a>, <a href="https://www.uber.com/">Uber</a></p>
<p><a href="https://hapijs.com/">Hapi</a> 诞生</p>
<h4 id="2012">2012</h4>
<p>被采用的速度非常快</p>
<h4 id="2013">2013</h4>
<p>第一个使用 Node.js 的大型博客平台: <a href="https://ghost.org/">Ghost</a></p>
<p><a href="https://koajs.com/">Koa</a> 诞生</p>
<h4 id="2014">2014</h4>
<p>大事件: <a href="https://iojs.org/">IO.js</a> 是 Node.js 的一个重要分叉，目标是引入ES6支持，并快速推进。</p>
<h4 id="2015">2015</h4>
<p><a href="https://foundation.nodejs.org/">Node.js 基金会</a> 诞生</p>
<p>IO.js 回归到 Node.js 中</p>
<p>npm引入了私有模块</p>
<p><a href="https://nodejs.org/en/blog/release/v4.0.0/">Node 4</a> 发布（之前没有发布过1、2、3版本）</p>
<h4 id="2016">2016</h4>
<p><a href="https://blog.npmjs.org/post/141577284765/kik-left-pad-and-npm">leftpad 事件</a></p>
<p><a href="https://flaviocopes.com/yarn/">Yarn</a> 诞生：Node 6 发布</p>
<h4 id="2017">2017</h4>
<p>npm 更专注于安全：Node 8 发布</p>
<p><a href="https://nodejs.org/api/http2.html">HTTP/2</a></p>
<p><a href="https://flaviocopes.com/v8/">V8</a> 在其测试套件中引入了 Node，正式将 Node 作为除 Chrome 之外的 JavaScript 引擎的目标。</p>
<p>每周 30 亿次 npm 下载</p>
<h4 id="2018">2018</h4>
<p>Node 10 发布</p>
<p><a href="https://flaviocopes.com/es-modules/">ES modules</a>.</p>
<p><a href="https://nodejs.org/api/esm.html">mjs</a> 实验性支持</p>
<h3 id="nodejs">如何安装 Node.js</h3>
<h4 id="nodejsnvm">如何在你的系统上安装 Node.js：使用软件包管理器、官方网站安装程序或 nvm</h4>
<p>Node.js 可以通过不同的方式进行安装。这篇文章强调了最常见和最方便的方式。</p>
<p>所有主要平台的官方软件包都可以使用<a href="https://nodejs.org/en/download/">这里</a>。</p>
<p>安装 Node.js 的一个非常方便的方法是通过包管理器。在这种情况下，每个操作系统都有自己的。</p>
<p>在macOS上，<a href="https://brew.sh/">Homebrew</a> 是事实上的标准，而且一旦安装，就可以通过在 CLI 中运行这个命令，非常容易地安装 Node.js：</p>
<pre><code class="language-shell">brew install node
</code></pre>
<p>其他用于 Linux 和 Windows 的软件包管理器被列出 <a href="https://nodejs.org/en/download/package-manager/">这里</a>。</p>
<p><a href="https://github.com/creationix/nvm/blob/master/README.md">nvm</a> 是运行 Node.js 的一种流行方式。它允许你轻松地切换 Node.js 的版本，并安装新的版本来尝试，并在发生故障时轻松回滚，例如。</p>
<p>它对于用旧的 Node.js 版本测试你的代码也非常有用。</p>
<p>我的建议是，如果你刚刚开始，而且你还没有使用 Homebrew，就使用官方安装程序。否则，Homebrew是我最喜欢的解决方案。</p>
<h3 id="nodejsjavascript">使用 Node.js，你需要知道多少 JavaScript？</h3>
<p>如果你刚刚开始学习 JavaScript，你需要对这门语言有多深的了解？</p>
<p>作为一个初学者，你很难达到对自己的编程能力有足够信心的程度。</p>
<p>在学习编程的过程中，你可能也会迷惑不解，到底哪里是 JavaScript 的终点，哪里是 Node.js 的起点，反之亦然。</p>
<p>我建议你在深入学习 Node.js 之前，先对主要的 JavaScript 概念有一个很好的掌握：</p>
<ul>
<li>Lexical Structure（语法结构）</li>
<li>Expressions（表示式）</li>
<li>Types （类型）</li>
<li>Variables （变量）</li>
<li>Functions （函数）</li>
<li>this</li>
<li>Arrow Functions （箭头函数）</li>
<li>Loops （循环）</li>
<li>Loops and Scope （循环和作用域）</li>
<li>Arrays （数组）</li>
<li>Template Literals （模板文字）</li>
<li>Semicolons （分号）</li>
<li>Strict Mode （严格模式）</li>
<li>ECMAScript 6, 2016, 2017 （ES6 ES2016 ES2017标准）</li>
</ul>
<p>有了这些概念，你就可以在浏览器和 Node.js 中成为一名熟练的 JavaScript 开发者了。</p>
<p>以下概念也是理解异步编程的关键，这也是 Node.js 的一个基本部分：</p>
<ul>
<li>异步编程（Asynchronous programming）和回调（callbacks）</li>
<li>定时器（Timers）</li>
<li>Promises</li>
<li>Async and Await</li>
<li>闭包（Closures）</li>
<li>事件循环（The Event Loop）</li>
</ul>
<p>幸运的是，我写了一本免费的电子书，解释了所有这些主题，它叫做 <a href="https://flaviocopes.com/javascript/">JavaScript基础知识</a>。这是你能找到的学习所有这些的最紧凑的资源。</p>
<h3 id="nodejs">Node.js 和浏览器之间的差异</h3>
<p>在 Node.js 中编写 JavaScript 应用程序与在浏览器内为网络编程有什么不同。</p>
<p>浏览器和 Node 都使用 JavaScript 作为其编程语言。</p>
<p>构建在浏览器中运行的应用程序与构建 Node.js 应用程序是完全不同的事情。</p>
<p>尽管它始终是 JavaScript，但有一些关键的区别，使体验有了根本的不同。</p>
<p>编写 Node.js 应用程序的前端开发者有一个巨大的优势--语言仍然是一样的。</p>
<p>你有一个巨大的机会，因为我们知道全面、深入地学习一门编程语言是多么困难。通过使用相同的语言来执行你在网络上的所有工作--无论是在客户端还是在服务器上--你就处于一个独特的优势地位。</p>
<p>生态系统的变化。</p>
<p>在浏览器中，大多数时候你所做的是与DOM或其他网络平台 API（如 Cookies ）进行交互。当然，这些并不存在于 Node.js 中。你没有 <code>document</code>、<code>window</code> 和所有其他由浏览器提供的对象。</p>
<p>而且在浏览器中，我们没有 Node.js 通过其模块提供的所有好用的 API，如文件系统访问功能。</p>
<p>另一个很大的区别是，在 Node.js 中你可以控制环境。除非你正在构建一个任何人都可以在任何地方部署的开源应用程序，否则你知道你将在哪个版本的 Node.js 上运行该应用程序。与浏览器环境相比，你不能奢侈地选择你的访问者将使用什么浏览器。</p>
<p>这意味着你可以编写所有你的 Node 版本支持的现代 ES6-7-8-9 的 JavaScript。</p>
<p>由于 JavaScript 的发展如此之快，但浏览器可能有点慢，用户的升级也有点慢--有时在网络上，你只能使用旧的 JavaScript/ECMAScript 版本。</p>
<p>你可以使用 Babel 将你的代码转换为兼容 ES5 的版本，然后再运到浏览器上，但在 Node.js 中，你就不需要这样了。</p>
<p>另一个区别是，Node.js 使用 <a href="https://flaviocopes.com/commonjs/">CommonJS</a> 模块系统，而在浏览器中，我们开始看到 ES 模块标准被实施。</p>
<p>在实践中，这意味着你暂时在 Node.js 中使用 <code>require()</code>，而在浏览器中使用 <code>import</code>（译者注：Node 13.2.0 起开始正式支持 ES Modules，可以在 Node 中使用 <code>import</code> :)。</p>
<h3 id="v8javascript">V8 JavaScript 引擎</h3>
<p>V8 是谷歌浏览器的 JavaScript 引擎的名字。在使用 Chrome 浏览器浏览时，它能接收我们的JavaScript 并执行它。</p>
<p>V8 提供了运行时环境，在其中执行 JavaScript。DOM 和其他网络平台 API 是由浏览器提供的。</p>
<p>最酷的是，JavaScript 引擎是独立于它所承载的浏览器的。这一关键特征使 Node.js 的崛起成为可能。V8 早在2009年就被 Node.js 选择为引擎，随着 Node.js 的普及，V8成为现在为大量用JavaScript 编写的服务器端代码提供动力的引擎。</p>
<p>Node.js 的生态系统是巨大的，由于它的存在，V8也为桌面应用程序提供了动力，比如 <a href="https://electronjs.org/">Electron</a> 等项目。</p>
<h4 id="js">其他 JS 引擎</h4>
<p>其他浏览器有自己的 JavaScript 引擎：</p>
<ul>
<li>Firefox使用 <a href="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/SpiderMonkey">Spidermonkey</a></li>
<li>Safari使用 <a href="https://developer.apple.com/documentation/javascriptcore">JavaScriptCore</a> (也叫Nitro)</li>
<li>Edge使用 <a href="https://github.com/Microsoft/ChakraCore">Chakra</a>（译者注： 现在Edge放弃自己的引擎，使用chrome一样的引擎，即V8）</li>
</ul>
<p>还有很多JavaScript引擎。</p>
<p>所有这些引擎都实现了ECMA ES-262标准，也叫ECMAScript，即JavaScript使用的标准。</p>
<h4 id="">对性能的追求</h4>
<p>V8 是用 C++ 编写的，而且它在不断改进。它是可移植的，可以在 Mac、Windows、Linux 和其他一些系统上运行。</p>
<p>在这个 V8 介绍中，我将忽略 V8 的实现细节。它们可以在更权威的网站上找到，包括 <a href="https://developers.google.com/v8/">V8官方网站</a>，而且它们随着时间的推移而变化，往往是很大的变化。</p>
<p>V8一直在发展，就像周围的其他JavaScript引擎一样，以加快网络和 Node.js 生态系统的发展。</p>
<p>在网络上，有一场多年来一直在进行的性能竞赛，我们（作为用户和开发者）从这场竞争中获益良多，因为我们年复一年地得到更快和更优化的机器。</p>
<h4 id="compilation">编译（Compilation）</h4>
<p>一般来说，JavaScript 被认为是一种解释语言，但现代的 JavaScript 引擎不再只是解释JavaScript，而是对其进行编译。</p>
<p>这发生在 2009 年，当时 SpiderMonkey JavaScript 编译器被添加到 Firefox 3.5 中，每个人都遵循这个想法。</p>
<p>JavaScript 由V8内部编译，采用实时制（JIT）编译，以加快执行速度。</p>
<p>这可能看起来违反直觉，。但自从2004年谷歌地图问世以来，JavaScript 已经从一般执行几十行代码的语言发展到在浏览器中运行的几千到几十万行的完整应用程序。</p>
<p>我们的应用程序现在可以在浏览器中运行数小时，而不仅仅是一些表单验证规则或简单的脚本。</p>
<p>在这个<strong>新世界</strong>里，编译JavaScript是非常有意义的，因为虽然可能需要多花一点时间来让JavaScript<strong>就绪</strong>，但一旦完成，它的性能就会比纯粹的解释代码高得多。</p>
<h3 id="nodejs">如何退出 Node.js 程序</h3>
<p>终止一个 Node.js 应用程序有多种方法。</p>
<p>在控制台中运行程序时，你可以用 <code>ctrl-C</code> 关闭它，但我想在这里讨论的是以编程方式退出。</p>
<p>让我们从最激烈的一个开始，看看为什么你最好<strong>不</strong>使用它。</p>
<p><code>process</code> 核心模块提供了一个方便的方法，允许你以编程方式退出Node.js程序：<code>process.exit()</code>。</p>
<p>当 Node.js 运行这一行时，进程会立即被强制终止。</p>
<p>这意味着任何正在等待的回调，任何仍在发送的网络请求，任何文件系统访问，或写到 <code>stdout</code> 或 <code>stderr</code> 的进程--都将立即被强制地终止。</p>
<p>如果这对你来说是好的，你可以传递一个整数，向操作系统发出退出代码的信号:</p>
<pre><code class="language-js">process.exit(1)
</code></pre>
<p>默认情况下，退出代码是`0'，这意味着成功。不同的退出代码有不同的含义，你可能想在自己的系统中使用这些代码，让程序与其他程序沟通。</p>
<p>你可以阅读更多关于退出代码的内容 <a href="https://nodejs.org/api/process.html#process_exit_codes">这里</a>。</p>
<p>你也可以设置 <code>process.exitCode</code> 属性:</p>
<pre><code class="language-js">process.exitCode = 1
</code></pre>
<p>而当程序后来结束时，Node.js 将返回该退出代码。</p>
<p>当所有的处理完成后，一个程序会优雅地退出。</p>
<p>很多时候，我们用 Node.js 启动服务器，比如这个 HTTP 服务器:</p>
<pre><code class="language-js">const express = require('express')
const app = express()
app.get('/', (req, res) =&gt; {  res.send('Hi!')})
app.listen(3000, () =&gt; console.log('Server ready'))
</code></pre>
<p>这个程序永远不会结束。如果你调用 <code>process.exit()</code>，任何正在等待或运行的请求都会被终止。这是<strong>不好的</strong>。</p>
<p>在这种情况下，你需要给命令发送一个 `SIGTERM' 信号，并通过进程信号处理程序来处理:</p>
<p><strong>注意：</strong> <code>process</code> 不需要 <code>require</code>，它是自带的。</p>
<pre><code class="language-js">const express = require('express')
const app = express()

app.get('/', (req, res) =&gt; {  res.send('Hi!')})

app.listen(3000, () =&gt; console.log('Server ready'))

process.on('SIGTERM', () =&gt; {  
  app.close(() =&gt; {
      console.log('Process terminated')  
  })
})
</code></pre>
<p>什么是信号？信号是一种便携式操作系统接口（POSIX）的互通系统：为了通知一个进程发生的事件而向其发送的通知。</p>
<p><code>SIGKILL</code> 是告诉一个进程立即终止的信号，最好是像 <code>process.exit()</code> 那样。</p>
<p><code>SIGTERM</code> 是告诉一个进程优雅地终止的信号。它是进程管理器发出的信号，如 <code>upstart</code> 或 <code>supervisord</code> 和其他的。</p>
<p>你可以从程序内部，在另一个函数中发送这个信号:</p>
<pre><code class="language-js">process.kill(process.pid, 'SIGTERM')
</code></pre>
<p>或者从另一个正在运行的Node.js程序，或者在你的系统中运行的任何其他应用程序，知道你想终止的进程的PID。</p>
<h3 id="nodejs">如何从 Node.js 读取环境变量</h3>
<p>Node的 <code>process</code> 核心模块提供了 <code>env</code> 属性，它承载了所有在进程启动时设置的环境变量。</p>
<p>下面是一个访问 <code>NODE_ENV</code> 环境变量的例子，该变量默认设置为 <code>development</code>。</p>
<pre><code class="language-js">process.env.NODE_ENV // "development"
</code></pre>
<p>在脚本运行前将其设置为 <code>production</code> 将告诉 Node.js，这是一个生产环境。</p>
<p>以同样的方式，你可以访问你设置的任何自定义环境变量。</p>
<h3 id="nodejs">在哪里托管一个 Node.js 应用程序</h3>
<p>一个 Node.js 应用程序可以被托管在很多地方，这取决于你的需求。</p>
<p>下面是一个非详尽的清单，当你想部署你的应用程序并使其可以公开访问时，你可以探索这些选项。</p>
<p>我将列出从最简单和受限制到更复杂和强大的选项。</p>
<h4 id="">最简单的选择：本地隧道</h4>
<p>即使你有一个动态 IP，或者你在一个 NAT 下，你也可以部署你的应用程序，并使用本地隧道从你的计算机上提供请求。</p>
<p>这个选项适合于一些快速测试、演示产品或与一小部分人分享应用程序。</p>
<p>一个在所有平台上都可用的非常好的工具是 <a href="https://ngrok.com/">ngrok</a>。</p>
<p>使用它，你只需输入 <code>ngrok PORT</code>，你想要的端口就会暴露在互联网上。你会得到一个 ngrok.io 的域名，但如果付费订阅，你可以得到一个自定义的 URL 以及更多的安全选项（记住，你的机器是向公共互联网开放的）。</p>
<p>你可以使用的另一项服务是 <a href="https://github.com/localtunnel/localtunnel">localtunnel</a>。</p>
<h4 id="">零配置部署</h4>
<h4 id="glitch">Glitch</h4>
<p><a href="https://glitch.com/">Glitch</a> 是一个 playground，是一种比以往任何时候都更快地建立你的应用程序的方式，并看到它们在自己的 glitch.com 子域上运行。你目前不能拥有一个自定义域名，而且还有一些 <a href="https://glitch.com/faq#restrictions">限制</a>，但它真的很适合做原型。它看起来很有趣（这是个优点），而且它不是一个傻瓜式的环境--你得到了 Node.js 的所有功能，一个 CDN，安全的证书存储，GitHub 导入/导出和更多。</p>
<p>由 FogBugz 和 Trello 背后的公司（以及 Stack Overflow 的共同创建者）提供。</p>
<p>我经常使用它来做演示。</p>
<h4 id="codepen">Codepen</h4>
<p><a href="https://codepen.io/">Codepen</a> 是一个了不起的平台和社区。你可以创建一个有多个文件的项目，并以自定义域名进行部署。</p>
<h4 id="serverless">Serverless</h4>
<p>无服务器（Serverless）是一种发布应用的方式，而且完全没有服务器需要管理。无服务器是一种范式，你把你的应用发布为<strong>功能</strong>，它们在网络端点上做出响应（也叫FAAS--功能即服务）。</p>
<p>非常受欢迎的解决方案有:</p>
<ul>
<li><a href="https://serverless.com/framework/">Serverless Framework</a></li>
<li><a href="https://stdlib.com/">Standard Library</a></li>
</ul>
<p>它们都为在 AWS Lambda 和其他基于 Azure 或谷歌云的 FAAS 解决方案上发布，提供了一个抽象层。</p>
<h4 id="paas">PAAS</h4>
<p>PAAS 是 Platform As A Service 的缩写。这些平台解决了很多你在部署应用时应该担心的事情。</p>
<h4 id="zeitnow">Zeit Now</h4>
<p><a href="https://zeit.co/now">Zeit</a> 是一个有趣的选择。你只需在终端输入 <code>now</code>，它就会负责部署你的应用程序。有一个有限制的免费版本，而付费版本则更强大。你只需忘记有一个服务器，你只需部署应用程序。</p>
<h4 id="nanobox">Nanobox</h4>
<p><a href="https://nanobox.io/">Nanobox</a></p>
<h4 id="heroku">Heroku</h4>
<p><a href="https://www.heroku.com/">Heroku</a> 是一个神奇的平台。</p>
<p>这是一篇好文章，<a href="https://devcenter.heroku.com/articles/getting-started-with-node">在Heroku上开始使用Node.js</a>.</p>
<h4 id="microsoftazure">Microsoft Azure</h4>
<p><a href="https://azure.microsoft.com/en-us/">Azure</a> 是微软的云产品。</p>
<p>查看 <a href="https://docs.microsoft.com/en-us/azure/app-service/app-service-web-get-started-node">在Azure中创建一个Node.js Web应用</a>.</p>
<h4 id="googlecloudplatform">Google Cloud Platform</h4>
<p><a href="https://cloud.google.com/">Google Cloud</a> 是你的应用程序的一个了不起的结构。</p>
<p>他们有一个很好的<a href="https://cloud.google.com/node/">Node.js文档部分</a>.</p>
<h4 id="virtualprivateserver">Virtual Private Server（虚拟私有服务器）</h4>
<p>在本节中，你会找到常见的选择,从友好到更不友好的顺序排列:</p>
<ul>
<li><a href="https://www.digitalocean.com/">Digital Ocean</a></li>
<li><a href="https://www.linode.com/">Linode</a></li>
<li><a href="https://aws.amazon.com/">Amazon Web Services</a>, 我特别提到 Amazon Elastic Beanstalk，因为它抽象了一点 AWS 的复杂性。</li>
</ul>
<p>因为他们提供了一个空的Linux机器，你可以在上面工作，所以这些没有具体的教程。</p>
<p>在VPS类别中还有很多选择，这些只是我使用的和我推荐的。</p>
<h4 id="baremetal">Bare metal（裸金属）</h4>
<p>另一个解决方案是获得一个 <a href="https://en.wikipedia.org/wiki/Bare-metal_server">裸机金属服务器</a>，安装一个Linux发行版，把它连接到互联网上（或者每月租一个，比如你可以使用 <a href="https://www.vultr.com/pricing/baremetal/">虚拟裸金属</a>服务）。</p>
<h3 id="nodejsrepl">如何使用 Node.js REPL</h3>
<p>REPL 是 Read-Evaluate-Print-Loop 的缩写，它是一种快速探索 Node.js 功能的好方法。</p>
<p><code>node</code> 命令是我们用来运行 Node.js 脚本的命令:</p>
<pre><code class="language-shell">node script.js
</code></pre>
<p>如果我们省略文件名，我们在 REPL 模式下使用它:</p>
<pre><code class="language-shell">node
</code></pre>
<p>如果你现在在你的终端尝试一下，会发生这样的情况:</p>
<blockquote>
<blockquote>
<p>node</p>
</blockquote>
</blockquote>
<p>该命令保持在空闲模式，等待我们输入什么。</p>
<p><strong>提示</strong>：如果你不确定如何打开你的终端，谷歌 “How to open terminal on <your operating="" system="">”。</your></p>
<p>REPL正在等待我们输入一些JavaScript代码。</p>
<p>从简单的开始，然后按下 <code>enter</code> 键:</p>
<pre><code class="language-js">&gt; console.log('test')
&gt; test
&gt; undefined
</code></pre>
<p>第一个值 <code>test</code>，是我们告诉控制台要打印的输出，然后我们得到 undefined，这是运行 <code>console.log()</code> 的返回值。</p>
<p>现在我们可以输入一行新的 JavaScript 了。</p>
<h4 id="tab">通过使用 tab 键完成自动补全</h4>
<p>REPL最酷的地方是它是互动的。</p>
<p>当你写代码时，如果你按下 <code>tab</code> 键，REPL 将尝试自动完成你写的内容，以匹配你已经定义的变量或预定义的变量。</p>
<h4 id="javascript">探索 JavaScript 对象</h4>
<p>试着输入一个 JavaScript 类的名称，如 <code>Number</code>，加一个点，然后按 <code>tab</code>。</p>
<p>REPL将打印出你可以访问该类的所有属性和方法:</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/MgYHCtgjD1rom1yKM43E-qBh7ansJuyglRWr" alt="MgYHCtgjD1rom1yKM43E-qBh7ansJuyglRWr" width="600" height="400" loading="lazy"></p>
<h4 id="globalobjects">探索全局对象（global objects）</h4>
<p>你可以通过输入 "global. "并按 "tab "来检查你可以访问的globals对象:</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/e2qWLuyjYC4DFZjEs2jYWK-NL9AXbpDiSdA7" alt="e2qWLuyjYC4DFZjEs2jYWK-NL9AXbpDiSdA7" width="600" height="400" loading="lazy"></p>
<h4 id="the_specialvariable">The _ special variable （特殊变量）</h4>
<p>如果在一些代码之后，你输入<code>_</code>，这将会打印出最后一个操作的结果。</p>
<h4 id="dotcommands">点命令（Dot commands）</h4>
<p>REPL有一些特殊的命令，都以点<code>.</code>开头。它们是</p>
<ul>
<li><code>.help</code>: 显示点命令的帮助。</li>
<li><code>.editor</code>: 启用更多的编辑器，可以轻松地编写多行JavaScript代码。一旦你进入这个模式，输入ctrl-D就可以运行你写的代码。</li>
<li><code>.break</code>: 当输入一个多行表达式时，输入.break命令将中止继续输入。与按下ctrl-C相同。</li>
<li><code>.clear</code>: 将REPL上下文重置为空对象，并清除当前正在输入的任何多行表达式。</li>
<li><code>.load</code>: 加载一个JavaScript文件，相对于当前工作目录。</li>
<li><code>.save</code>: 将你在REPL会话中输入的所有内容保存到一个文件（指定文件名）</li>
<li><code>.exit</code>: 退出repl（与按两次ctrl-C相同）</li>
</ul>
<p>REPL知道你什么时候在输入一个多行语句，而不需要调用<code>.editor</code>。</p>
<p>例如，如果你开始键入一个迭代，像这样:</p>
<pre><code class="language-js">[1, 2, 3].forEach(num =&gt;; {
</code></pre>
<p>并按下 "enter"，REPL 将转到一个以 3 个点开始的新行，表明你现在可以继续在该块上工作。</p>
<pre><code class="language-js">... console.log(num)... })
</code></pre>
<p>如果你在一行的结尾处输入 <code>.break</code>，多行模式将停止，语句不会被执行。</p>
<h3 id="nodejs">Node.js，接受来自命令行的参数</h3>
<p>如何在 Node.js 程序中接受从命令行传递的参数</p>
<p>在调用 Node.js 程序时，你可以使用以下方式传递任意数量的参数:</p>
<pre><code class="language-shell">node app.js
</code></pre>
<p>参数可以是独立的，也可以有一个键和一个值。</p>
<p>例如:</p>
<pre><code class="language-shell">node app.js flavio
</code></pre>
<p>或者</p>
<pre><code class="language-shell">node app.js name=flavio
</code></pre>
<p>这改变了你在 Node.js 代码中检索该值的方式。</p>
<p>你检索它的方式是使用 Node.js 内置的 <code>process</code> 对象。</p>
<p>它暴露了一个 <code>argv</code> 属性，这是一个包含所有命令行调用参数的数组。</p>
<p>第一个参数是 <code>node</code> 命令的完整路径。</p>
<p>第二个元素是正在执行的文件的完整路径。</p>
<p>所有的附加参数都是从第三个位置开始往前出现。</p>
<p>你可以用一个循环遍历所有的参数（包括节点路径和文件路径）:</p>
<pre><code class="language-js">process.argv.forEach((val, index) =&gt; {  
  console.log(`${index}: ${val}`)
})
</code></pre>
<p>你可以通过创建一个排除前两个参数的新数组，只获得额外的参数:</p>
<pre><code class="language-js">const args = process.argv.slice(2)
</code></pre>
<p>如果你有一个没有索引名称的参数，像这样:</p>
<pre><code class="language-shell">node app.js flavio
</code></pre>
<p>你可以通过以下方式访问它</p>
<pre><code class="language-js">const args = process.argv.slice(2)
args[0]
</code></pre>
<p>在这种情况下:</p>
<pre><code class="language-shell">node app.js name=flavio
</code></pre>
<p><code>args[0]</code> 的值是 <code>name=flavio</code>，而你需要对它进行解析。最好的方法是使用 <a href="https://www.npmjs.com/package/minimist"><code>minimist</code></a>，它有助于处理参数:</p>
<pre><code class="language-js">const args = require('minimist')(process.argv.slice(2))
args['name'] //flavio
</code></pre>
<h3 id="nodejs">使用 Node.js 输出到命令行</h3>
<p>如何使用 Node.js 打印到命令行控制台，从基本的 console.log 到更复杂的情况。</p>
<h4 id="">使用控制台模块的基本输出</h4>
<p>Node.js提供了一个 <a href="https://nodejs.org/api/console.html">console模块</a>，它提供了大量非常有用的方法来与命令行进行交互。</p>
<p>它基本上与你在浏览器中找到的 <code>console</code> 对象相同。</p>
<p>最基本和最常用的方法是 <code>console.log()</code>，它将你传递给它的字符串打印到控制台。</p>
<p>如果你传递一个对象，它将把它渲染成一个字符串。</p>
<p>你可以向 <code>console.log</code> 传递多个变量，例如:</p>
<pre><code class="language-js">const x = 'x'
const y = 'y'
console.log(x, y)
</code></pre>
<p>而 Node.js 会同时打印。</p>
<p>我们还可以通过传递变量和格式指定器来格式化短语，让它看起来更漂亮。</p>
<p>例如:</p>
<pre><code class="language-js">console.log('My %s has %d years', 'cat', 2)
</code></pre>
<ul>
<li><code>%s</code> 格式化变量为字符串</li>
<li><code>%d</code> 或者 <code>%i</code> 格式化变量为整数</li>
<li><code>%f</code> 格式化一个变量为浮点数</li>
<li><code>%O</code> 用于打印一个对象的表示方法</li>
</ul>
<p>比如:</p>
<pre><code class="language-js">console.log('%O', Number)
</code></pre>
<h4 id="">清空控制台</h4>
<p><code>console.clear()</code> 清空控制台（其行为可能取决于所使用的控制台）</p>
<h4 id="">元素统计</h4>
<p><code>console.count()</code> 是一个很方便的方法。</p>
<p>以此代码为例:</p>
<pre><code class="language-js">const x = 1
const y = 2
const z = 3 
console.count(  'The value of x is ' + x + ' and has been checked .. how many times?')
console.count(  'The value of x is ' + x + ' and has been checked .. how many times?')
console.count(  'The value of y is ' + y + ' and has been checked .. how many times?')
</code></pre>
<p>发生的情况是，<code>count</code> 将计算一个字符串被打印的次数，并在其旁边打印出计数。</p>
<p>你可以只统计 apples 和 oranges:</p>
<pre><code class="language-js">const oranges = ['orange', 'orange']
const apples = ['just one apple'] 
oranges.forEach(fruit =&gt; {console.count(fruit)}) 
apples.forEach(fruit =&gt; {console.count(fruit)})
</code></pre>
<h4 id="">打印堆栈跟踪</h4>
<p>在有些情况下，打印一个函数的调用堆栈跟踪是很有用的，也许是为了回答这个问题。"你是如何到达代码的这一部分的？”</p>
<p>你可以使用 <code>console.trace()</code>:</p>
<pre><code class="language-js">const function2 = () =&gt; console.trace()
const function1 = () =&gt; function2()
function1()
</code></pre>
<p>这将打印出堆栈跟踪。如果我在 Node REPL 中尝试这样做，会打印出以下内容:</p>
<pre><code class="language-js">Trace    
at function2 (repl:1:33)    
at function1 (repl:1:25)    
at repl:1:1    
at ContextifyScript.Script.runInThisContext (vm.js:44:33)    
at REPLServer.defaultEval (repl.js:239:29)    
at bound (domain.js:301:14)    
at REPLServer.runBound [as eval] (domain.js:314:12)    
at REPLServer.onLine (repl.js:440:10)    
at emitOne (events.js:120:20)    
at REPLServer.emit (events.js:210:7)
</code></pre>
<h4 id="">计算花费的时间</h4>
<p>你可以使用 <code>time()</code> 和 <code>timeEnd()</code> 轻松计算出一个函数的运行时间。</p>
<pre><code class="language-js">const doSomething = () =&gt; console.log('test')
const measureDoingSomething = () =&gt; {
    console.time('doSomething()')     //do something, and measure the time it takes doSomething() 
    doSomething()
    console.timeEnd('doSomething()')
}

measureDoingSomething()
</code></pre>
<h4 id="stdoutstderr">stdout 和 stderr</h4>
<p>正如我们所看到的，console.log 很适合在控制台中打印信息。这就是所谓的标准输出（<code>stdout</code>）。</p>
<p><code>console.error</code> 打印到 <code>stderr</code> 流（stream）。</p>
<p>它不会出现在控制台，但会出现在错误日志中。</p>
<h4 id="">输出颜色</h4>
<p>你可以通过使用转义序列给控制台中的文本输出着色。转义序列是一组标识颜色的字符。</p>
<p>Example:</p>
<pre><code class="language-js">console.log('\x1b[33m%s\x1b[0m', 'hi!')
</code></pre>
<p>你可以在 Node REPL 中尝试这样做，它将以黄色打印 <code>hi!</code>。</p>
<p>然而，这只是低级别的方法。为控制台输出着色的最简单方法是使用一个库。<a href="https://github.com/chalk/chalk">Chalk</a> 就是这样一个库，除了着色之外，它还可以帮助其他的样式设计，比如将文本加粗、斜体或下划线。</p>
<p>你用 <code>npm install chalk</code> 安装它，然后你就可以使用它:</p>
<pre><code class="language-js">const chalk = require('chalk')
console.log(chalk.yellow('hi!'))
</code></pre>
<p>使用 <code>chalk.yellow</code> 比记住转义代码要方便得多，而且代码的可读性也很强。</p>
<p>查看我在上面发布的 Chalk 项目链接，了解更多的使用实例。</p>
<h4 id="">创建一个进度条</h4>
<p><a href="https://www.npmjs.com/package/progress">Progress</a> 是一个很棒的软件包，可以在控制台中创建一个进度条。使用 <code>npm install progress</code> 来安装它。</p>
<p>这个片段创建了一个10步的进度条，每100毫秒完成一步。当进度条完成后，我们会清除间隔时间:</p>
<pre><code class="language-js">const ProgressBar = require('progress')
const bar = new ProgressBar(':bar', { total: 10 })
const timer = setInterval(() =&gt; {
    bar.tick()
    if (bar.complete) { 
      clearInterval(timer) 
    }
}, 100)
</code></pre>
<h3 id="nodejs">在 Node.js 中接受来自命令行的输入</h3>
<p>如何使 Node.js CLI 程序具有交互性？</p>
<p>Node 从第7版开始就提供了 <a href="https://nodejs.org/api/readline.html">readline模块</a> 来执行这个任务：从一个可读流中获取输入，比如 <code>process.stdin</code> 流，在 Node 程序的执行过程中，它就是终端输入，一次一个行。</p>
<pre><code class="language-js">const readline = require('readline')
                .createInterface({
                  input: process.stdin, output: process.stdout 
                })
readline.question(`What's your name?\n`, (name) =&gt; {
    console.log(`Hi ${name}!`)
    readline.close()
})
</code></pre>
<p>这段代码询问用户名，一旦输入文本并且用户按了回车键，我们就会发送一个问候语。</p>
<p><code>question()</code> 方法显示第一个参数（一个问题）并等待用户输入。一旦回车键被按下，它就会调用回调函数。</p>
<p>在这个回调函数中，我们关闭 readline 接口。</p>
<p><code>readline</code> 提供了其他几个方法，我将让你在我上面 <a href="https://nodejs.org/api/readline.html">链接</a> 文档中查看它们。</p>
<p>如果你需要要求一个密码，现在最好是回显它，而是显示一个<code>*</code>符号。</p>
<p>最简单的方法是使用 <a href="https://www.npmjs.com/package/readline-sync">readline-sync</a>，它在API方面非常相似，可以开箱即用。</p>
<p>一个更完整和抽象的解决方案是由 <a href="https://github.com/SBoudrias/Inquirer.js">Inquirer.js</a> 提供。</p>
<p>你可以用 <code>npm install inquirer</code> 来安装它，然后你可以像这样复制上面的代码:</p>
<pre><code class="language-js">const inquirer = require('inquirer')
const questions = [{ type: 'input', name: 'name', message: "What's your name?", }]
inquirer.prompt(questions).then(answers =&gt; { 
    console.log(`Hi ${answers['name']}!`) 
})
</code></pre>
<p>Inquirer.js 可以让你做很多事情，比如问多个选择，有单选按钮，确认选项等等。</p>
<p>值得了解所有的替代方案，特别是 Node.js 提供的内置方案，但如果你打算将 CLI 输入提高到一个新的水平，Inquirer.js 是一个最佳选择。</p>
<h3 id="exportsnodejsexposefunctionality">使用 exports 从 Node.js 文件中暴露功能（Expose functionality）</h3>
<p>如何使用 <code>module.exports</code> API 将数据暴露给你的应用程序中的其他文件，或者也暴露给其他应用程序。</p>
<p>Node.js 有一个内置的模块系统。</p>
<p>一个 Node.js 文件可以导入其他 Node.js 文件所暴露的功能。</p>
<p>当你想导入一些东西时，你可以使用:</p>
<pre><code class="language-js">const library = require('./library')
</code></pre>
<p>导入驻扎在当前文件夹中的 <code>library.js</code> 文件中所暴露的功能。</p>
<p>在这个文件中，功能必须在被其他文件导入之前被公开。</p>
<p>文件中定义的任何其他对象或变量默认为私有，不向外界公开。</p>
<p>这就是 <a href="https://nodejs.org/api/modules.html">module 系统</a>提供的 <code>module.exports</code> API所允许我们做的。</p>
<p>当你把一个对象或一个函数指定为新的 <code>exports</code> 属性时，这就是被暴露的东西。因此，它可以被导入到你的应用程序的其他部分，或者其他应用程序中。</p>
<p>你可以通过2种方式做到这一点。</p>
<p>首先是给 <code>module.exports</code> 指定一个对象，这是一个由模块系统提供的开箱即用的对象，这将使你的文件只导出<strong>那个对象</strong>:</p>
<pre><code class="language-js">const car = {  brand: 'Ford',  model: 'Fiesta'}
module.exports = car
</code></pre>
<pre><code class="language-js">//..in the other file
const car = require('./car')
</code></pre>
<p>第二种方式是将导出的对象作为 <code>exports</code> 的一个属性。这种方式允许你导出<strong>多个</strong>对象、函数或数据:</p>
<pre><code class="language-js">const car = {  brand: 'Ford',  model: 'Fiesta'}
exports.car = car
</code></pre>
<p>或直接</p>
<pre><code class="language-js">exports.car = {  brand: 'Ford',  model: 'Fiesta'}
</code></pre>
<p>而在另一个文件中，你将通过引用你导入的一个属性来使用它:</p>
<pre><code class="language-js">const items = require('./items')items.car
</code></pre>
<p>或者</p>
<pre><code class="language-js">const car = require('./items').car
</code></pre>
<p><code>module.exports</code> 和 <code>exports</code> 之间有什么区别？</p>
<p>前者暴露了 <strong>它所指向的对象</strong>。后者暴露了它所指向的对象的 <strong>属性</strong>。</p>
<h3 id="npm">npm 简介</h3>
<p><code>npm</code> 是 <strong>node 软件包管理器</strong>。</p>
<p>2017年1月，超过35万个软件包被报告列在 npm registry 中，使其成为地球上最大的单一语言代码库，你可以肯定有一个软件包用于（几乎！）一切。</p>
<p>它开始时是一种下载和管理Node.js包的依赖关系的方式，但后来它也成为了一个用于前端 JavaScript 的工具。</p>
<p><code>npm</code> 做了很多事情。</p>
<h4 id="">下载</h4>
<p><code>npm</code> 管理你项目的依赖项下载。</p>
<h4 id="">安装所有的依赖项</h4>
<p>如果一个项目有一个 <code>packages.json</code> 文件，通过运行</p>
<pre><code class="language-shell">npm install
</code></pre>
<p>它将在 <code>node_modules</code> 文件夹中安装项目所需的一切，如果它不存在，则创建它。</p>
<h4 id="">安装单个软件包</h4>
<p>你也可以通过运行以下命令来安装一个特定的软件包</p>
<pre><code class="language-shell">npm install &lt;package-name&gt;
</code></pre>
<p>通常你会看到在这个命令中加入更多的标志（flags）:</p>
<ul>
<li><code>--save</code> 安装并添加条目到 <code>package.json</code> 文件中 <code>dependencies</code>。</li>
<li><code>--save-dev</code> 安装并添加条目到 <code>package.json</code> 文件 <code>devDependencies</code> 中。</li>
</ul>
<p>区别主要在于，<code>devDependencies</code> 通常是开发工具，如测试库，而 <code>dependencies</code> 是与生产中的应用捆绑在一起的。</p>
<h4 id="">更新软件包</h4>
<p>更新也很容易，通过运行</p>
<pre><code class="language-shell">npm update
</code></pre>
<p><code>npm</code> 将检查所有软件包是否有满足你的版本约束的较新版本。</p>
<p>你可以指定一个单独的软件包来更新:</p>
<pre><code class="language-shell">npm update &lt;package-name&gt;
</code></pre>
<h4 id="">版本管理</h4>
<p>除了普通的下载，<code>npm</code> 还管理<strong>版本</strong>，所以你可以指定一个包的任何特定版本，或者要求比你所需要的版本高或低。</p>
<p>很多时候，你会发现一个库只与另一个库的主要版本兼容。</p>
<p>或者一个库的最新版本中的一个bug，仍未被修复，导致了一个问题。</p>
<p>指定一个库的明确版本也有助于让每个人都处于同一个确切的软件包版本上，这样整个团队就会运行同一个版本，直到 <code>package.json</code> 文件被更新。</p>
<p>在所有这些情况下，版本管理都有很大帮助，<code>npm</code> 遵循语义版本管理（semver）标准。</p>
<h4 id="">运行任务</h4>
<p>package.json 文件支持一种指定命令行任务的格式，可以通过使用</p>
<pre><code class="language-shell">npm &lt;task-name&gt;
</code></pre>
<p>例如:</p>
<pre><code class="language-json">{
    "scripts": {
        "start-dev": "node lib/server-development",
        "start": "node lib/server-production"
    }
}
</code></pre>
<p>使用这个功能来运行Webpack是非常普遍的:</p>
<pre><code class="language-json">{
    "scripts": {
        "watch": "webpack --watch --progress --colors --config webpack.conf.js",
        "dev": "webpack --progress --colors --config webpack.conf.js",
        "prod": "NODE_ENV=production webpack -p --config webpack.conf.js",
    }
}
</code></pre>
<p>因此，与其输入那些容易忘记或打错的长命令，你可以运行</p>
<pre><code class="language-shell">npm watch 
npm dev 
npm prod
</code></pre>
<h3 id="npm">npm在哪里安装软件包？</h3>
<p>当你使用<code>npm</code>（或 <a href="https://flaviocopes.com/yarn/">yarn</a>）安装一个软件包时，你可以执行2种类型的安装:</p>
<ul>
<li>a local install  （本地安装）</li>
<li>a global install （全局安装）</li>
</ul>
<p>默认情况下，当你输入一个 "npm install "命令时，例如:</p>
<pre><code class="language-shell">npm install lodash
</code></pre>
<p>包被安装在当前文件树下的 <code>node_modules</code> 子文件夹中。</p>
<p>在这种情况下，<code>npm</code> 也会在当前文件夹中的 <code>package.json</code> 文件的 <code>dependencies</code> 属性中添加 <code>lodash</code> 条目。</p>
<p>使用 <code>-g</code> 标志进行全局安装:</p>
<pre><code class="language-shell">npm install -g lodash
</code></pre>
<p>当这种情况发生时，npm 不会将软件包安装在本地文件夹下，而是会使用一个全局位置。</p>
<p>具体在哪里？</p>
<p><code>npm root -g</code> 命令将告诉你该位置在你的机器上的确切位置。</p>
<p>在MacOS或Linux上，这个位置可以是 <code>/usr/local/lib/node_modules</code>。 在 Windows 上应该是<code>C:\Users\YOU\AppData\Roaming\npm\node_modules</code></p>
<p>然而，如果你使用 <code>nvm</code> 来管理 Node.js 的版本，这个位置会有所不同。</p>
<p>例如，我使用 <code>nvm</code>，我的软件包位置显示为 <code>/Users/flavio/.nvm/versions/node/v8.9.0/lib/node_modules</code>。</p>
<h3 id="npm">如何使用或执行一个用 npm 安装的软件包</h3>
<h4 id="node_modules">如何在你的代码中包含并使用安装在 node_modules 文件夹中的软件包</h4>
<p>当你使用 <code>npm</code> 安装一个包到你的 <code>node_modules</code> 文件夹，或者全局安装，你如何在你的Node代码中使用它？</p>
<p>假设你安装了 <code>lodash</code>，一个流行的 JavaScript 工具库，使用</p>
<pre><code class="language-shell">npm install lodash
</code></pre>
<p>这将在本地的 <code>node_modules</code> 文件夹中安装该软件包</p>
<p>要在你的代码中使用它，你只需要用 <code>require</code> 将它导入你的程序:</p>
<pre><code class="language-js">const _ = require('lodash')
</code></pre>
<p>如果你的软件包是一个可执行文件呢？</p>
<p>在这种情况下，它将把可执行文件放在 <code>node_modules/.bin/</code> 文件夹下。</p>
<p>一个简单的演示, 使用 <a href="https://www.npmjs.com/package/cowsay">cowsay</a>。</p>
<p>cowsay 软件包提供了一个命令行程序，执行该程序可以让一头牛说些什么（也可以是其他动物）。</p>
<p>当你使用 <code>npm install cowsay</code> 来安装这个包时，它将自己和一些依赖项安装在 node_modules 文件夹:</p>
<p>有一个隐藏的.bin文件夹，它包含cowsay二进制文件的符号链接。</p>
<p>你如何执行这些？</p>
<p>你当然可以输入 <code>./node_modules/.bin/cowsay</code> 来运行它，它也可以工作，但是 <a href="https://flaviocopes.com/npx/">npx</a>，包括在最近版本的 npm 中（从5.2开始），是一个更好的选择。你只需运行:</p>
<pre><code class="language-js">npx cowsay
</code></pre>
<p>而npx会找到软件包的位置。</p>
<h3 id="packagejson">package.json 指南</h3>
<p>package.json 文件是很多基于 Node.js 生态系统的应用代码库中的一个关键元素。</p>
<p>如果你用 JavaScript 工作，或者你曾经与一个 JavaScript 项目、Node.js 或前端项目互动，你肯定见过 <code>package.json</code> 文件。</p>
<p>那是做什么用的？你应该知道些什么，你可以用它做哪些很酷的事情？</p>
<p><code>package.json</code> 文件是你项目的清单。它可以做很多事情，完全不相关。例如，它是一个工具配置的中央仓库。它也是 <a href="https://flaviocopes.com/npm/">npm</a> 和 <a href="https://flaviocopes.com/yarn/">yarn</a> 存储它所安装的软件包的名称和版本的地方。</p>
<h4 id="">文件结构</h4>
<p>下面是一个 package.json 文件的例子:</p>
<pre><code class="language-json">{}
</code></pre>
<p>它是空的! 对于一个应用程序来说,<code>package.json</code> 文件中应该包含什么并没有固定的要求。唯一的要求是遵循 JSON 格式，否则它不能被试图以编程方式访问其属性的程序读取。</p>
<p>如果你正在构建一个 Node.js 包，你想通过 <code>npm</code> 发布，事情就会发生根本性的变化，你必须有一套属性来帮助其他人使用它。我们将在后面看到更多关于这方面的内容。</p>
<p>这是另一个 package.json:</p>
<pre><code class="language-json">{  "name": "test-project"}
</code></pre>
<p>它定义了一个 <code>name</code> 属性，它告诉了这个文件所在的同一文件夹中包含的应用程序或包的名称。</p>
<p>这里有一个更复杂的例子，我从一个 Vue.js 应用样本中提取了这个例子:</p>
<pre><code class="language-json">{
    "name": "test-project",
    "version": "1.0.0",
    "description": "A Vue.js project",
    "main": "src/main.js",
    "private": true,
    "scripts": {
        "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
        "start": "npm run dev",
        "unit": "jest --config test/unit/jest.conf.js --coverage",
        "test": "npm run unit",
        "lint": "eslint --ext .js,.vue src test/unit",
        "build": "node build/build.js"
    },
    "dependencies": {
        "vue": "^2.5.2"
    },
    "devDependencies": {
        "autoprefixer": "^7.1.2",
        "babel-core": "^6.22.1",
        "babel-eslint": "^8.2.1",
        "babel-helper-vue-jsx-merge-props": "^2.0.3",
        "babel-jest": "^21.0.2",
        "babel-loader": "^7.1.1",
        "babel-plugin-dynamic-import-node": "^1.2.0",
        "babel-plugin-syntax-jsx": "^6.18.0",
        "babel-plugin-transform-es2015-modules-commonjs": "^6.26.0",
        "babel-plugin-transform-runtime": "^6.22.0",
        "babel-plugin-transform-vue-jsx": "^3.5.0",
        "babel-preset-env": "^1.3.2",
        "babel-preset-stage-2": "^6.22.0",
        "chalk": "^2.0.1",
        "copy-webpack-plugin": "^4.0.1",
        "css-loader": "^0.28.0",
        "eslint": "^4.15.0",
        "eslint-config-airbnb-base": "^11.3.0",
        "eslint-friendly-formatter": "^3.0.0",
        "eslint-import-resolver-webpack": "^0.8.3",
        "eslint-loader": "^1.7.1",
        "eslint-plugin-import": "^2.7.0",
        "eslint-plugin-vue": "^4.0.0",
        "extract-text-webpack-plugin": "^3.0.0",
        "file-loader": "^1.1.4",
        "friendly-errors-webpack-plugin": "^1.6.1",
        "html-webpack-plugin": "^2.30.1",
        "jest": "^22.0.4",
        "jest-serializer-vue": "^0.3.0",
        "node-notifier": "^5.1.2",
        "optimize-css-assets-webpack-plugin": "^3.2.0",
        "ora": "^1.2.0",
        "portfinder": "^1.0.13",
        "postcss-import": "^11.0.0",
        "postcss-loader": "^2.0.8",
        "postcss-url": "^7.2.1",
        "rimraf": "^2.6.0",
        "semver": "^5.3.0",
        "shelljs": "^0.7.6",
        "uglifyjs-webpack-plugin": "^1.1.1",
        "url-loader": "^0.5.8",
        "vue-jest": "^1.0.2",
        "vue-loader": "^13.3.0",
        "vue-style-loader": "^3.0.1",
        "vue-template-compiler": "^2.5.2",
        "webpack": "^3.6.0",
        "webpack-bundle-analyzer": "^2.9.0",
        "webpack-dev-server": "^2.9.1",
        "webpack-merge": "^4.1.0"
    },
    "engines": {
        "node": "&gt;= 6.0.0",
        "npm": "&gt;= 3.0.0"
    },
    "browserslist": ["&gt; 1%", "last 2 versions", "not ie &amp;lt;= 8"]
}
</code></pre>
<p>这里有<strong>很多</strong>的事情要做:</p>
<ul>
<li><code>name</code> 设置应用程序/包的名称</li>
<li><code>version</code> 设置应用程序/包的名称</li>
<li><code>description</code> 是对应用程序/包的简要描述</li>
<li><code>main</code> 设置应用程序的入口点</li>
<li><code>private</code> 如果设置为 <code>true</code> 可以防止应用程序/软件包被意外地发布到 <code>npm</code></li>
<li><code>scripts</code> 定义了一组你可以运行的node脚本</li>
<li><code>dependencies</code> 设置一个作为依赖项安装的 <code>npm</code> 包的列表</li>
<li><code>devDependencies</code> 设置一个作为开发依赖的 <code>npm</code> 包的列表</li>
<li><code>engines</code> 设置该软件包/应用程序适用于哪些版本的 Node</li>
<li><code>browserslist</code> 用于告诉你要支持哪些浏览器（以及它们的版本）</li>
</ul>
<p>所有这些属性都被 <code>npm</code> 或其他我们可以使用的工具所使用。</p>
<h4 id="">属性分类</h4>
<p>本节详细描述了你可以使用的属性。我指的是 <code>包（package）</code>，但同样的事情也适用于你不作为包使用的本地应用程序。</p>
<p>这些属性大多只在 npm<a href="https://www.npmjs.com/">网站</a> 上使用，其他的由与你的代码交互的脚本使用，如 <code>npm</code> 或其他。</p>
<h4 id="name"><code>name</code></h4>
<p>设置软件包的名称。</p>
<p>例如:</p>
<pre><code class="language-json">{"name": "test-project"}
</code></pre>
<p>该名称必须少于214个字符，不能有空格，只能包含小写字母、连字符（<code>-</code>）或下划线（<code>_</code>）。</p>
<p>这是因为当一个软件包在 <code>npm</code> 上发布时，它会根据这个属性获得自己的URL。</p>
<h4 id="author"><code>author</code></h4>
<p>列出软件包作者的名字</p>
<p>例如:</p>
<pre><code class="language-json">{
    "author": "Flavio Copes &lt;flavio@flaviocopes.com&gt; (https://flaviocopes.com)"
}
</code></pre>
<p>也可与此格式一起使用:</p>
<pre><code class="language-json">{
    "author": {
        "name": "Flavio Copes",
        "email": "flavio@flaviocopes.com",
        "url": "https://flaviocopes.com"
    }
}
</code></pre>
<h4 id="contributors"><code>contributors</code></h4>
<p>除了作者之外，该项目还可以有一个或多个贡献者。这个属性是一个数组，列出他们。</p>
<p>例如:</p>
<pre><code class="language-json">{
    "contributors": ["Flavio Copes &lt;flavio@flaviocopes.com&gt; (https://flaviocopes.com)"]
}
</code></pre>
<p>也可与此格式一起使用:</p>
<pre><code class="language-json">{
    "contributors": [{
        "name": "Flavio Copes",
        "email": "flavio@flaviocopes.com",
        "url": "https://flaviocopes.com"
    }]
}
</code></pre>
<h4 id="bugs"><code>bugs</code></h4>
<p>链接到软件包的issues跟踪器，很可能是GitHub issues页面</p>
<p>比如:</p>
<pre><code class="language-plain">{  "bugs": "https://github.com/flaviocopes/package/issues"}
</code></pre>
<h4 id="homepage"><code>homepage</code></h4>
<p>设置软件包的主页</p>
<p>例子:</p>
<pre><code class="language-json">{  "homepage": "https://flaviocopes.com/package"}
</code></pre>
<h4 id="version"><code>version</code></h4>
<p>表示软件包的当前版本。</p>
<p>例子:</p>
<pre><code class="language-json">{"version": "1.0.0"}
</code></pre>
<p>这个属性遵循版本的语义版本（semver）符号，这意味着版本总是用3个数字表示。<code>x.x.x</code>。</p>
<p>第一个数字是主要版本，第二个是次要版本，第三个是补丁版本。</p>
<p>这些数字是有意义的：一个只修复bug的版本是补丁版本，一个引入了向后兼容的变化的版本是次要版本，一个主要版本可以有突破性的变化。</p>
<h4 id="license"><code>license</code></h4>
<p>表示该软件包的许可证。</p>
<p>例如:</p>
<pre><code class="language-json">{"license": "MIT"}
</code></pre>
<h4 id="keywords"><code>keywords</code></h4>
<p>这个属性包含一个与你的包所做的事情相关联的关键词。</p>
<p>例如:</p>
<pre><code class="language-json">{"keywords": [  "email",  "machine learning",  "ai"]}
</code></pre>
<p>这有助于人们在浏览类似的软件包或浏览npm官网时找到你的软件包。</p>
<h4 id="description"><code>description</code></h4>
<p>这个属性包含了对软件包的简要描述.</p>
<p>例如:</p>
<pre><code class="language-json">{"description": "A package to work with strings"}
</code></pre>
<p>如果你决定将你的软件包发布到<code>npm</code>上，这样人们就能发现软件包的内容，这就特别有用。</p>
<h4 id="repository"><code>repository</code></h4>
<p>这个属性指定了这个软件包仓库的位置。</p>
<p>例如:</p>
<pre><code class="language-json">{"repository": "github:flaviocopes/testing"}
</code></pre>
<p>注意<code>github</code>前缀。 还有其他受欢迎的服务：:</p>
<pre><code class="language-json">{"repository": "gitlab:flaviocopes/testing"}
</code></pre>
<pre><code class="language-json">{"repository": "bitbucket:flaviocopes/testing"}
</code></pre>
<p>你可以明确地设置所使用的版本控制系统:</p>
<pre><code class="language-json">{"repository": {  "type": "git",  "url": "https://github.com/flaviocopes/testing.git"}}
</code></pre>
<p>你可以使用不同的版本控制系统:</p>
<pre><code class="language-json">{"repository": {  "type": "svn",  "url": "..."}}
</code></pre>
<h4 id="main"><code>main</code></h4>
<p>设置包的入口点。</p>
<p>当你在一个应用程序中导入这个包时，应用程序就会在这里搜索模块的出口。</p>
<p>例如:</p>
<pre><code class="language-json">{"main": "src/main.js"}
</code></pre>
<h4 id="private"><code>private</code></h4>
<p>如果设置为 "true"，可以防止应用程序/软件包被意外地发布在 "npm" 上</p>
<p>例如:</p>
<pre><code class="language-json">{"private": true }
</code></pre>
<h4 id="scripts"><code>scripts</code></h4>
<p>定义了一组你可以运行的 node 脚本</p>
<p>例如:</p>
<pre><code class="language-json">{
  "scripts": {
    "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
    "start": "npm run dev",
    "unit": "jest --config test/unit/jest.conf.js --coverage",
    "test": "npm run unit",
    "lint": "eslint --ext .js,.vue src test/unit",
    "build": "node build/build.js"
  }
}
</code></pre>
<p>这些脚本是命令行应用程序。你可以通过调用 <code>npm run XXXX</code> 或 <code>yarn XXXX</code> 来运行它们，其中 <code>XXXX</code> 是命令名称。</p>
<p>例如:<br>
<code>npm run dev</code></p>
<p>你可以为命令使用任何你想要的名字，而且脚本可以做任何你想要的事情。</p>
<h4 id="dependencies"><code>dependencies</code></h4>
<p>设置一个作为依赖项安装的 <code>npm</code> 包的列表。</p>
<p>当你使用 npm 或 yarn 安装一个包时:</p>
<pre><code class="language-shell">npm install &lt;PACKAGENAME&gt;
yarn add &lt;PACKAGENAME&gt;
</code></pre>
<p>该软件包会自动插入该列表中。</p>
<p>例如:</p>
<pre><code class="language-json"> {"dependencies": {  "vue": "^2.5.2"}}
</code></pre>
<h4 id="devdependencies"><code>devDependencies</code></h4>
<p>设置一个作为开发依赖的 <code>npm</code> 包的列表。</p>
<p>它们与 <code>dependencies</code> 不同，因为它们只能安装在开发机器上，不需要在生产中运行代码。</p>
<p>当你使用 <code>npm</code> 或 <code>yarn</code> 安装一个包时:</p>
<pre><code class="language-shell">npm install --dev &lt;PACKAGENAME&gt;
yarn add --dev &lt;PACKAGENAME&gt;
</code></pre>
<p>该软件包会自动插入该列表中。</p>
<p>比如:</p>
<pre><code class="language-json">{"devDependencies": {  "autoprefixer": "^7.1.2",  "babel-core": "^6.22.1"}}
</code></pre>
<h4 id="engines"><code>engines</code></h4>
<p>设置该软件包/应用程序适用于哪些版本的Node.js和其他命令。</p>
<p>例如:</p>
<pre><code class="language-json">{"engines": {  "node": "&gt;= 6.0.0",  "npm": "&gt;= 3.0.0",  "yarn": "^0.13.0"}}
</code></pre>
<h4 id="browserslist"><code>browserslist</code></h4>
<p>是用来告诉你要支持哪些浏览器（以及它们的版本）。它被 Babel、Autoprefixer 和其他工具引用，只为你的目标浏览器添加所需的 polyfills（降级方案）和fallbacks（回退方案）。</p>
<p>例如:</p>
<pre><code class="language-json">{"browserslist": [  "&gt; 1%",  "last 2 versions",  "not ie &lt;= 8"]}
</code></pre>
<p>这种配置意味着你要支持所有至少有1%使用量的浏览器的最后两个主要版本（来自 <a href="https://caniuse.com/">CanIUse.com</a>统计），但IE8和更低版本除外（<a href="https://www.npmjs.com/package/browserslist">见更多</a> 浏览器列表）。</p>
<h4 id="commandspecificproperties">Command-specific properties</h4>
<p><code>package.json</code> 文件也可以承载特定命令的配置，例如 Babel、ESLint 等等。</p>
<p>每一个都有一个特定的属性，如 <code>eslintConfig</code>，<code>babel</code> 和其他。这些都是特定的命令，你可以在各自的命令/项目文档中找到如何使用它们。</p>
<h4 id="packageversions">Package versions</h4>
<p>你在上面的描述中看到了这样的版本号。<code>~3.0.0</code> 或 <code>^0.13.0</code>。它们是什么意思，你还可以使用哪些其他的版本指定符？</p>
<p>该符号指定了你的软件包接受哪些更新，来自该依赖关系。</p>
<p>鉴于使用semver（语义版本管理），所有的版本都有3位数字，第一位是主要版本，第二位是次要版本，第三位是补丁版本，你有这些规则:</p>
<ul>
<li><code>~</code>: 如果你写 <code>~0.13.0</code>, 你想只更新补丁版本。<code>0.13.1</code>可以，但<code>0.14.0</code>不可以。</li>
<li><code>^</code>: 如果你写 <code>^0.13.0</code>, 你想更新补丁和次要版本。<code>0.13.1</code>, <code>0.14.0</code>等等。</li>
<li><code>*</code>: 如果你写 <code>*</code>, 这意味着你接受所有的更新，包括主要版本的升级。</li>
<li><code>&gt;</code>: 你接受比你指定的版本高的任何版本。</li>
<li><code>&gt;=</code>: 你接受任何等于或高于你指定的版本。</li>
<li><code>&lt;=</code>: 你接受任何等于或低于你指定的版本。</li>
<li><code>&lt;</code>: 你接受比你指定的版本低的任何版本。</li>
</ul>
<p>也有其他规则:</p>
<ul>
<li><code>no symbol</code>：你只接受你指定的那个特定版本</li>
<li><code>latest</code>：你想使用最新的可用版本</li>
</ul>
<p>你可以将上述大部分的范围结合起来，就像这样。<code>1.0.0 || &gt;=1.1.0 &lt; 1.2.0</code>，以使用1.0.0或1.1.0以上的一个版本，但低于1.2.0。</p>
<h3 id="packagelockjson">package-lock.json 文件</h3>
<p>package-lock.json 文件是在安装 Nodo包时自动生成的。</p>
<p>在版本5中，NPM 引入了 <code>package-lock.json</code> 文件。</p>
<p>那是什么？你可能知道 <code>package.json</code> 文件，它更常见，存在的时间也更长。</p>
<p>该文件的目的是跟踪每一个安装的软件包的确切版本，这样，即使软件包被维护者更新，产品也能以同样的方式100%重现。</p>
<p>这解决了 <code>package.json</code> 未解决的一个非常具体的问题。在 package.json 中，你可以使用 <strong>semver</strong> 注解来设置你想升级到哪个版本（补丁或小版本），例如:</p>
<ul>
<li>如果你写 <code>~0.13.0</code>, 你想只更新补丁版本。<code>0.13.1</code>可以，但 <code>0.14.0</code> 不行。</li>
<li>如果你写 <code>^0.13.0</code>, 你想更新补丁和次要版本。<code>0.13.1</code>, <code>0.14.0</code> 等等。</li>
<li>如果你写 <code>0.13.0</code>, 这就是将被使用的确切版本，永远都是 <code>0.13.0</code>。</li>
</ul>
<p>你不会向 Git 提交你的 node_modules 文件夹，它通常是巨大的，当你试图通过使用 <code>npm install</code> 命令在另一台机器上复制项目时，如果你指定了<code>~</code>语法，并且一个包的补丁版本已经发布，那就会被安装。对于 <code>^</code> 和次要版本也是如此。</p>
<p>如果你指定了准确的版本，如例子中的 "0.13.0"，你就不会受到这个问题的影响。</p>
<p>可能是你，也可能是另一个人在世界的另一端试图通过运行 <code>npm install</code> 来初始化这个项目。</p>
<p>所以你的原始项目和新初始化的项目实际上是不同的。即使一个补丁或小版本不应该引入破坏性的变化，我们都知道bug可以（所以，他们会）潜入。</p>
<p><code>package-lock.json</code> 将你当前安装的每个软件包的版本<strong>in stone</strong>上，<code>npm</code> 在运行 <code>npm install</code> 时将使用这些确切的版本。</p>
<p>这个概念并不新鲜，其他编程语言的包管理器（如 PHP 中的 Composer）多年来也使用类似的系统。</p>
<p><code>package-lock.json</code> 文件需要提交到你的 Git 仓库，如果项目是公开的或者你有合作者，或者你使用 Git 作为部署的来源，那么它可以被其他人取走。</p>
<p>当你运行 <code>npm update</code> 时，依赖的版本将在 <code>package-lock.json</code> 文件中更新。</p>
<h4 id="">一个例子</h4>
<p>这是一个 <code>package-lock.json</code> 文件的结构示例，当我们在一个空的文件夹中运行 <code>npm install cowsay</code> 时，我们得到了这个文件:</p>
<pre><code class="language-json">{
    "requires": true, "lockfileVersion": 1, "dependencies": {
        "ansi-regex": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" }, "cowsay": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/cowsay/-/cowsay-1.3.1.tgz", "integrity": "sha512-3PVFe6FePVtPj1HTeLin9v8WyLl+VmM1l1H/5P+BTTDkMAjufp+0F9eLjzRnOHzVAYeIYFF5po5NjRrgefnRMQ==", "requires": { "get-stdin": "^5.0.1", "optimist": "~0.6.1", "string-width": "~2.1.1", "strip-eof": "^1.0.0" } }, "get-stdin": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-5.0.1.tgz", "integrity": "sha1-Ei4WFZHiH/TFJTAwVpPyDmOTo5g=" }, "is-fullwidth-code-point": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" }, "minimist": { "version": "0.0.10", "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=" }, "optimist": {
            "version": "0.6.1", "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=",

                "requires": { "minimist": "~0.0.1", "wordwrap": "~0.0.2" }
        }, "string-width": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", "requires": { "is-fullwidth-code-point": "^2.0.0", "strip-ansi": "^4.0.0" } }, "strip-ansi": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", "requires": { "ansi-regex": "^3.0.0" } }, "strip-eof": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=" }, "wordwrap": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=" }
    }
}
</code></pre>
<p>我们安装了 <code>cowsay</code>，它依赖于:</p>
<ul>
<li><code>get-stdin</code></li>
<li><code>optimist</code></li>
<li><code>string-width</code></li>
<li><code>strip-eof</code></li>
</ul>
<p>反过来，这些软件包需要其他的软件包，我们可以从 "requires "属性中看到，有些软件包有:</p>
<ul>
<li><code>ansi-regex</code></li>
<li><code>is-fullwidth-code-point</code></li>
<li><code>minimist</code></li>
<li><code>wordwrap</code></li>
<li><code>strip-eof</code></li>
</ul>
<p>它们按字母顺序被添加到文件中，每一个都有一个 <code>version</code> 字段，一个指向软件包位置的 <code>resolved</code> 字段，以及一个 <code>integrity</code> 字符串，我们可以用来验证该软件包。</p>
<h3 id="npm">查找一个npm包的安装版本</h3>
<p>查看所有安装的 npm 包的最新版本，包括它们的依赖关系:</p>
<pre><code class="language-shell">npm list
</code></pre>
<p>例如:</p>
<pre><code class="language-shell">❯ npm list/Users/flavio/dev/node/cowsay
  └─┬ cowsay@1.3.1  
    ├── get-stdin@5.0.1  
    ├─┬ optimist@0.6.1  
    │ ├── minimist@0.0.10  
    │ └── wordwrap@0.0.3  
    ├─┬ string-width@2.1.1  
      ├── is-fullwidth-code-point@2.0.0  
      │ └─┬ strip-ansi@4.0.0  
      │   └── ansi-regex@3.0.0  
      └── strip-eof@1.0.0
</code></pre>
<p>你也可以直接打开 <code>package-lock.json</code> 文件，但这涉及一些用眼睛查看。</p>
<p><code>npm list -g</code> 也是一样的，只是针对全局安装的软件包。</p>
<p>要想只得到你的顶层软件包（基本上是你告诉 npm 安装的和你在 <code>package.json</code> 中列出的那些），运行 <code>npm list --depth=0</code>:</p>
<pre><code class="language-shell">❯ npm list --depth=0/Users/flavio/dev/node/cowsay
   └── cowsay@1.3.1
</code></pre>
<p>你可以通过指定名称来获得一个特定软件包的版本:</p>
<pre><code class="language-shell">❯ npm list cowsay/Users/flavio/dev/node/cowsay
  └── cowsay@1.3.1
</code></pre>
<p>这也适用于你安装的软件包的依赖性:</p>
<pre><code class="language-shell">❯ npm list minimist/Users/flavio/dev/node/cowsay
  └─┬ cowsay@1.3.1  
    └─┬ optimist@0.6.1    
      └── minimist@0.0.10
</code></pre>
<p>如果你想查看npm仓库中软件包的最新可用版本，运行<code>npm view [package_name] version</code>:</p>
<pre><code class="language-shell">❯ npm view cowsay version
</code></pre>
<pre><code class="language-plain">1.3.1
</code></pre>
<h3 id="npm">安装一个旧版本的 npm 包</h3>
<p>安装一个旧版本的 npm 包可能对解决兼容性问题有帮助。</p>
<p>你可以使用 <code>@</code> 语法来安装一个npm包的旧版本:</p>
<pre><code class="language-shell">npm install &lt;package&gt;@&lt;;version&gt;
</code></pre>
<p>例如:</p>
<pre><code class="language-shell">npm install cowsay
</code></pre>
<p>安装1.3.1版本（在撰写本文时）。</p>
<p>安装1.2.0 版本:</p>
<pre><code class="language-shell">npm install cowsay@1.2.0
</code></pre>
<p>同样的情况也可以用全局包来做:</p>
<pre><code class="language-shell">npm install -g webpack@4.16.4
</code></pre>
<p>你也可能对列出一个包的所有以前的版本感兴趣。你可以用 <code>npm view &lt;package&gt; versions</code> 来做:</p>
<pre><code class="language-shell">❯ npm view cowsay versions
</code></pre>
<pre><code class="language-shell">[ '1.0.0',  '1.0.1',  '1.0.2',  '1.0.3',  '1.1.0',  '1.1.1',  '1.1.2',  '1.1.3',  '1.1.4',  '1.1.5',  '1.1.6',  '1.1.7',  '1.1.8',  '1.1.9',  '1.2.0',  '1.2.1',  '1.3.0',  '1.3.1' ]
</code></pre>
<h3 id="node">将所有 Node 的依赖关系更新为最新版本</h3>
<p>当你使用 <code>npm install &lt;packagename&gt;</code> 安装一个软件包时，该软件包的最新可用版本会被下载并放在 <code>node_modules文件夹</code> 中，并且在你当前文件夹中的<code>package.json</code> 和 <code>package-lock.json</code> 文件中添加相应条目。</p>
<p>npm会计算依赖关系，并安装那些最新的可用版本。</p>
<p>假设你安装了 <code>[cowsay][85]</code>，一个很酷的命令行工具，可以让你让cow（牛）说<strong>东西</strong>。</p>
<p>当你 <code>npm安装cowsay</code> 时，这个条目被添加到 <code>package.json</code> 文件中:</p>
<pre><code class="language-json">{  "dependencies": {    "cowsay": "^1.3.1"  }}
</code></pre>
<p>这是 <code>package-lock.json</code> 的摘录，为了清晰起见，我把嵌套的依赖关系去掉了:</p>
<pre><code class="language-json">{
    "requires": true,
    "lockfileVersion": 1,
    "dependencies": {
        "cowsay": {
            "version": "1.3.1",
            "resolved": "https://registry.npmjs.org/cowsay/-/cowsay-1.3.1.tgz",
            "integrity": "sha512-3PVFe6FePVtPj1HTeLin9v8WyLl+VmM1l1H/5P+BTTDkMAjufp+0F9eLjzRnOHzVAYeIYFF5po5NjRrgefnRMQ==",
            "requires": {
                "get-stdin": "^5.0.1",
                "optimist": "~0.6.1",
                "string-width": "~2.1.1",
                "strip-eof": "^1.0.0"
            }
        }
    }
}
</code></pre>
<p>现在这2个文件告诉我们，我们安装了cowsay的<code>1.3.1</code>版本，而我们的更新规则是 <code>^1.3.1</code>，对于npm的版本规则（后面会解释）意味着npm可以更新到补丁和小版本。<code>0.13.1</code>，<code>0.14.0</code>，以此类推。</p>
<p>如果有一个新的次要版本或补丁版本，我们输入 <code>npm update</code>，安装的版本就会被更新，<code>package-lock.json</code> 文件就会勤奋地填上新的版本。</p>
<p><code>package.json</code> 保持不变。</p>
<p>为了发现新发布的软件包，你可以运行 <code>npm outdated</code>。</p>
<p>下面是我很久没有更新的一个软件库中的一些过时的软件包的列表:</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/dQXY78UwUHW2iHblpRRLd8YdM4Zvdyf-3ctc" alt="dQXY78UwUHW2iHblpRRLd8YdM4Zvdyf-3ctc" width="600" height="400" loading="lazy"></p>
<p>其中一些更新是主要版本。运行 <code>npm update</code> 不会更新这些版本。主要版本从不以这种方式更新，因为它们（顾名思义）会带来破坏性的变化，而 <code>npm</code> 想为你省去麻烦。</p>
<p>要将所有软件包更新到新的主要版本，请在全局安装 <code>npm-check-updates</code> 包:</p>
<pre><code class="language-shell">npm install -g npm-check-updates
</code></pre>
<p>然后运行:</p>
<pre><code class="language-shell">ncu -u
</code></pre>
<p>这将升级 <code>package.json</code> 文件中的所有版本提示，到 <code>dependencies</code> 和 <code>devDependencies</code>，所以 npm 可以安装新的主要版本（major version）。</p>
<p>现在你已经准备好运行更新:</p>
<pre><code class="language-shell">npm update
</code></pre>
<p>如果你刚下载的项目没有 <code>node_modules</code> 的依赖，你想先安装全新版本，只要运行</p>
<pre><code class="language-shell">npm install
</code></pre>
<h3 id="npm">使用 npm 进行语义版本管理</h3>
<p>语义版本管理（Semantic Versioning）是一种用来为版本提供意义的惯例。</p>
<p>如果说Node.js包中有什么了不起的东西，那就是所有的人都同意使用语义版本控制（Semantic Versioning）来进行版本编号。</p>
<p>语义版本控制的概念很简单：所有的版本都有3位数字。<code>x.y.z</code>。</p>
<ul>
<li>第一个数字是主版本（major version）</li>
<li>第二位数字是次要版本 （minor version）</li>
<li>第三位数字是补丁版本 （patch version）</li>
</ul>
<p>当你制作一个新的版本时，你不会随心所欲地提高一个数字，而是有规则的:</p>
<ul>
<li>当你对API进行不兼容的修改时，你要提高主版本的等级</li>
<li>当你以向后兼容的方式增加功能时，你要提高次要版本的数量</li>
<li>当你进行向后兼容的 bug 修复时，你要提高补丁版本</li>
</ul>
<p>这个惯例在所有的编程语言中都被采用，而且每个 <code>npm</code> 包都要遵守这个惯例，这一点非常重要，因为整个系统都依赖于此。</p>
<p>为什么这么重要？</p>
<p>因为 <code>npm</code> 设置了一些规则，我们可以在 <a href="https://flaviocopes.com/package-json/">package.json文件</a> 中使用，以便在运行 <code>npm update</code> 时，选择它可以将我们的软件包更新到哪些版本。</p>
<p>规则使用这些符号:</p>
<ul>
<li><code>^</code></li>
<li><code>~</code></li>
<li><code>=</code></li>
<li><code>&gt;=</code></li>
<li><code>&lt;</code></li>
<li><code>&lt;=</code></li>
<li><code>=</code></li>
<li><code>-</code></li>
<li><code>||</code></li>
</ul>
<p>让我们看看这些规则的细节:</p>
<ul>
<li><code>^</code>: 如果你写 <code>^0.13.0</code>，当运行 <code>npm update</code>， 它可以更新到补丁（patch）和次要版本（minor version）: <code>0.13.1</code>, <code>0.14.0</code> 等。</li>
<li><code>~</code>: 如果你写 <code>~0.13.0</code>, 当运行 <code>npm update</code>，它可以更新到补丁（patch）: <code>0.13.1</code>是可以的, 但 <code>0.14.0</code> 不行。</li>
<li><code>&gt;</code>: 你接受比你指定的版本高的任何版本</li>
<li><code>&gt;=</code>: 你接受任何等于或高于你指定的版本</li>
<li><code>&lt;=</code>: 你接受任何等于或低于你指定的版本</li>
<li><code>&lt;</code>: 你接受比你指定的版本低的任何版本</li>
<li><code>=</code>: 你接受确切的版本</li>
<li><code>-</code>: 你接受一定范围的版本。例如: <code>2.1.0 - 2.6.2</code></li>
<li><code>||</code>: 你结合一组。例如: <code>&lt; 2.1 ||  &gt; 2.6</code></li>
</ul>
<p>你可以结合这些符号，例如使用 <code>1.0.0 || &gt;=1.1.0 &lt;1.2.0</code> 来表示使用 1.0.0 或 1.1.0 以上的一个版本，但低于1.2.0。</p>
<p>也有其他规则:</p>
<ul>
<li><code>no symbol</code>：你只接受你指定的那个特定版本（<code>1.2.1</code>）。</li>
<li><code>latest</code>：你想使用最新的可用版本</li>
</ul>
<h3 id="npm">在本地或全球范围内卸载 npm 包</h3>
<p>要卸载你以前<strong>本地</strong>安装的软件包（使用 <code>npm install &lt;package-name&gt;</code> 在 node_modules 文件夹下），请运行:</p>
<pre><code class="language-shell">npm uninstall &lt;package-name&gt;
</code></pre>
<p>使用 <code>-S</code> 标志，或 <code>--save</code>，这个操作也将删除 <a href="https://flaviocopes.com/package-json/">package.json 文件</a> 中的引用。</p>
<p>如果软件包是一个开发依赖，列在 <code>package.json</code> 文件的 devDependencies 中，你必须使用 <code>-D</code>/<code>--save-dev</code> 标志从文件中删除它:</p>
<pre><code class="language-shell">npm uninstall -S &lt;package-name&gt;
npm uninstall -D &lt;package-name&gt;
</code></pre>
<p>如果软件包在全局范围内**安装，你需要添加 <code>-g</code>/<code>--global</code> 标志:</p>
<pre><code class="language-shell">npm uninstall -g &lt;package-name&gt;
</code></pre>
<p>例如:</p>
<pre><code class="language-shell">npm uninstall -g webpack
</code></pre>
<p>你可以在你系统的任何地方运行这个命令，因为你目前所在的文件夹并不重要。</p>
<h3 id="npm">全局或本地软件 npm 包</h3>
<p>一个软件包最好在什么时候全局安装？以及为什么？</p>
<p>本地包和全局包的主要区别:</p>
<ul>
<li><strong>本地包</strong> 安装在你运行 <code>npm install &lt;package-name&gt;</code> 的目录下，并且它们被放在这个目录下的 node_modules 文件夹中。</li>
<li><strong>全局包</strong> 都放在你系统中的一个地方（具体位置取决于你的设置），不管你在哪里运行 <code>npm install -g &lt;package-name&gt;</code>。</li>
</ul>
<p>在你的代码中，它们都是以同样的方式被导入:</p>
<pre><code class="language-js">require('package-name')
</code></pre>
<p>那么，你应该在什么时候以一种或另一种方式安装呢？</p>
<p>一般来说，所有的软件包都应该以<strong>本地包</strong>的方式安装。</p>
<p>这可以确保你的电脑中可以有几十个应用程序，如果需要的话，都可以运行每个软件包的不同版本。</p>
<p>更新全局软件包会使你的所有项目都使用新的版本，你可以想象这可能会在维护方面造成噩梦，因为一些软件包可能会破坏与其他依赖的兼容性，等等。</p>
<p>所有的项目都有自己的本地包，即使这看起来是一种资源的浪费，但与可能产生的负面影响相比，它是微不足道的。</p>
<p>当一个软件包提供了一个可执行的命令，你可以从shell（CLI）中运行，并且在不同的项目中重复使用时，它应该被全局地安装。</p>
<p>你也可以在本地安装可执行命令，并使用 <a href="https://flaviocopes.com/npx/">npx</a> 运行它们，但有些软件包最好是全局安装。</p>
<p>你可能知道的流行的全局软件包的很好的例子:</p>
<ul>
<li><code>npm</code></li>
<li><code>create-react-app</code></li>
<li><code>vue-cli</code></li>
<li><code>grunt-cli</code></li>
<li><code>mocha</code></li>
<li><code>react-native-cli</code></li>
<li><code>gatsby-cli</code></li>
<li><code>forever</code></li>
<li><code>nodemon</code></li>
</ul>
<p>你的系统中可能已经有一些全局安装的软件包。你可以通过运行:</p>
<pre><code class="language-shell">npm list -g --depth 0
</code></pre>
<p>在你的终端.</p>
<h3 id="npmdependenciesdevdependencies">npm dependencies（依赖关系） 和 devDependencies（开发依赖关系）</h3>
<p>一个包什么时候是依赖关系，什么时候是开发依赖关系？</p>
<p>当你使用 <code>npm install &lt;package-name&gt;</code> 安装一个 npm 包时，你是把它为一个<strong>依赖</strong>。</p>
<p>该包会自动列在 package.json 文件的 <code>dependencies</code> 列表中（从 npm 5 开始：在你必须手动指定 <code>--save</code> 之前）。</p>
<p>当你添加 <code>-D</code> 标志，或 <code>--save-dev</code> 时，你就把它作为一个开发依赖项来安装，这就把它添加到 <code>devDependencies</code> 列表中。</p>
<p><strong>开发依赖</strong>是指仅用于开发的软件包，在生产中不需要。例如，测试包、webpack 或 Babel。</p>
<p>当你在 <strong>生产环境</strong> 中时，如果你输入 <code>npm install</code>，并且该文件夹包含 <code>package.json</code> 文件，它们就会被安装（译者注： devDependencies 的 npm 包也会被安装），因为 npm 认为这是一个开发部署。</p>
<p>你需要设置 <code>--production</code> flag （<code>npm install --production</code>），以避免安装这些开发依赖项。</p>
<h3 id="npxnode">npx Node包运行器</h3>
<p><code>npx</code> 是一种非常酷的运行 Node.js 代码的方式，并提供了许多有用的功能。</p>
<p>在本节中，我想介绍一个非常强大的命令，从2017年7月发布的5.2版本开始，<strong>npm</strong> 中就有这个命令,<strong>npx</strong>。</p>
<p>如果你不想安装 npm，你可以将 npx 作为一个 <a href="https://www.npmjs.com/package/npx">独立的包</a> 来安装。</p>
<p><code>npx</code> 让你运行用 Node.js 构建并通过 npm 注册表发布的代码。</p>
<h4 id="">轻松地运行本地命令</h4>
<p>Node.js 的开发者曾经将大多数可执行的命令作为全局包发布，以便让它们添加到系统的路径（path）中并可以执行。</p>
<p>这是一种痛苦，因为你无法真正安装同一命令的不同版本。</p>
<p>运行 <code>npx commandname</code> 会自动在项目的 <code>node_modules</code> 文件夹中找到该命令的正确引用，不需要知道确切的路径，也不需要在全局和用户的路径中安装包。</p>
<h4 id="">免安装的命令执行</h4>
<p><code>npm</code> 还有一个很好的功能，就是允许运行命令而不需要先安装它们。</p>
<p>这相当有用，主要是因为:</p>
<ol>
<li>你不需要安装任何东西</li>
<li>你可以使用语法 <code>@version</code> 来运行同一命令的不同版本</li>
</ol>
<p>使用 <code>npx</code> 的一个典型示范是通过 <code>cowsay</code> 命令。<code>cowsay</code> 将打印一头牛，说你在命令中写的东西。比如:</p>
<p><code>cowsay "Hello"</code> 将打印出</p>
<pre><code class="language-shell">&lt; Hello &gt;
 -------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||
</code></pre>
<p>现在，这需要你之前从npm全局安装了 <code>cowsay</code> 命令，否则当你试图运行该命令时，你会得到一个错误。</p>
<p><code>npx</code> 允许你在没有本地安装的情况下运行该npm命令:</p>
<pre><code class="language-shell">npx cowsay "Hello"
</code></pre>
<p>现在，这是一个有趣的无用命令。其他情况包括:</p>
<ul>
<li>运行 <code>vue CLI</code> 工具来创建新的应用程序并运行它们：<code>npx vue create my-vue-app</code></li>
<li>使用 <code>create-react-app</code> 创建一个新的React应用：<code>npx create-react-app my-react-app</code></li>
</ul>
<p>以及更多。</p>
<p>一旦下载，下载的代码将被抹去。</p>
<h4 id="nodejs">使用不同的 Node.js 版本运行一些代码</h4>
<p>使用 <code>@</code> 来指定版本，并将其与 <code>node</code> npm 包相结合:</p>
<pre><code class="language-shell">npx node@6 -v #v6.14.3
npx node@8 -v #v8.11.3
</code></pre>
<p>这有助于避免像 <code>nvm</code> 或其他 Node 版本管理工具的使用。</p>
<h4 id="url">直接从 URL 中运行任意的代码片段</h4>
<p><code>npx</code> 并不仅能运行在npm注册表上发布的软件包。</p>
<p>你可以运行位于 <a href="https://flaviocopes.com/github/">GitHub</a> gist 中的代码，例如:</p>
<pre><code class="language-shell">npx https://gist.github.com/zkat/4bc19503fe9e9309e2bfaa2c58074d32
</code></pre>
<p>当然，在运行不受你控制的代码时，你需要小心，因为巨大的权力伴随着巨大的责任。</p>
<h3 id="theeventloop">The Event Loop（事件循环）</h3>
<p>事件循环是了解JavaScript的最重要的方面之一。本节解释了JavaScript如何在单线程中工作的内部细节，以及它如何处理异步函数。</p>
<p>我已经用JavaScript编程多年了，但我从来没有 <strong>完全</strong> 理解过事情是如何在幕后运作的。不了解这个概念的细节是完全可以的。但是像往常一样，知道它是如何工作的是很有帮助的，而且在这一点上你可能只是有点好奇。</p>
<p>你的JavaScript代码是单线程（single threaded）运行的。每次只有一件事在发生。</p>
<p>这是一个实际上非常有帮助的限制，因为它简化了很多你编程的方式，而不用担心并发问题。</p>
<p>你只需要注意如何写你的代码，避免任何可能阻塞线程的东西，比如同步网络调用或无限 <a href="https://flaviocopes.com/javascript-loops/">循环</a>。</p>
<p>一般来说，在大多数浏览器中，每个浏览器标签都有一个事件循环，以使每个进程都被隔离，避免一个有无限循环的网页或繁重的处理过程阻塞你整个浏览器。</p>
<p>该环境管理着多个并发的事件循环，以处理例如API调用。<a href="https://flaviocopes.com/web-workers/">Web Workers</a> 也是在自己的事件循环中运行。</p>
<p>你主要需要关注的是，<strong>你的代码</strong>会在一个事件循环中运行，写代码时要考虑到这个事情，避免阻塞它。</p>
<h4 id="">阻塞事件循环</h4>
<p>任何 JavaScript 代码，如果需要花费太长的时间将控制权返回到事件循环中，就会阻断页面中任何 JavaScript 代码的执行，甚至阻断UI线程，用户就无法点击、滚动页面，等等。</p>
<p>JavaScript 中几乎所有的 I/O 原生语句都是无阻塞的。如网络请求、Node.js 文件系统操作，等等。阻塞是个例外，这就是为什么 JavaScript 如此基于回调，以及最近的 promises 和 async/await 的原因。</p>
<h4 id="thecallstack">The call stack（调用栈）</h4>
<p>调用栈是一个 LIFO 队列（Last In, First Out）。</p>
<p>事件循环不断检查<strong>调用栈（call stack）</strong>，看是否有任何函数需要运行。</p>
<p>在这样做的同时，它将发现的任何函数调用添加到调用栈中，并按顺序执行每个函数。</p>
<p>你知道你可能熟悉的错误堆栈跟踪，在调试器或浏览器控制台中？</p>
<p>浏览器查找调用堆栈中的函数名称，以告知你当前调用是由哪个函数发起的：</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/SFxrWa7lVtAfUsjnjoMqgCGdG4bK0jDvi-11" alt="SFxrWa7lVtAfUsjnjoMqgCGdG4bK0jDvi-11" width="600" height="400" loading="lazy"></p>
<h4 id="">一个简单的事件循环解释</h4>
<p>举个例子:</p>
<pre><code class="language-js">const bar = () =&gt; console.log('bar')
const baz = () =&gt; console.log('baz')
const foo = () =&gt; {  
  console.log('foo')  
  bar()  
  baz()
}

foo()
</code></pre>
<p>这段代码打印出:</p>
<pre><code class="language-js">foo
bar
baz
</code></pre>
<p>正如预期的那样。</p>
<p>当这段代码运行时，首先调用 <code>foo()</code>。在 <code>foo()</code> 中，我们首先调用 <code>bar()</code>，然后我们调用<code>baz()</code>。</p>
<p>在这一点上，调用栈看起来像这样:</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/bFPM-QZwRcB6APbq6sSJpyQMZHWRACvJzAly" alt="bFPM-QZwRcB6APbq6sSJpyQMZHWRACvJzAly" width="600" height="400" loading="lazy"></p>
<p>事件循环在每个迭代中都会查看调用栈中是否有东西，并执行它:</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/T3jPPIkLHGvy0QXBrUz8cb3VM0bVVez-joQ4" alt="T3jPPIkLHGvy0QXBrUz8cb3VM0bVVez-joQ4" width="600" height="400" loading="lazy"></p>
<p>直到调用栈为空。</p>
<h4 id="">排列函数执行</h4>
<p>上面的例子看起来很正常，没有什么特别的地方。JavaScript找到要执行的东西，按顺序运行它们。</p>
<p>我们来看看如何推迟一个函数，直到堆栈清空。</p>
<p><code>setTimeout(() =&gt; {},0)</code> 的用例是调用一个函数，但要在代码中的其他函数都执行完后再执行它。</p>
<p>以此为例:</p>
<pre><code class="language-js">const bar = () =&gt; console.log('bar')

const baz = () =&gt; console.log('baz')

const foo = () =&gt; {  
  console.log('foo') 
  setTimeout(bar, 0) 
  baz()
}

foo()
</code></pre>
<p>这段代码的打印结果，也许是令人惊讶的:</p>
<pre><code class="language-js">foo
baz
bar
</code></pre>
<p>当这段代码运行时，首先 <code>foo()</code> 被调用。在 <code>foo()</code> 中，我们首先调用 <code>setTimeout</code>，传递 <code>bar</code> 作为参数，我们指示它立即尽可能快地运行，传递 <code>0</code> 作为计时器。然后我们调用 <code>baz()</code>。</p>
<p>在这一点上，调用栈看起来像这样:</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/iUnlUVBLW8ozpE2ewbJswyp9tOP5OzPUXn0-" alt="iUnlUVBLW8ozpE2ewbJswyp9tOP5OzPUXn0-" width="600" height="400" loading="lazy"></p>
<p>以下是我们程序中所有函数的执行顺序:</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/MsT6C2UAZJaEEm6SmU266PO-V4b-DY0wlMqb" alt="MsT6C2UAZJaEEm6SmU266PO-V4b-DY0wlMqb" width="600" height="400" loading="lazy"></p>
<p>为什么会发生这种情况？</p>
<h4 id="">消息队列</h4>
<p>当 <code>setTimeout()</code> 被调用时，浏览器或Node.js开始计时。一旦定时器过期，在这种情况下，由于我们把 <code>0</code> 作为超时，回调函数就会被放到<strong>消息队列</strong>中。</p>
<p>消息队列也是用户启动的事件（如点击和键盘事件或获取响应）在您的代码有机会对其做出反应之前排队的地方。 或者像 <code>onLoad</code> 这样的 DOM 事件。</p>
<p>循环优先考虑调用栈。 它首先处理它在调用栈中找到的所有内容，一旦那里没有任何内容，它就会去获取消息队列中的内容。</p>
<p>我们不必等待像 <code>setTimeout</code>、fetch或其他东西的函数来做它们自己的工作，因为它们是由浏览器提供的，而且它们生活在自己的线程上。例如，如果你把 <code>setTimeout</code> 的超时时间设为2秒，你就不必等待2秒，等待发生在其他地方。</p>
<h4 id="es6jobqueuees6">ES6 Job Queue（ES6任务队列）</h4>
<p>ECMAScript 2015引入了Job Queue的概念，Promises（也在ES6/ES2015中引入）也使用了这个概念。这是一种尽快执行异步函数结果的方法，而不是放在调用栈的最后。</p>
<p>在当前函数结束之前解析的 Promise 将在当前函数之后立即执行。</p>
<p>我觉得用游乐园的过山车来比喻很好：消息队列让你回到队列中，排在所有其他人之后，而工作队列是一张快速通行票，让你在完成前一项工作后马上再坐一次。</p>
<p>Example:</p>
<pre><code class="language-js">const bar = () =&gt; console.log('bar')

const baz = () =&gt; console.log('baz')

const foo = () =&gt; {  
  console.log('foo')  
  setTimeout(bar, 0)  
  new Promise((resolve, reject) =&gt; resolve('should be right after baz, before bar'))
                                  .then(resolve =&gt; console.log(resolve))
                                  
  bar()
}

foo()
</code></pre>
<p>这打印出:</p>
<pre><code class="language-js">foo
baz
should be right after foo, before barbar
</code></pre>
<p>这是Promises（以及建立在Promises 之上的 <code>async/await</code>）和通过 <code>setTimeout()</code> 或其他平台API的普通异步函数之间的巨大区别。</p>
<h3 id="processnexttick">理解process.nextTick()</h3>
<p>当你试图理解Node.js的事件循环时，它的一个重要部分是 <code>process.nextTick()</code>。它以一种特殊的方式与事件循环互动。</p>
<p>每当事件循环进行一次完整的旅行，我们称它为tick。</p>
<p>当我们向 <code>process.nextTick()</code> 传递一个函数时，我们指示引擎在当前操作结束时，在下一个事件循环tick开始前调用这个函数:</p>
<pre><code class="language-js">process.nextTick(() =&gt; {
  //do something
})
</code></pre>
<p>事件循环正忙于处理当前的函数代码。</p>
<p>当这个操作结束时，JavaScript引擎会运行该操作期间传递给 <code>nextTick</code> 调用的所有函数。</p>
<p>这是我们可以告诉JavaScript引擎异步处理一个函数的方法（在当前函数之后），但要尽快，不要排队。</p>
<p>调用 <code>setTimeout(() =&gt; {}, 0)</code> 将在下一个tick中执行该函数，比使用 <code>nextTick()</code> 时晚得多。</p>
<p>当你想确保在下一个事件循环迭代中，代码已经被执行时，使用 <code>nextTick()</code>。</p>
<h3 id="setimmediate">了解 setImmediate()</h3>
<p>当你想异步执行一些代码，但又想尽快执行时，一个选择是使用 Node.js 提供的 <code>setImmediate()</code> 函数:</p>
<pre><code class="language-js">setImmediate(() =&gt; {
  //run something
})
</code></pre>
<p>作为 <code>setImmediate()</code> 参数传递的函数都是一个回调，会在事件循环的下一次迭代中执行。</p>
<p><code>setImmediate()</code> 与 <code>setTimeout(() =&gt; {}, 0)</code>（传递0ms的超时）和 <code>from process.nextTick()</code> 有何不同？</p>
<p>传递给 <code>process.nextTick()</code> 的函数将在事件循环的当前迭代中执行，在当前操作结束后。这意味着它将总是在 <code>setTimeout()</code> 和 <code>setImmediate()</code> 之前执行。</p>
<p>具有0ms延迟的 <code>setTimeout()</code> 回调与 <code>setImmediate()</code> 非常相似。执行顺序取决于各种因素，但它们都将在事件循环的下一次迭代中运行。</p>
<h3 id="">定时器</h3>
<p>在编写JavaScript代码时，你可能想延迟一个函数的执行。学习如何使用 <code>setTimeout()</code> 和 <code>setInterval()</code> 来安排未来的函数。</p>
<h4 id="settimeout"><code>setTimeout()</code></h4>
<p>在编写JavaScript代码时，你可能想延迟一个函数的执行。这就是 <code>setTimeout</code> 的工作。</p>
<p>你可以指定一个稍后执行的回调函数，以及一个表达你希望它多长时间运行的值，单位是毫秒:</p>
<pre><code class="language-js">setTimeout(() =&gt; {
  //do something
}, 2000)// runs after 2 seconds
</code></pre>
<pre><code class="language-js">setTimeout(() =&gt; {
  //do something
}, 50)// runs after 50 milliseconds
</code></pre>
<p>这种语法定义了一个新的函数。你可以在这里调用任何你想调用的其他函数，或者你可以传递一个现有的函数名和一组参数:</p>
<pre><code class="language-js">const myFunction = (firstParam, secondParam) =&gt; {
  // do something
}
setTimeout(myFunction, 2000, firstParam, secondParam)// runs after 2 seconds
</code></pre>
<p><code>setTimeout()</code> 返回定时器ID。这通常是不使用的，但你可以存储这个ID，如果你想删除这个预定函数的执行，可以清除它:</p>
<pre><code class="language-js">const id = setTimeout(() =&gt; {
  // should run after 2 seconds
}, 2000) 
clearTimeout(id)   // 取消定时器
</code></pre>
<h4 id="zerodelay">Zero delay（零延迟）</h4>
<p>如果你指定超时延迟为 <code>0</code>，回调函数将尽快执行，但在当前函数执行之后:</p>
<pre><code class="language-js">setTimeout(() =&gt; {
  console.log('after ')
}, 0)

console.log(' before ')
</code></pre>
<p>将打印出 <code>before after</code>.</p>
<p>这对于避免在密集型任务上阻塞CPU，并在执行繁重的计算时让其他函数被执行，通过在调度器中排队的函数特别有用。</p>
<p>一些浏览器（IE 和 Edge）实现了一个 <code>setImmediate()</code> 方法，可以实现这个完全相同的功能，但它不是标准的，<a href="https://caniuse.com/#feat=setimmediate">在其他浏览器上不可用</a>。但它是 Node.js 的一个标准函数。</p>
<h4 id="setinterval"><code>setInterval()</code></h4>
<p><code>setInterval()</code> 是一个类似于 <code>setTimeout()</code> 的函数，但有一点不同。它不是运行一次回调函数，而是在你指定的特定时间间隔（以毫秒为单位）永远运行它:</p>
<pre><code class="language-js">setInterval(() =&gt; {
    // runs every 2 seconds
}, 2000) 
</code></pre>
<p>上面的函数每2秒运行一次，除非你用 <code>clearInterval</code> 告诉它停止，并把 <code>setInterval</code> 返回的定时器id传给它:</p>
<pre><code class="language-js">const id = setInterval(() =&gt; {
  // runs every 2 seconds
}, 2000)
</code></pre>
<p>通常在 <code>setInterval</code> 回调函数中调用 <code>clearInterval</code>，让它自动判断是否应该再次运行或停止。例如，这段代码在 <code>App.somethingIWait</code> 的值为 <code>arrived</code> 时才运行。</p>
<pre><code class="language-js">const interval = setInterval(function() {  
  if (App.somethingIWait === 'arrived') {    
    clearInterval(interval)
  }}, 100)// otherwise do things  
</code></pre>
<h4 id="">递归设置超时</h4>
<p><code>setInterval</code> 每隔 <code>n</code> 毫秒启动一个函数，完全不考虑函数何时执行完毕。</p>
<p>如果一个函数花费的时间总是一样的，那就没问题了:</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/eyf875I-cxYqAgNDSeh7CeLg4RXdJIgJphEw" alt="eyf875I-cxYqAgNDSeh7CeLg4RXdJIgJphEw" width="600" height="400" loading="lazy"></p>
<p>也许该函数需要不同的执行时间，取决于网络条件，例如:</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/ge2DPdTuZwHnJIyUH9VSLok1J5WHPOlc1DML" alt="ge2DPdTuZwHnJIyUH9VSLok1J5WHPOlc1DML" width="600" height="400" loading="lazy"></p>
<p>也许一个长期执行的项目会与下一个项目重叠:</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/I9kJc6l-BIT850OGlNDJre80RcsLp7N4amvy" alt="I9kJc6l-BIT850OGlNDJre80RcsLp7N4amvy" width="600" height="400" loading="lazy"></p>
<p>为了避免这种情况，你可以安排一个递归的setTimeout，当回调函数完成时被调用:</p>
<pre><code class="language-js">const myFunction = () =&gt; {  // do something
   setTimeout(myFunction, 1000)
} 
   
setTimeout(myFunction()}, 1000)
</code></pre>
<p>实现这一设想:</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/B2kod2dFuR5U1uwaaW9SGiC1zX5gIUEaiJ8A" alt="B2kod2dFuR5U1uwaaW9SGiC1zX5gIUEaiJ8A" width="600" height="400" loading="lazy"></p>
<p><code>setTimeout</code> 和 <code>setInterval</code> 在 Node.js 中也可以使用，通过 <a href="https://nodejs.org/api/timers.html">Timer模块</a>。</p>
<p>Node.js还提供了 <code>setImmediate()</code>，相当于使用 <code>setTimeout(() =&gt; {}, 0)</code>，主要用于与 Node.js 事件循环一起工作。</p>
<h3 id="asynchronousprogrammingcallbacks">异步编程（Asynchronous Programming）和回调（Callbacks）</h3>
<p>JavaScript 默认是同步的，而且是单线程的。这意味着，代码不能创建新的线程，也不能并行运行。</p>
<h4 id="">编程语言中的异步性</h4>
<p>计算机在设计上是异步的。</p>
<p>异步的意思是，事情可以独立于主程序流程而发生。</p>
<p>在目前的消费类计算机中，每个程序都在一个特定的时间段内运行，然后它停止执行，让另一个程序继续执行。这件事的运行周期非常快，不可能注意到，我们认为我们的计算机同时运行许多程序，但这是一种错觉（除了在多处理器机器上）。</p>
<p>程序在内部使用<strong>中断</strong>，这是一种向处理器发出的信号，以获得系统的处理。</p>
<p>我不会去研究这个的内部情况，但只要记住，程序是异步的，在需要注意之前停止执行是正常的，计算机可以在这期间执行其他事情。当一个程序在等待来自网络的响应时，它不能停止处理器，直到请求完成。</p>
<p>通常情况下，编程语言是同步的，有些语言提供了一种管理异步性的方法，在语言中或通过库。C、Java、C#、PHP、Go、Ruby、Swift、Python，它们默认都是同步的。其中一些语言通过使用线程来处理异步性，生成一个新的进程。</p>
<h4 id="javascript">JavaScript</h4>
<p>JavaScript 默认是<strong>同步的</strong>，并且是单线程的。 这意味着代码无法创建新线程并并行运行。</p>
<p>代码行一个接一个地依次执行。</p>
<p>例如:</p>
<pre><code class="language-js">const a = 1
const b = 2
const c = a * b
console.log(c)
doSomething()
</code></pre>
<p>但是 JavaScript 是在浏览器内部诞生的。 一开始，它的主要工作是响应用户操作，如 <code>onClick</code>、<code>onMouseOver</code>、<code>onChange</code>、<code>onSubmit</code> 等。 它如何使用同步编程模型做到这一点？</p>
<p>答案就在它的环境中。 <strong>浏览器</strong>通过提供一组可以处理此类功能的 API 来提供一种方法。</p>
<p>最近，Node.js 引入了非阻塞 I/O 环境，将这一概念扩展到文件访问、网络调用等。</p>
<h4 id="callbacks">Callbacks（回调）</h4>
<p>你不可能知道用户何时会点击一个按钮，所以你要做的是<strong>为点击事件定义一个事件处理程序</strong>。</p>
<p>这个事件处理程序接受一个函数，当事件被触发时，该函数将被调用:</p>
<pre><code class="language-js">document.getElementById('button')
        .addEventListener('click', () =&gt; {
            console.log('button clicked')
        })//item clicked
</code></pre>
<p>这就是所谓的<strong>回调</strong>。</p>
<p>回调是一个简单的函数，它作为一个值传递给另一个函数，只有当事件发生时才会被执行。我们可以这样做，因为JavaScript有的函数是一等公民，它可以被分配给变量并传递给其他函数（称为<strong>高阶函数</strong>）。</p>
<p>常见的做法是将所有的客户端代码包裹在 "window "对象的 "load "事件监听器中，只有当页面准备好时才运行回调函数:</p>
<pre><code class="language-js">window.addEventListener('load', () =&gt; {})//window loaded  //do what you want
</code></pre>
<p>回调无处不在，不仅仅是在DOM事件中使用。</p>
<p>一个常见的例子是通过使用定时器:</p>
<pre><code class="language-js">setTimeout(() =&gt; {  
  // runs after 2 seconds
}, 2000)
</code></pre>
<p><a href="https://en.wikipedia.org/wiki/XMLHttpRequest">XHR请求</a> 也接受回调，在这个例子中，通过给一个属性分配一个函数，当一个特定的事件发生时（在这个例子中，请求的状态改变），该函数将被调用:</p>
<pre><code class="language-js">const xhr = new XMLHttpRequest()
xhr.onreadystatechange = () =&gt; {  
   if (xhr.readyState === 4) {
      xhr.status === 200 ? console.log(xhr.responseText) : console.error('error')
}}

xhr.open('GET', 'https://yoursite.com')
xhr.send()
</code></pre>
<h4 id="">处理回调中的错误</h4>
<p>你如何用回调来处理错误？一个很常见的策略是使用 Node.js 采用的方法：任何回调函数的第一个参数是错误对象，错误优先的回调。</p>
<p>如果没有错误，该对象是 <code>null</code>。如果有错误，它包含一些错误的描述和其他信息。</p>
<pre><code class="language-js">fs.readFile('/file.json', (err, data) =&gt; {  
      if (err !== null) {    //handle error    
         console.log(err)    
         return 
      }
      console.log(data) //no errors, process data 
})
</code></pre>
<h4 id="">使用回调会遇到的问题</h4>
<p>回调非常适合简单的情况！</p>
<p>但是，每个回调都会增加一层嵌套。 当你有很多回调时，代码很快就会变得复杂:</p>
<pre><code class="language-js">window.addEventListener('load', () =&gt; {  
  document.getElementById('button')
          .addEventListener('click', () =&gt; {
            setTimeout(() =&gt; {
              items.forEach(item =&gt; {}) 
            }, 2000)
          })     //your code here
})
</code></pre>
<p>这只是一个简单的4级代码，但我见过更多级别的嵌套，这很不好玩。</p>
<p>我们如何解决这个问题呢？</p>
<h4 id="">回调的替代方案</h4>
<p>从ES6开始，JavaScript 引入了几个功能，帮助我们处理不涉及使用回调的异步代码:</p>
<ul>
<li>Promises (ES6)</li>
<li>Async/Await (ES8)</li>
</ul>
<h3 id="promises">Promises</h3>
<p>Promises 是处理 JavaScript 中异步代码的一种方式，而不需要在代码中写太多的回调。</p>
<h4 id="promises">Promises 简介</h4>
<p>承诺通常被定义为<strong>一个最终会变得可用的值的代理</strong>。</p>
<p>虽然已经存在多年，但它们在 ES2015 中被标准化并引入，现在它们在 ES2017 中被 async 函数所取代。</p>
<p>**异步函数（Async functions）**使用 Promise API 作为其构建模块，因此，即使在较新的代码中，你可能会使用异步函数而不是 Promise，理解它们也是基本的。</p>
<h4 id="promises">Promises是如何工作的</h4>
<p>一旦一个 Promises 被调用，它将开始处于<strong>待定状态</strong>。这意味着调用者函数继续执行，同时等待 Promises 做它自己的处理，并给调用者函数一些反馈。</p>
<p>在这一点上，调用者函数等待它在**解决的状态（resolved state）<strong>下返回promises，或者在</strong>拒绝的状态（rejected state）**下返回 Promises，但是你知道 JavaScript 是异步的，所以函数继续执行，而承诺在做它的工作。</p>
<h4 id="jsapipromises">哪些 JS API 使用 Promises?</h4>
<p>除了你自己的代码和库的代码外，Promises 还被标准的现代网络 API 所使用，如:</p>
<ul>
<li><strong><em>the Battery API</em></strong></li>
<li>the <a href="https://flaviocopes.com/fetch-api/">Fetch API</a></li>
<li><a href="https://flaviocopes.com/service-workers/">Service Workers</a></li>
</ul>
<p>在现代 JavaScript 中，你发现自己<strong>不可能不</strong>使用承诺，所以让我们直接开始研究它们。</p>
<h4 id="promise">创建 Promise</h4>
<p>Promise API 暴露了一个 Promise 构造函数，你可以使用 <code>new Promise()</code> 来初始化它:</p>
<pre><code class="language-js">let done = true
const isItDoneYet = new Promise(  (resolve, reject) =&gt; {
       if (done) { 
         const workDone = 'Here is the thing I built'
         resolve(workDone)
       } else {
         const why = 'Still working on something else'      
         reject(why)    
       }  
})
</code></pre>
<p>正如你所看到的，承诺会检查 <code>done</code> 全局常量，如果是真的，我们会返回一个已解决（resolved）的 Promise，否则就是拒绝（rejected ）的 resolved 。</p>
<p>使用 <code>resolve</code> 和 <code>reject</code> 我们可以返回一个值，在上面的例子中我们只是返回一个字符串，但也可以是一个对象。</p>
<h4 id="consumingpromise">消费（Consuming）Promise</h4>
<p>在上一节中，我们介绍了如何创建一个 Promise。</p>
<p>现在让我们来看看promise如何被<strong>消费</strong>或使用。</p>
<pre><code class="language-js">const isItDoneYet = new Promise()//...

const checkIfItsDone = () =&gt; { 
  isItDoneYet.then((ok) =&gt; {console.log(ok)})
             .catch((err) =&gt; {console.error(err)})
}
</code></pre>
<p>运行 <code>checkIfItsDone()</code> 将执行 <code>isItDoneYet()</code> 承诺，并等待它的解析，使用 <code>then</code> 回调，如果有错误，它将在 <code>catch</code> 回调中处理。</p>
<h4 id="promises">链式 Promises</h4>
<p>一个 Promise 可以返回给另一个 Promise，形成一个 Promise 链。</p>
<p><a href="https://flaviocopes.com/fetch-api">Fetch API</a> 给出了一个很好的 Promise 链的例子，它是 <code>XMLHttpRequest</code> API 上面的一层，我们可以用它来获取一个资源，并在获取资源的时候排队执行一连串的 Promise。</p>
<p>Fetch API是一个基于promise的机制，调用 <code>fetch()</code> 等同于使用 <code>new Promise()</code> 定义我们自己的 Promise。</p>
<h4 id="promises">Promises链的例子</h4>
<pre><code class="language-js">const status = (response) =&gt; {  
   if (response.status &gt;= 200 &amp;&amp; response.status &lt; 300) 
      { return Promise.resolve(response)  }  
   return Promise.reject(new Error(response.statusText))}
</code></pre>
<pre><code class="language-js">const json = (response) =&gt; response.json()
fetch('/todos.json')
.then(status)
.then(json)
.then((data) =&gt; {console.log('Request succeeded with JSON response', data)})
.catch((error) =&gt; { console.log('Request failed', error) })
</code></pre>
<p>在这个例子中，我们调用 <code>fetch()</code> 从当前目录中找到的 <code>todos.json</code> 文件中获得一个 TODO 项目的列表，并且我们创建了一个 Promises 链。</p>
<p>运行 <code>fetch()</code> 返回一个 <a href="https://fetch.spec.whatwg.org/#concept-response">response</a>，它有许多属性，在这些属性中我们引用了:</p>
<ul>
<li><code>status</code>, 一个代表HTTP状态代码的数字值</li>
<li><code>statusText</code>, 一个状态信息，如果请求成功，就是 <code>OK</code></li>
</ul>
<p><code>response</code> 也有一个 <code>json()</code> 方法，它返回一个 Promise，该 Promise 将对 body 的内容进行处理并转化为 JSON。</p>
<p>所以在这些前提下，会发生这样的事情：链中的第一个 Promise是我们定义的一个函数，叫做<code>status()</code>，它检查响应状态，如果不是一个成功的响应（在200和299之间），它拒绝这个 Promise（rejects the promise）。</p>
<p>这个操作将导致承诺链跳过所有列出的链式 Promise，直接跳到底部的 <code>catch()</code> 语句，记录 <code>Request failed</code> 文本和错误信息。</p>
<p>如果成功了，它会调用我们定义的 json() 函数。由于前一个 Promise 在成功时返回了 <code>response</code> 对象，我们得到它作为第二个 Promise 的输入。</p>
<p>在这种情况下，我们返回经过处理的 JSON 数据，所以第三个 Promise 直接接收 JSON:</p>
<pre><code class="language-js">.then((data) =&gt; { 
  console.log('Request succeeded with JSON response', data)
})
</code></pre>
<p>我们只需将其记录到控制台。</p>
<h4 id="">处理错误</h4>
<p>在上一节的例子中，我们有一个附加到 Promise 链上的 <code>catch</code>。</p>
<p>当 Promise 链中的任何东西失败并引发错误或拒绝 Promise 时，控制就会转到链下最近的 <code>catch()</code> 语句。</p>
<pre><code class="language-js">new Promise((resolve, reject) =&gt; {
        throw new Error('Error')}) 
       .catch((err) =&gt; { console.error(err) })
</code></pre>
<p>或者这样写</p>
<pre><code class="language-js">new Promise((resolve, reject) =&gt; { 
    reject('Error')})
    .catch((err) =&gt; { console.error(err) })
</code></pre>
<h4 id="cascadingerrors">Cascading（层叠）errors</h4>
<p>如果在 <code>catch()</code> 里面你引发了一个错误，你可以附加第二个 <code>catch()</code> 来处理它，以此类推。</p>
<pre><code class="language-js">new Promise((resolve, reject) =&gt; {throw new Error('Error')})
      .catch((err) =&gt; { throw new Error('Error') })  
      .catch((err) =&gt; { console.error(err) })
</code></pre>
<h3 id="orchestratingpromises">Orchestrating（协调） Promises</h3>
<h4 id="promiseall"><code>Promise.all()</code></h4>
<p>如果你需要同步不同的 Promise，<code>Promise.all()</code> 可以帮助你定义一个 Promise 列表，并在它们都被解决（resolved）后执行一些操作。</p>
<p>例如:</p>
<pre><code class="language-js">const f1 = fetch('/something.json')
const f2 = fetch('/something2.json')

Promise.all([f1, f2])
       .then((res) =&gt; {console.log('Array of results', res)})
       .catch((err) =&gt; {console.error(err)})
</code></pre>
<p><a href="https://flaviocopes.com/ecmascript/#destructuring-assignments">ES2015的析构赋值</a> 语法允许你也这样做:</p>
<pre><code class="language-js">Promise.all([f1, f2])
       .then(([res1, res2]) =&gt; {
          console.log('Results', res1, res2)
       })
</code></pre>
<p>当然，你并不局限于使用 <code>fetch</code>，<strong>任何 Promise 都可以使用</strong>。</p>
<h4 id="promiserace"><code>Promise.race()</code></h4>
<p><code>Promise.race()</code> 在你传递给它的第一个 Promise 得到解决时运行，它只运行一次所附的回调，并使用第一个 Promise 的解决结果。</p>
<p>例如:</p>
<pre><code class="language-js">const first = new Promise((resolve, reject) =&gt; {
  setTimeout(resolve, 500, 'first')
})
 
const second = new Promise((resolve, reject) =&gt; {
  setTimeout(resolve, 100, 'second')
})

Promise.race([first, second]).then((result) =&gt; {  
  console.log(result) 
})// second
</code></pre>
<h4 id="commonerroruncaughttypeerrorundefinedisnotapromise">Common error, Uncaught TypeError: undefined is not a promise</h4>
<p>如果你在控制台得到 <code>Uncaught TypeError: undefined is not a promise</code>,请确保你使用 <code>new Promise()</code>，而不是仅仅使用 <code>Promise()</code>。</p>
<h3 id="asyncandawait">Async and Await</h3>
<p>探索 JavaScript 中异步函数的现代方法。</p>
<p>JavaScript在很短的时间内从回调（callback）发展到了 Promise（ES2015），而从ES2017开始，异步 JavaScript 通过 async/await 语法变得更加简单。</p>
<p>异步函数是 Promises 和生成器（generators）的结合，基本上，它们是 Promises 的更高层次的抽象。让我重复一遍：<code>async/await</code> 是建立在 Promises 之上的。</p>
<h4 id="asyncawait">为什么会引入 async/await？</h4>
<p>它们减少了围绕 Promises 的模板, 以及链式 Promises 的 <code>don’t break the chain</code> 的限制。</p>
<p>当 Promises 在 ES2015 中被引入时，它们旨在解决异步代码的问题，而且它们确实做到了，但在 ES2015 和 ES2017 之间的两年时间里，显然 Promises 不可能成为最终的解决方案。</p>
<p>Promises 的引入是为了解决著名的回调地狱（callback hell）问题，但它们本身就引入了复杂性，而且是语法上的复杂性。</p>
<p>它们是很好的基元（primitives），围绕它们可以向开发者展示更好的语法，所以当时机成熟的时候，我们得到了<strong>同步函数（async functions）</strong>。</p>
<p>它们使代码看起来是同步的，但在幕后却是异步的和非阻塞的。</p>
<h4 id="">它是如何工作的</h4>
<p>一个 "async" 函数会返回一个 Promise，比如在这个例子中:</p>
<pre><code class="language-js">const doSomethingAsync = () =&gt; {
  return new Promise((resolve) =&gt; {
    setTimeout(() =&gt; resolve('I did something'), 3000) 
  })
}
</code></pre>
<p>当你想调用这个函数时，你要在前面加上 <code>await</code>，调用代码将停止<strong>直到承诺被解决或拒绝（promise is resolved or rejected）</strong>。有一点需要注意：客户端函数必须定义为 <code>async</code>。</p>
<p>这是一个例子:</p>
<pre><code class="language-js">const doSomething = async () =&gt; {    
  console.log(await doSomethingAsync())
}
</code></pre>
<h4 id="">一个简短的例子</h4>
<p>这是一个简单的 <code>async/await</code> 的例子，用于异步运行一个函数:</p>
<pre><code class="language-js">const doSomethingAsync = () =&gt; { 
  return new Promise((resolve) =&gt; { 
    setTimeout(() =&gt; resolve('I did something'), 3000) 
  })
}

const doSomething = async () =&gt; { 
  console.log(await doSomethingAsync())
}

console.log('Before')
doSomething()
console.log('After')
</code></pre>
<p>上述代码将向浏览器控制台打印以下内容:</p>
<pre><code class="language-plain">Before
After
I did something //after 3s
</code></pre>
<h4 id="promiseallthethings">Promise all the things</h4>
<p>在任何函数前加上 <code>async</code> 关键字意味着该函数将返回一个 promise。</p>
<p>即使它没有明确地这样做，也会在内部使其返回一个promise。</p>
<p>这就是为什么这段代码是有效的:</p>
<pre><code class="language-js">const aFunction = async () =&gt; { return 'test'}

aFunction().then(alert) // This will alert 'test'
</code></pre>
<p>跟下面的是相同的:</p>
<pre><code class="language-js">const aFunction = async () =&gt; { 
  return Promise.resolve('test')
}

aFunction().then(alert) // This will alert 'test'
</code></pre>
<h4 id="">代码阅读起来更简单</h4>
<p>正如你在上面的例子中看到的，我们的代码看起来非常简单。与使用普通承诺的代码相比，它有链式和回调函数。</p>
<p>而这只是一个非常简单的例子，主要的好处将出现在代码更复杂的时候。</p>
<p>例如，这里是你如何获得一个JSON资源并解析它，使用 Promises:</p>
<pre><code class="language-js">const getFirstUserData = () =&gt; {  
       return fetch('/users.json') // get users list    
      .then(response =&gt; response.json()) // parse JSON    
      .then(users =&gt; users[0]) // pick first user    
      .then(user =&gt; fetch(`/users/${user.name}`)) // get user data    
      .then(userResponse =&gt; response.json()) // parse JSON
}

getFirstUserData()
</code></pre>
<p>这里是用以下方式提供的相同功能,通过使用 <code>await/async</code>:</p>
<pre><code class="language-js">const getFirstUserData = async () =&gt; { 
        const response = await fetch('/users.json')  // get users list  
        const users = await response.json() // parse JSON  const user = users[0] // pick first user  
        const userResponse = await fetch(`/users/${user.name}`) // get user data 
        const userData = await user.json() // parse JSON  return userData
}

getFirstUserData()
</code></pre>
<h4 id="">串联多个的异步函数</h4>
<p><code>async</code> 函数可以很容易地串联起来，而且语法比普通的 Promises 更易读:</p>
<pre><code class="language-js">const promiseToDoSomething = () =&gt; { return 
  new Promise(resolve =&gt; { 
    setTimeout(() =&gt; resolve('I did something'), 10000) 
  }
)}

const watchOverSomeoneDoingSomething = async () =&gt; { 
  const something = await promiseToDoSomething()    
  return something + ' and I watched'
}

const watchOverSomeoneWatchingSomeoneDoingSomething = async () =&gt; { 
  const something = await watchOverSomeoneDoingSomething()  
  return something + ' and I watched as well'
}

watchOverSomeoneWatchingSomeoneDoingSomething().then((res) =&gt; { 
     console.log(res)
})
</code></pre>
<p>将会打印:</p>
<pre><code class="language-shell">I did something and I watched and I watched as well
</code></pre>
<h4 id="">更容易调试</h4>
<p>调试 Promises 是很难的，因为调试器不会在异步代码上断点。</p>
<p><code>async/await</code> 使之变得非常容易，因为对编译器来说，它就像同步代码。</p>
<h3 id="nodejseventemitter">Node.js 事件发送器（Event Emitter）</h3>
<p>你可以在 Node.js 中使用自定义事件。</p>
<p>如果你在浏览器中使用 JavaScript，你就知道用户的大部分交互是通过事件处理的：鼠标点击、键盘按键、对鼠标移动的反应等等。</p>
<p>在后端，Node.js为我们提供了使用 <a href="https://nodejs.org/api/events.html">events模块</a> 建立类似系统的选择。</p>
<p>这个模块特别提供了 <code>EventEmitter</code> 类，我们将用它来处理我们的事件。</p>
<p>你可以用以下方式初始化它:</p>
<pre><code class="language-js">const eventEmitter = require('events').EventEmitter()
</code></pre>
<p>这个对象暴露了 <code>on</code> 和 <code>emit</code> 方法，以及其他许多方法。</p>
<ul>
<li><code>emit</code> 用于触发一个事件。</li>
<li><code>on</code> 用于添加一个回调函数，当事件被触发时将被执行。</li>
</ul>
<p>例如，让我们创建一个<code>start</code>事件，作为提供一个样本的问题，我们对该事件的反应只是记录到控制台:</p>
<pre><code class="language-js">eventEmitter.on('start', () =&gt; {  
  console.log('started')
})
</code></pre>
<p>当我们运行:</p>
<pre><code class="language-js">eventEmitter.emit('start')
</code></pre>
<p>事件处理函数被触发，我们得到控制台日志。</p>
<p>你可以通过将参数作为附加参数传递给 <code>emit()</code> 来给事件处理程序传递参数:</p>
<pre><code class="language-js">eventEmitter.on('start', (number) =&gt; {  
  console.log(`started ${number}`)
})
eventEmitter.emit('start', 23)
</code></pre>
<p>多个参数:</p>
<pre><code class="language-js">eventEmitter.on('start', (start, end) =&gt; {  
  console.log(`started from ${start} to ${end}`)
})
eventEmitter.emit('start', 1, 100)
</code></pre>
<p>EventEmitter 对象还公开了其他几个与事件互动的方法，比如:</p>
<ul>
<li><code>once()</code>:  添加一个一次性的监听器</li>
<li><code>removeListener()</code> / <code>off()</code>: 从一个事件中删除一个事件监听器</li>
<li><code>removeAllListeners()</code>: 删除一个事件的所有监听器。</li>
</ul>
<h3 id="http">HTTP请求如何工作</h3>
<p>当你在浏览器中输入一个 URL 时，从开始到结束会发生什么？</p>
<p>本节介绍浏览器如何使用 HTTP/1.1 协议执行页面请求。</p>
<p>如果你曾经做过一次面试，你可能会被问到。"当你在谷歌搜索框中输入东西并按下回车键时会发生什么？"。</p>
<p>这是你被问到的最多的问题之一。人们只是想看看你是否能解释一些相当基本的概念，以及你是否了解互联网的实际运作。</p>
<p>在本节中，我将分析当你在浏览器的地址栏中输入一个 URL 并按下回车键时会发生什么。</p>
<p>在本节中，我将分析当你在浏览器的地址栏中输入一个 URL 并按下回车键时会发生什么。</p>
<p>这是很少变化的技术，它为人类有史以来最复杂、最广泛的生态系统之一提供动力。</p>
<h3 id="http">HTTP协议</h3>
<p>我只分析 URL 请求。</p>
<p>现代浏览器有能力知道你在地址栏中写的东西是一个实际的URL还是一个搜索词，如果它不是一个有效的URL，它们将使用默认的搜索引擎。</p>
<p>我假设你输入了一个实际的 URL。</p>
<p>当你输入URL并按下回车键时，浏览器首先建立完整的URL。</p>
<h4 id="macoslinux">与 MacOS/Linux 有关的事情</h4>
<p>仅供参考。Windows可能会对一些事情的处理方式略有不同。</p>
<h4 id="dns">DNS 查询阶段</h4>
<p>浏览器开始进行 DNS 查询以获得服务器的 IP 地址。</p>
<p>域名对我们人类来说是方便记忆，但互联网的组织方式是，计算机可以通过其 IP 地址查询服务器的确切位置，这是一组数字，如 <code>222.324.3.1</code>（IPv4）。</p>
<p>首先，它检查 DNS 的本地缓存，看这个域名最近是否已经被解析。</p>
<p><strong><em>Chrome 有一个方便的DNS缓存可视化工具，你可以在这个网址上看到：chrome://net-internals/#dns（复制并粘贴到Chrome浏览器地址栏）</em></strong></p>
<p>如果没有找到，浏览器就使用DNS解析器，使用 <code>gethostbyname</code> POSIX 系统调用来检索主机信息。</p>
<h4 id="gethostbyname">gethostbyname</h4>
<p><code>gethostbyname</code>：浏览器首先查找本地主机文件，在 macOS 或 Linux 上，该文件位于 <code>/etc/hosts</code>，以查看系统是否在本地提供了该信息。</p>
<p>如果这没有提供任何关于域名的信息，系统会向DNS服务器发出请求。</p>
<p>DNS服务器的地址存储在系统偏好中。</p>
<p>这些是2个流行的 DNS 服务器:</p>
<ul>
<li><code>8.8.8.8</code>: 谷歌公共 DNS 服务器</li>
<li><code>1.1.1.1</code>: CloudFlare DNS 服务器</li>
</ul>
<p>大多数人使用他们的互联网供应商提供的 DNS 服务器。</p>
<p>浏览器使用 UDP 协议执行 DNS 请求。</p>
<p>TCP 和 UDP 是计算机网络的两个基础性协议。它们处于相同的概念层面，但 TCP 是面向连接的，而 UDP 是一个无连接的协议，更轻巧，用于发送消息，开销很小。</p>
<p>如何进行 UDP 请求不在本手册的范围内。</p>
<p>DNS 服务器的缓存中可能有该域名的 IP。如果没有，它将询问<strong>根域名服务器</strong>。那是一个驱动整个互联网的系统（由 13 个实际的服务器组成，分布在地球上）。</p>
<p>DNS 服务器并<strong>不</strong>知道地球上每一个域名的地址。</p>
<p>它所知道的是<strong>顶级 DNS 解析器</strong>的位置。</p>
<p>顶级域名是域名的扩展名：<code>.com</code>、<code>.it</code>、<code>.pizza</code> 等等。</p>
<p>一旦根 DNS 服务器收到请求，它就将请求转发到该顶级域名（TLD）DNS 服务器。</p>
<p>假设你正在寻找 <code>flaviocopes.com</code>。根域名的 DNS 服务器返回 .com TLD 服务器的 IP。</p>
<p>现在，我们的 DNS 解析器将缓存该 TLD 服务器的 IP，所以它不必再向根 DNS 服务器询问它。</p>
<p>TLD DNS 服务器将拥有我们正在寻找的域名的权威性域名服务器的 IP 地址。</p>
<p>怎么会这样？当你购买一个域名时，域名注册商会向域名服务器发送适当的 TDL。当你更新名称服务器时（例如，当你改变主机提供商时），这些信息将由你的域名注册商自动更新。</p>
<p>这些是主机提供商的 DNS 服务器。它们通常不止一个，以作为备份。</p>
<p>例如:</p>
<ul>
<li><code>ns1.dreamhost.com</code></li>
<li><code>ns2.dreamhost.com</code></li>
<li><code>ns3.dreamhost.com</code></li>
</ul>
<p>DNS 解析器从第一个开始，试图询问你要找的域名（也包括子域名）的 IP。</p>
<p>这就是IP地址的最终真实来源。</p>
<p>现在我们有了 IP 地址，我们可以继续我们的旅程了。</p>
<h4 id="tcp">TCP 请求握手</h4>
<p>有了服务器的 IP 地址，现在浏览器可以启动一个 TCP 连接。</p>
<p>TCP 连接在完全初始化之前需要进行一些握手，然后就可以开始发送数据。</p>
<p>一旦连接建立，我们可以发送请求</p>
<h4 id="">发送请求</h4>
<p>请求是一个纯文本文件，以通信协议确定的精确方式结构化。</p>
<p>它由3个部分组成:</p>
<ul>
<li>请求行</li>
<li>请求头</li>
<li>请求体</li>
</ul>
<h4 id="">请求行</h4>
<p>请求行设置了，在一个单行上:</p>
<ul>
<li>HTTP 方法</li>
<li>资源位置</li>
<li>协议版本</li>
</ul>
<p>例如:</p>
<pre><code class="language-plain">GET / HTTP/1.1
</code></pre>
<h4 id="">请求头</h4>
<p>请求头是一组 "字段：值 "对，用于设置某些值。</p>
<p>有两个强制性的字段，一个是 <code>host</code>，另一个是 <code>Connection</code>，而所有其他字段是可选:</p>
<pre><code class="language-shell">Host: flaviocopes.comConnection: close
</code></pre>
<p><code>Host</code> 表示我们想要的目标域名，而 <code>Connection</code> 总是被设置为 <code>close</code>，除非连接必须保持开放。</p>
<p>一些最常用的请求头（header）字段是:</p>
<ul>
<li><code>Origin</code></li>
<li><code>Accept</code></li>
<li><code>Accept-Encoding</code></li>
<li><code>Cookie</code></li>
<li><code>Cache-Control</code></li>
<li><code>Dnt</code></li>
</ul>
<p>但还有更多。</p>
<p>请求头（head）部分由一个空行结束。</p>
<h4 id="">请求体</h4>
<p>请求体是可选的，在 GET 请求中不使用，但在 POST 请求中非常使用，有时也用于其他动词，它可以包含 JSON 格式的数据。</p>
<p>由于我们现在分析的是一个 GET 请求，所以请求体是空白的，我们不做更多研究。</p>
<h4 id="theresponse">The response（响应）</h4>
<p>一旦发送请求，服务器就会对其进行处理并发回一个响应。</p>
<p>响应以状态代码和状态信息开始。如果请求成功并返回 200:</p>
<pre><code class="language-plain">200 OK
</code></pre>
<p>该请求可能会返回一个不同的状态代码和信息，比如这些信息之一:</p>
<pre><code class="language-plain">404 Not Found
403 Forbidden
301 Moved Permanently
500 Internal Server Error
304 Not Modified
401 Unauthorized
</code></pre>
<p>然后，响应包含一个 HTTP 头的列表和响应体（因为我们是在浏览器中发出请求，所以它将是 HTML）。</p>
<h4 id="parsehtml">解析（Parse）HTML</h4>
<p>浏览器现在已经收到了 HTML，并开始解析它，它将重复我们对页面所需的所有资源所做的完全相同的过程:</p>
<ul>
<li>CSS 文件</li>
<li>图像</li>
<li>图标</li>
<li>JavaScript 文件</li>
<li>……</li>
</ul>
<p>浏览器是如何渲染页面的，这不在我们的讨论范围之内，但重要的是要明白，我所描述的过程不仅仅是针对 HTML 页面，而是针对任何通过 HTTP 提供的项目。</p>
<h3 id="nodejshttp">用 Node.js 建立一个 HTTP 服务器</h3>
<p>这里是我们在介绍使用 Node.js HTTP 网络服务器实现 Hello World 应用程序:</p>
<pre><code class="language-js">const http = require('http')
const port = 3000
const server = http.createServer((req, res) =&gt; {  
  res.statusCode = 200  
  res.setHeader('Content-Type', 'text/plain')
  res.end('Hello World\n')
})

server.listen(port, () =&gt; {  
  console.log(`Server running at http://${hostname}:${port}/`)
})s
</code></pre>
<p>让我们简单地分析一下。我们包括 <a href="https://nodejs.org/api/http.html">http模块</a>。</p>
<p>我们使用该模块来创建一个 HTTP 服务器。</p>
<p>该服务器被设置为监听指定的端口，<code>3000</code>。当服务器准备好时，<code>listen</code> 回调函数被调用。</p>
<p>我们传递的回调函数是在每个请求到来时都要执行的函数。每当收到一个新的请求，<a href="https://nodejs.org/api/http.html#http_event_request">request event</a> 被调用，提供两个对象：一个请求（一个 <a href="https://nodejs.org/api/http.html#http_class_http_incomingmessage">http.IncomingMessage</a> 对象）和一个响应（一个 <a href="https://nodejs.org/api/http.html#http_class_http_serverresponse">http.ServerResponse</a> 对象）。</p>
<p><code>request</code> 提供了请求的细节。通过它，我们可以访问请求头和请求数据。</p>
<p><code>response</code> 用于填充我们要返回给客户端的数据。</p>
<p><code>response</code> 用于填充我们要返回给客户端的数据:</p>
<pre><code class="language-js">res.statusCode = 200
</code></pre>
<p>我们将 <code>statusCode</code> 属性设置为 <code>200</code>，以表示响应成功。</p>
<p>我们还设置了 <code>Content-Type</code> 头:</p>
<pre><code class="language-js">res.setHeader('Content-Type', 'text/plain')
</code></pre>
<p>然后我们结束关闭响应，将内容作为参数添加到 <code>end()</code>:</p>
<pre><code class="language-js">res.end('Hello World\n')
</code></pre>
<h3 id="nodejshttp">用 Node.js 做 HTTP 请求</h3>
<p>如何用 Node.js 执行 HTTP 请求，使用 GET、POST、PUT 和 DELETE。</p>
<p>我使用 HTTP 一词，但 HTTPS 才是应该到处使用的（译者注：HTTPS 更安全），因此这些例子使用 HTTPS 而不是 HTTP。</p>
<h4 id="performagetrequest">Perform a GET Request</h4>
<pre><code class="language-js">const https = require('https')
const options = { 
  hostname: 'flaviocopes.com', 
  port: 443, path: '/todos', 
  method: 'GET' 
}

const req = https.request(options, (res) =&gt; {
    console.log(`statusCode: ${res.statusCode}`)
    res.on('data', (d) =&gt; { process.stdout.write(d) })
})

req.on('error', (error) =&gt; { console.error(error) })

req.end()
</code></pre>
<h4 id="performapostrequest">Perform a POST Request</h4>
<pre><code class="language-js">const https = require('https')

const data = JSON.stringify({  todo: 'Buy the milk'})

const options = {  
  hostname: 'flaviocopes.com',
  port: 443,  path: '/todos',  
  method: 'POST',  
  headers: {'Content-Type':'application/json', 'Content-Length': data.length  }
}

const req = https.request(options, (res) =&gt; { 
  console.log(`statusCode: ${res.statusCode}`)
})

res.on('data', (d) =&gt; {process.stdout.write(d)})

req.on('error', (error) =&gt; {console.error(error)})

req.write(data)
req.end()
</code></pre>
<h4 id="putdelete">PUT 和 DELETE</h4>
<p>PUT 和 DELETE 请求使用相同的 POST 请求格式，只是改变 <code>options.method</code> 值。</p>
<h3 id="nodejsaxioshttp">在 Node.js 中使用 Axios 的 HTTP 请求</h3>
<p>Axios 是一个非常流行的 JavaScript 库，你可以用来执行 HTTP 请求，它可以在浏览器和 Node.js 平台上工作。</p>
<p>它支持所有的现代浏览器，包括对 IE8 和更高版本的支持。</p>
<p>它是 promise-based, 这让我们可以非常容易编写异步/等待代码来执行 <a href="https://flaviocopes.com/xhr/">XHR</a> 请求。</p>
<p>与原生的 Fetch API 相比，使用 Axios 有很多优势:</p>
<ul>
<li>支持旧的浏览器（Fetch 需要一个 polyfill，即降级方案）</li>
<li>可以中止请求</li>
<li>可以设置响应超时</li>
<li>有内置的 CSRF 保护</li>
<li>支持上传进度</li>
<li>执行自动 JSON 数据转换</li>
<li>可以在 Node.js 中使用</li>
</ul>
<h4 id="">安装</h4>
<p>Axios 可以用 npm 安装:</p>
<pre><code class="language-shell">npm install axios
</code></pre>
<p>或者 yarn:</p>
<pre><code class="language-shell">yarn add axios
</code></pre>
<p>或简单地使用 unpkg.com，在你的页面引用:</p>
<pre><code class="language-html">&lt;script src="https://unpkg.com/axios/dist/axios.min.js"&gt;&lt;/script&gt;
</code></pre>
<h4 id="axiosapi">Axios API</h4>
<p>你可以从 <code>axios</code> 对象中开始一个 HTTP 请求:</p>
<pre><code class="language-js">axios({  
  url: 'https://dog.ceo/api/breeds/list/all',  
  method: 'get',  
  data: {foo:'bar'}
})
</code></pre>
<p>但为了方便起见，你一般会使用:</p>
<ul>
<li><code>axios.get()</code></li>
<li><code>axios.post()</code></li>
</ul>
<p>(就像在 jQuery 中你会使用 <code>$.get()</code> 和 <code>$.post()</code> 而不是 <code>$.ajax()</code>)</p>
<p>Axios 为所有的 HTTP 动词提供了方法，这些动词不太流行，但仍在使用:</p>
<ul>
<li><code>axios.delete()</code></li>
<li><code>axios.put()</code></li>
<li><code>axios.patch()</code></li>
<li><code>axios.options()</code></li>
</ul>
<p>和一个方法来获取一个请求的 HTTP 头信息，并丢弃正文（discarding the body）:</p>
<ul>
<li><code>axios.head()</code></li>
</ul>
<h4 id="get">GET 请求</h4>
<p>使用 Axios 的一个方便方法是使用现代（ES2017）的 <code>async/await</code> 语法。</p>
<p>这个 Node.js 例子查询了 <a href="https://dog.ceo/">Dog API</a>，使用 <code>axios.get()</code> 检索了所有狗的品种列表，并对它们进行了统计:</p>
<pre><code class="language-js">const axios = require('axios')

const getBreeds = async () =&gt; { 
  try {  
       return await axios.get('https://dog.ceo/api/breeds/list/all')  
  } 
  catch (error) {    
    console.error(error)  
  }
}

const countBreeds = async () =&gt; {  
  const breeds = await getBreeds()
  if (breeds.data.message) {
    console.log(`Got ${Object.entries(breeds.data.message).length} breeds`) 
  }
}

countBreeds()
</code></pre>
<p>如果你不想使用 <code>async/await</code>，你可以使用 <a href="https://flaviocopes.com/javascript-promises/">Promises</a> 语法:</p>
<pre><code class="language-js">const axios = require('axios')

const getBreeds = () =&gt; {  
  try { 
    return axios.get('https://dog.ceo/api/breeds/list/all')
  } 
  catch (error) {
    console.error(error) 
   }
}

const countBreeds = async () =&gt; {  
  const breeds = getBreeds().then(response =&gt; { 
    if (response.data.message) {       
      console.log(`Got ${Object.entries(response.data.message).length} breeds`)
    }
  }).catch(error =&gt; {
    console.log(error)})
}

countBreeds()
</code></pre>
<h4 id="get">在GET请求中添加参数</h4>
<p>一个 GET 响应可以在 URL 中包含参数，像这样 <a href="https://site.com/?foo=bar.">https://site.com/?foo=bar</a>。</p>
<p>使用 Axios，你可以通过简单地使用该 URL 来执行:</p>
<pre><code class="language-js">axios.get('https://site.com/?foo=bar')
</code></pre>
<p>或者你可以在选项中使用一个 <code>params</code> 属性:</p>
<pre><code class="language-js">axios.get('https://site.com/', {  params: {    foo: 'bar'  }})
</code></pre>
<h4 id="post">POST 请求</h4>
<p>执行 POST 请求就像执行 GET 请求一样，但你使用的不是 <code>axios.get</code>，而是 <code>axios.post</code>:</p>
<pre><code class="language-js">axios.post('https://site.com/')
</code></pre>
<p>一个包含 POST 参数的对象是第二个参数:</p>
<pre><code class="language-js">axios.post('https://site.com/', {  foo: 'bar'})
</code></pre>
<h3 id="nodejswebsockets">在 Node.js 中使用 WebSockets</h3>
<p>WebSockets 是网络应用中 HTTP 通信的替代方案。</p>
<p>它们在客户端和服务器之间提供了一个长期的、双向的通信通道。</p>
<p>一旦建立，通道就会保持开放，提供一个非常快速的连接，延迟和开销都很低。</p>
<h3 id="websockets">浏览器对 WebSockets 的支持</h3>
<p>所有现代浏览器都支持 WebSockets。</p>
<h3 id="websocketshttp">WebSockets与HTTP有什么不同</h3>
<p>HTTP 是一个非常不同的协议，并且有不同的通信方式。</p>
<p>HTTP 是一个请求/响应协议：服务器在客户端请求时返回一些数据。</p>
<p>WebSockets:</p>
<ul>
<li>服务器可以向客户端发送一个消息，而不需要客户端明确请求什么</li>
<li>客户端和服务器可以同时<strong>彼此对话</strong></li>
<li>发送消息所需的数据开销非常小。这意味着<strong>低延迟的通信</strong></li>
</ul>
<p>WebSockets 非常适用于实时和长期的通信。</p>
<p>HTTP 非常适用于偶尔的数据交换和由客户端发起的互动。</p>
<p>HTTP 的实现要简单得多，而 WebSockets 则需要更多的开销。</p>
<h3 id="websockets">安全的 WebSockets</h3>
<p>始终使用安全的、加密的 WebSockets 协议，<code>wss://</code>。</p>
<p><code>ws://</code> 指的是不安全的 WebSockets 版本（WebSockets 的<code>http://</code>），由于明显的原因，应该避免使用。</p>
<h3 id="websockets">创建一个新的 WebSockets 连接</h3>
<pre><code class="language-js">const url = 'wss://myserver.com/something'
const connection = new WebSocket(url)
</code></pre>
<p><code>connection</code> 是一个 <a href="https://developer.mozilla.org/en-US/docs/Web/API/WebSocket">WebSocket</a> 对象。</p>
<p>当连接被成功建立时，"open" 事件被触发。</p>
<p>通过给 <code>connection</code> 对象的 <code>onopen</code> 属性分配一个回调函数来监听它:</p>
<pre><code class="language-js">connection.onopen = () =&gt; {} //...
</code></pre>
<p>如果有任何错误，<code>onerror</code> 函数回调被触发:</p>
<pre><code class="language-js">connection.onerror = error =&gt; { 
  console.log(`WebSocket error: ${error}`)
}
</code></pre>
<h3 id="websockets">使用 WebSockets 向服务器发送数据</h3>
<p>一旦连接被打开，你就可以向服务器发送数据。</p>
<p>你可以在 <code>onopen</code> 回调函数中方便地这样做:</p>
<pre><code class="language-js">connection.onopen = () =&gt; {  connection.send('hey')}
</code></pre>
<h3 id="websockets">使用 WebSockets 从服务器接收数据</h3>
<p>在 <code>onmessage</code> 上使用回调函数进行监听，当收到 <code>message</code> 事件时被调用:</p>
<pre><code class="language-js">connection.onmessage = e =&gt; {  console.log(e.data)}
</code></pre>
<h3 id="nodejswebsockets">在 Node.js 中实现一个 WebSockets 服务器</h3>
<p><a href="https://github.com/websockets/ws">ws</a> 是一个用于 Node.js 的流行的 WebSockets 库。</p>
<p>我们将用它来建立一个 WebSockets 服务器。它也可以用来实现一个客户端，并使用 WebSockets 在两个后端服务之间通信。</p>
<p>使用以下方法轻松地安装它:</p>
<pre><code class="language-js">yarn init
yarn add ws
</code></pre>
<p>你需要写的代码非常少:</p>
<pre><code class="language-js">const WebSocket = require('ws')
const wss = new WebSocket.Server({ port: 8080 })
wss.on('connection', ws =&gt; {  ws.on('message', message =&gt; { 
  console.log(`Received message =&gt; ${message}`)  
})  
ws.send('ho!')})
</code></pre>
<p>这段代码在 8080 端口（WebSockets 的默认端口）创建了一个新的服务器，并在建立连接时添加了一个回调函数，向客户端发送 <code>ho!</code>，并记录它收到的消息。</p>
<h3 id="glitch">请看 Glitch 上的一个运行中的例子</h3>
<p><a href="https://glitch.com/edit/#!/flavio-websockets-server-example">Here</a> 是一个 WebSockets 服务器的活例子。</p>
<p><a href="https://glitch.com/edit/#!/flavio-websockets-client-example">Here</a> 是一个与服务器互动的 WebSockets 客户端。</p>
<h3 id="nodejs">在 Node.js 中使用文件描述符</h3>
<p>在你能够与文件系统中的文件互动之前，你必须获得一个文件描述符。</p>
<p>文件描述符是使用 <code>fs</code> 模块提供的 <code>open()</code> 方法打开文件时返回的东西:</p>
<pre><code class="language-js">const fs = require('fs')
fs.open('/Users/flavio/test.txt', 'r', (err, fd) =&gt; { })//fd is our file descriptor
</code></pre>
<p>注意我们用 <code>r</code> 作为 <code>fs.open()</code> 调用的第二个参数。</p>
<p>这个标志意味着我们打开文件进行阅读。</p>
<p>你经常使用的其他标志是</p>
<ul>
<li><code>r+</code> 打开文件进行读写</li>
<li><code>w+</code> 打开文件进行读写，将流定位在文件的开头。如果文件不存在，将被创建。</li>
<li><code>a</code> 打开文件进行写入，将数据流定位在文件的末端。如果不存在，文件将被创建。</li>
<li><code>a+</code> `打开文件进行读写，将数据流定位在文件的末端。如果不存在，文件被创建。</li>
</ul>
<p>你也可以通过使用 <code>fs.openSync</code> 方法来打开文件，它不是在回调中提供文件描述符对象，而是返回它:</p>
<pre><code class="language-js">const fs = require('fs')
try {  
  const fd = fs.openSync('/Users/flavio/test.txt', 'r')
} catch (err) {  
    console.error(err)
}
</code></pre>
<p>一旦你得到了文件描述符，无论你选择什么方式，你都可以执行所有需要它的操作，比如调用 <code>fs.open()</code> 和许多其他与文件系统互动的操作。</p>
<h3 id="nodejsstats">Node.js 文件统计（stats）</h3>
<p>每个文件都带有一组细节，我们可以使用 Node.js 检查。</p>
<p>特别是，使用 <code>fs</code> 模块提供的 <code>stat()</code> 方法。</p>
<p>你通过一个文件路径来调用它，一旦 Node.js 得到了文件的细节，它将调用你传递的带有 2 个参数的回调函数：一个错误信息和文件统计:</p>
<pre><code class="language-js">const fs = require('fs')fs.stat('/Users/flavio/test.txt', (err, stats) =&gt; { 
  if (err) { 
    console.error(err) 
    return  
  } 
}) //we have access to the file stats in `stats`
</code></pre>
<p>Node.js 还提供了一个同步方法，它可以阻塞线程，直到文件统计准备就绪:</p>
<pre><code class="language-js">const fs = require('fs')
try { 
  const stats = fs.stat('/Users/flavio/test.txt')
} 
catch (err) { 
   console.error(err)
}
</code></pre>
<p>文件信息被包含在 stats 变量中。我们可以用 stats 提取什么样的信息？</p>
<p>很多，包括:</p>
<ul>
<li>如果文件是一个目录或一个文件，使用 <code>stats.isFile()</code> 和 <code>stats.isDirectory()</code></li>
<li>如果文件是一个符号链接，使用 <code>stats.isSymbolicLink()</code></li>
<li>使用 <code>stats.size</code> 来计算文件的字节数</li>
</ul>
<p>还有其他高级方法，但你在日常编程中会用到的大部分方法是这样的:</p>
<pre><code class="language-js">const fs = require('fs')fs.stat('/Users/flavio/test.txt', (err, stats) =&gt; {
   if (err) {
     console.error(err)   
     return  
   }
   stats.isFile() //true  
   stats.isDirectory() //false  
   stats.isSymbolicLink() //false  
   stats.size //1024000 //= 1MB})
}
</code></pre>
<h3 id="nodejs">Node.js 文件路径</h3>
<p>系统中的每个文件都有一个路径。</p>
<p>在 Linux 和 MacOS 上，一个路径可能看起来像:</p>
<p><code>/users/flavio/file.txt</code></p>
<p>而 Windows 电脑则不同，它的结构如:</p>
<p><code>C:\users\flavio\file.txt</code></p>
<p>在你的应用程序中使用路径时，你需要注意，因为必须考虑到这种差异。</p>
<p>你在你的文件中包括这个模块，使用:</p>
<pre><code class="language-js">const path = require('path')
</code></pre>
<p>你可以开始使用它的方法。</p>
<h4 id="">从路径中获取信息</h4>
<p>给定一个路径，你可以用这些方法提取其中的信息:</p>
<ul>
<li><code>dirname</code>: 获取文件的父文件夹</li>
<li><code>basename</code>: 获取文件名部分</li>
<li><code>extname</code>: 获得文件的扩展名</li>
</ul>
<p>例如:</p>
<pre><code class="language-js">const notes = '/users/flavio/notes.txt'

path.dirname(notes) // /users/flavio
path.basename(notes) // notes.txt
path.extname(notes) // .txt
</code></pre>
<p>你可以通过给 <code>basename</code> 指定第二个参数来获得不带扩展名的文件名:</p>
<pre><code class="language-js">path.basename(notes, path.extname(notes)) //notes
</code></pre>
<h4 id="">使用路径工作</h4>
<p>你可以通过使用 <code>path.join()</code> 来连接一个路径的两个或多个部分:</p>
<pre><code class="language-js">const name = 'flavio'path.join('/', 'users', name, 'notes.txt') //'/users/flavio/notes.txt'
</code></pre>
<p>你可以使用 <code>path.resolve()</code> 从相对路径获得绝对路径计算:</p>
<pre><code class="language-js">path.resolve('flavio.txt') //'/Users/flavio/flavio.txt' if run from my home folder
</code></pre>
<p>在这种情况下，Node.js 将简单地把 <code>/flavio.txt</code> 追加到当前工作目录。如果你指定了第二个参数文件夹，<code>resolve</code> 将使用第一个作为第二个的基础:</p>
<pre><code class="language-js">path.resolve('tmp', 'flavio.txt')// '/Users/flavio/tmp/flavio.txt' if run from my home folder
</code></pre>
<p>如果第一个参数以斜线开头，这意味着它是一个绝对路径:</p>
<pre><code class="language-js">path.resolve('/etc', 'flavio.txt')// '/etc/flavio.txt'
</code></pre>
<p><code>path.normalize()</code> 是另一个有用的函数，它将尝试计算实际的路径，当它包含像<code>.</code>或<code>.</code>这样的相对指定符，或双斜线时:</p>
<pre><code class="language-js">path.normalize('/users/flavio/..//test.txt') // /users/test.txt
</code></pre>
<p>但是 <code>resolve</code> 和 <code>normalize</code> <strong>不会</strong> 检查路径是否存在。它们只是根据得到的信息计算出一个路径。</p>
<h3 id="nodejs">用 Node.js 读取文件</h3>
<p>在 Node.js 中读取文件的最简单方法是使用 <code>fs.readFile()</code> 方法，将文件路径和一个回调函数传递给它，该函数将被调用，并带有文件数据（和错误）:</p>
<pre><code class="language-js">const fs = require('fs')
fs.readFile('/Users/flavio/test.txt', (err, data) =&gt; {  
  if (err) {    
    console.error(err)    
    return  
  }  
  console.log(data)
})
</code></pre>
<p>或者，你可以使用同步版本 <code>fs.readFileSync()</code>:</p>
<pre><code class="language-js">const fs = require('fs')
try {  
  const data = fs.readFileSync('/Users/flavio/test.txt') 
  console.log(data)
} catch (err) {  
  console.error(err)
}
</code></pre>
<p>默认编码是 "utf8"，但你可以使用第二个参数指定一个自定义编码。</p>
<p><code>fs.readFile()</code> 和 <code>fs.readFileSync()</code> 都是在返回数据之前读取内存中的全部文件内容。</p>
<p>这意味着大文件会对你的内存消耗和程序的执行速度产生重大影响。</p>
<p>在这种情况下，一个更好的选择是使用流（streams）读取文件内容。</p>
<h3 id="nodejs">用 Node.js 写文件</h3>
<p>在 Node.js 中写入文件的最简单方法是使用 <code>fs.writeFile()</code> API。</p>
<p>例如:</p>
<pre><code class="language-js">const fs = require('fs')
const content = 'Some content!'
fs.writeFile('/Users/flavio/test.txt', content, (err) =&gt; {  
  if (err) {
    console.error(err)    
    return  
  }  
})//file written successfully
</code></pre>
<p>或者，你可以使用同步版本 <code>fs.writeFileSync()</code>:</p>
<pre><code class="language-js">const fs = require('fs')
const content = 'Some content!'
try {  
  const data = fs.writeFileSync('/Users/flavio/test.txt', content) 
  //file written successfully
} 
catch (err) {  
  console.error(err)
}
</code></pre>
<p>默认情况下，如果文件已经存在，该 API 将 <strong>替换</strong> 该文件的内容。</p>
<p>你可以通过指定一个标志（flag）来修改默认值:</p>
<pre><code class="language-js">fs.writeFile('/Users/flavio/test.txt', content, { flag: 'a+' }, (err) =&gt; {})
</code></pre>
<p>你可能会用到的标志（flags）:</p>
<ul>
<li><code>r+</code> 打开文件进行读写。</li>
<li><code>w+</code> 打开文件进行读写，将流定位在文件的开头。如果文件不存在，将被创建。</li>
<li><code>a</code>  打开文件进行写入，将数据流定位在文件的末端。如果不存在，文件将被创建。</li>
<li><code>a+</code> 打开文件进行读写，将数据流定位在文件的末端。如果不存在，该文件将被创建</li>
</ul>
<p>你可以找到更多关于 <a href="https://nodejs.org/api/fs.html#fs_file_system_flags">flags</a> 的信息</p>
<h4 id="">追加到一个文件中</h4>
<p>一个方便的方法是 <code>fs.appendFile()</code> (和它的 <code>fs.appendFileSync()</code> 对应同步的方法)，将内容附加到文件的末尾:</p>
<pre><code class="language-js">const content = 'Some content!'
fs.appendFile('file.log', content, (err) =&gt; {  
  if (err) { 
    console.error(err)    
    return  
  }  
})//done!
</code></pre>
<h4 id="streams">使用流（streams）</h4>
<p>所有这些方法在把控制权返回给你的程序之前都会把全部内容写入文件（在异步版本中，这意味着执行回调）。</p>
<p>在这种情况下，一个更好的选择是使用流（streams）来写文件内容。</p>
<h3 id="nodejs">在 Node.js 中使用文件夹</h3>
<p>Node.js <code>fs</code> 核心模块提供了许多方便的方法，你可以用来处理文件夹。</p>
<h4 id="">检查一个文件夹是否存在</h4>
<p>使用 <code>fs.access()</code> 来检查文件夹是否存在，并且 Node.js 可以用其权限来访问它。</p>
<h4 id="">创建文件夹</h4>
<p>使用 <code>fs.mkdir()</code> 或 <code>fs.mkdirSync()</code> 来创建一个新文件夹:</p>
<pre><code class="language-js">const fs = require('fs')
const folderName = '/Users/flavio/test'
try {  
   if (!fs.existsSync(dir))
     {fs.mkdirSync(dir)}
} catch (err){ 
   console.error(err)
}
</code></pre>
<h4 id="">读取一个目录的内容</h4>
<p>使用 <code>fs.readdir()</code> 或 <code>fs.readdirSync</code> 来读取一个目录的内容。</p>
<p>这段代码读取一个文件夹的内容，包括文件和子文件夹，并返回其相对路径:</p>
<pre><code class="language-js">const fs = require('fs')
const path = require('path')
const folderPath = '/Users/flavio'
fs.readdirSync(folderPath)
</code></pre>
<p>你可以得到完整的路径:</p>
<pre><code class="language-js">fs.readdirSync(folderPath)
  .map(fileName =&gt; {
     return path.join(folderPath, fileName)
})
</code></pre>
<p>你还可以过滤结果，只返回文件，并排除文件夹:</p>
<pre><code class="language-js">const isFile = fileName =&gt; {
  return fs.lstatSync(fileName).isFile()
}
fs.readdirSync(folderPath).map(fileName =&gt; { 
   return path.join(folderPath, fileName).filter(isFile)
})
</code></pre>
<h4 id="">重命名文件夹</h4>
<p>使用 <code>fs.rename()</code> 或 <code>fs.renameSync()</code> 来重命名文件夹。</p>
<p>第一个参数是当前路径，第二个参数是新路径:</p>
<pre><code class="language-js">const fs = require('fs')
fs.rename('/Users/flavio', '/Users/roger', (err) =&gt; { 
    if (err) {
      console.error(err)    
      return  
    }  
})//done
</code></pre>
<p><code>fs.renameSync()</code> 是同步版本:</p>
<pre><code class="language-js">const fs = require('fs')
try { fs.renameSync('/Users/flavio', '/Users/roger')
} catch (err) {  
  console.error(err)
}
</code></pre>
<h4 id="">删除文件夹</h4>
<p>使用 <code>fs.rmdir()</code> 或 <code>fs.rmdirSync()</code> 来删除一个文件夹。</p>
<p>删除一个有内容的文件夹可能比你需要的更复杂。</p>
<p>在这种情况下，我建议安装 <code>fs-extra</code> 模块，它非常受欢迎，维护得很好，它可以直接替换 <code>fs</code> 模块，在其基础上提供更多的功能。</p>
<p>在这种情况下，<code>remove()</code> 方法是你想要的。</p>
<p>用以下方法安装它:</p>
<p><code>npm install fs-extra</code></p>
<p>像这样使用它:</p>
<pre><code class="language-js">const fs = require('fs-extra')
const folder = '/Users/flavio'
fs.remove(folder, err =&gt; {console.error(err)})
</code></pre>
<p>它也可以与 Promises 一起使用:</p>
<pre><code class="language-js">fs.remove(folder).then(() =&gt; {done}).catch(err =&gt; {
  console.error(err)
}) //done 
</code></pre>
<p>或者使用 <code>async/await</code>:</p>
<pre><code class="language-js">async function removeFolder(folder) {  
  try { 
    await fs.remove(folder)
  }//done 
  catch (err) {console.error(err) }
}

const folder = '/Users/flavio'
removeFolder(folder)
</code></pre>
<h3 id="nodejsfs">Node.js fs 模块</h3>
<p><code>fs</code> 模块提供了很多非常有用的功能来访问文件系统并与之互动。</p>
<p>不需要安装它。作为 Node.js 核心的一部分，它可以通过简单地要求它来使用:</p>
<pre><code class="language-js">const fs = require('fs')
</code></pre>
<p>一旦你这样做，你就可以使用它的所有方法，其中包括:</p>
<ul>
<li><code>fs.access()</code>: 检查文件是否存在，并且Node可以用其权限访问它。</li>
<li><code>fs.appendFile()</code>: 将数据追加到文件中。如果文件不存在，就创建它</li>
<li><code>fs.chmod()</code>: 改变一个由文件名指定的文件的权限。相关的: <code>fs.lchmod()</code>, <code>fs.fchmod()</code></li>
<li><code>fs.chown()</code>: 改变由文件名指定的文件的所有者和组。相关的: <code>fs.fchown()</code>, <code>fs.lchown()</code></li>
<li><code>fs.close()</code>: 关闭一个文件描述符</li>
<li><code>fs.copyFile()</code>: 复制一个文件</li>
<li><code>fs.createReadStream()</code>: 创建一个可读文件流</li>
<li><code>fs.createWriteStream()</code>: 创建一个可写的文件流</li>
<li><code>fs.link()</code>: 为文件创建一个新的硬链接</li>
<li><code>fs.mkdir()</code>: 创建一个新的文件夹</li>
<li><code>fs.mkdtemp()</code>: 创建一个临时目录</li>
<li><code>fs.open()</code>: 设置文件模式</li>
<li><code>fs.readdir()</code>: 读取一个目录的内容</li>
<li><code>fs.readFile()</code>: 读取一个文件的内容. 相关的: <code>fs.read()</code></li>
<li><code>fs.readlink()</code>: 读取一个符号链接的值</li>
<li><code>fs.realpath()</code>: 将相对文件路径指针 (<code>.</code>, <code>..</code>) 解析为全路径</li>
<li><code>fs.rename()</code>:  重命名一个文件或文件夹</li>
<li><code>fs.rmdir()</code>: 删除一个文件夹</li>
<li><code>fs.stat()</code>: 返回由文件名识别的文件的状态。相关的: <code>fs.fstat()</code>, <code>fs.lstat()</code></li>
<li><code>fs.symlink()</code>: 创建一个新的符号链接到一个文件</li>
<li><code>fs.truncate()</code>: 将文件名标识的文件截断到指定长度。相关的: <code>fs.ftruncate()</code></li>
<li><code>fs.unlink()</code>: 删除一个文件或一个符号链接</li>
<li><code>fs.unwatchFile()</code>: 停止监视一个文件的变化</li>
<li><code>fs.utimes()</code>: 改变由文件名标识的文件的时间戳。 相关的: <code>fs.futimes()</code></li>
<li><code>fs.watchFile()</code>: 开始监视一个文件的变化。相关的: <code>fs.watch()</code></li>
<li><code>fs.writeFile()</code>: 向文件写入数据。 相关的: <code>fs.write()</code></li>
</ul>
<p>关于 <code>fs</code> 模块的一个特别之处是，所有的方法默认都是异步的，但它们也可以通过附加 <code>Sync</code> 而同步工作。</p>
<p>例子:</p>
<ul>
<li><code>fs.rename()</code></li>
<li><code>fs.renameSync()</code></li>
<li><code>fs.write()</code></li>
<li><code>fs.writeSync()</code></li>
</ul>
<p>这对你的应用流程有很大的影响。</p>
<p>Node 10 包括对基于 Promise 的API的 <a href="https://nodejs.org/api/fs.html#fs_fs_promises_api">实验性支持</a>。</p>
<p>例如，让我们检查 <code>fs.rename()</code> 方法。异步 API 是用一个回调来实现的:</p>
<pre><code class="language-js">const fs = require('fs')
fs.rename('before.json', 'after.json', (err) =&gt; {  
     if (err) {    
       return console.error(err)  
    }
})//done
</code></pre>
<p>一个同步的 API 可以这样使用，用一个 <code>try/catch</code> 块来处理错误:</p>
<pre><code class="language-js">const fs = require('fs')
try {
  fs.renameSync('before.json', 'after.json')//done
} catch(err) {  
  console.error(err)
}
</code></pre>
<p>这里的关键区别是，在第二个例子中，你的脚本的执行将被阻塞，直到文件操作成功。</p>
<h3 id="nodejs">Node.js 的路径模块</h3>
<p><code>path</code> 模块提供了很多非常有用的功能，可以访问文件系统并与之互动。</p>
<p>没有必要安装它。作为 Node.js 核心的一部分，它可以通过简单地要求它来使用:</p>
<pre><code class="language-js">const path = require('path')
</code></pre>
<p>这个模块提供了 <code>path.sep</code>，它提供了路径段的分隔符（在 Windows下为 <code>/</code>，在 Linux/MacOS下为 <code>/</code>），以及 <code>path.delimiter</code>，它提供了路径分隔符（在 Windows 下为<code>；</code>，在 Linux/MacOS下为<code>：</code>）。</p>
<p>这些是 <code>路径</code> 方法。</p>
<h4 id="pathbasename"><code>path.basename()</code></h4>
<p>返回一个路径的最后部分。第二个参数可以过滤掉文件扩展名:</p>
<pre><code class="language-js">require('path').basename('/test/something') //something
require('path').basename('/test/something.txt') //something.txt
require('path').basename('/test/something.txt', '.txt') //something
</code></pre>
<h4 id="pathdirname"><code>path.dirname()</code></h4>
<p>返回一个路径的目录部分:</p>
<pre><code class="language-js">require('path').dirname('/test/something') // /test
require('path').dirname('/test/something/file.txt') // /test/something
</code></pre>
<h4 id="pathextname"><code>path.extname()</code></h4>
<p>返回一个路径的扩展部分:</p>
<pre><code class="language-js">require('path').dirname('/test/something') // ''
require('path').dirname('/test/something/file.txt') // '.txt'
</code></pre>
<h4 id="pathisabsolute"><code>path.isAbsolute()</code></h4>
<p>如果它是一个绝对路径，则返回 true:</p>
<pre><code class="language-js">require('path').isAbsolute('/test/something') // true
require('path').isAbsolute('./test/something') // false
</code></pre>
<h4 id="pathjoin"><code>path.join()</code></h4>
<p>连接一个路径的两个或多个部分:</p>
<pre><code class="language-js">const name = 'flavio'
require('path').join('/', 'users', name, 'notes.txt') //'/users/flavio/notes.txt'
</code></pre>
<h4 id="pathnormalize"><code>path.normalize()</code></h4>
<p>试图计算实际路径，当它包含相对指定符如 <code>.</code> 或 <code>..</code>，或双斜线（<code>//</code>）:</p>
<pre><code class="language-js">require('path').normalize('/users/flavio/..//test.txt') ///users/test.txt
</code></pre>
<h4 id="pathparse"><code>path.parse()</code></h4>
<p>解析一个对象的路径和组成它的片段:</p>
<ul>
<li><code>root</code>: 根目录</li>
<li><code>dir</code>: 从根开始的文件夹路径</li>
<li><code>base</code>: 文件名+扩展名</li>
<li><code>name</code>: 文件名</li>
<li><code>ext</code>: 扩展名</li>
</ul>
<p>例如:</p>
<pre><code class="language-js">require('path').parse('/users/test.txt')
</code></pre>
<p>结果:</p>
<pre><code class="language-js">{  root: '/',  dir: '/users',  base: 'test.txt',  ext: '.txt',  name: 'test'}
</code></pre>
<h4 id="pathrelative"><code>path.relative()</code></h4>
<p>接受两个路径作为参数。基于当前工作目录，返回从第一个路径到第二个路径的相对路径。</p>
<p>例如:</p>
<pre><code class="language-js">require('path').relative('/Users/flavio', '/Users/flavio/test.txt') //'test.txt'
require('path').relative('/Users/flavio', '/Users/flavio/something/test.txt') //'something/test.txt'
</code></pre>
<h4 id="pathresolve"><code>path.resolve()</code></h4>
<p>你可以使用 <code>path.resolve()</code> 获得从相对路径得到绝对路径:</p>
<pre><code class="language-js">path.resolve('flavio.txt') //'/Users/flavio/flavio.txt' if run from my home folder
</code></pre>
<p>通过指定第二个参数，<code>resolve</code> 将使用第一个参数作为第二个参数的基础:</p>
<pre><code class="language-js">path.resolve('tmp', 'flavio.txt')//'/Users/flavio/tmp/flavio.txt' if run from my home folder
</code></pre>
<p>如果第一个参数以斜线开头，这意味着它是一个绝对路径:</p>
<pre><code class="language-js">path.resolve('/etc', 'flavio.txt')//'/etc/flavio.txt'
</code></pre>
<h3 id="nodejsos">Node.js 的 os 模块</h3>
<p>这个模块提供了许多功能，你可以用来从底层的<strong>操作系统</strong>和程序运行的计算机上检索信息，并与之进行交互。</p>
<pre><code class="language-js">const os = require('os')
</code></pre>
<p>有几个有用的属性告诉我们一些与处理文件有关的关键事情:</p>
<p><code>os.EOL</code> 给出了行的定界符序列。在 Linux和MacOS 上是 <code>\n</code>，而在 Windows 上是 <code>\r\n</code>。</p>
<p>当我说 Linux 和 MacOS 时，我指的是 POSIX 平台。为了简单起见，我排除了其他不太流行的操作系统，Node 可以在上面运行。</p>
<p><code>os.constants.signals</code> 告诉我们所有与处理进程信号有关的常量，如 SIGHUP, SIGKILL 等。</p>
<p><code>os.constants.errno</code> 设置错误报告的常量，如 EADDRINUSE、EOVERFLOW 等。</p>
<p>你可以全部阅读 <a href="https://nodejs.org/api/os.html#os_signal_constants">这里</a>。</p>
<p>现在让我们看看 <code>os</code> 提供的主要方法:</p>
<ul>
<li><code>os.arch()</code></li>
<li><code>os.cpus()</code></li>
<li><code>os.endianness()</code></li>
<li><code>os.freemem()</code></li>
<li><code>os.homedir()</code></li>
<li><code>os.hostname()</code></li>
<li><code>os.loadavg()</code></li>
<li><code>os.networkInterfaces()</code></li>
<li><code>os.platform()</code></li>
<li><code>os.release()</code></li>
<li><code>os.tmpdir()</code></li>
<li><code>os.totalmem()</code></li>
<li><code>os.type()</code></li>
<li><code>os.uptime()</code></li>
<li><code>os.userInfo()</code></li>
</ul>
<h4 id="osarch"><code>os.arch()</code></h4>
<p>返回标识底层架构的字符串，如 <code>arm</code>, <code>x64</code>, <code>arm64</code>.</p>
<h4 id="oscpus"><code>os.cpus()</code></h4>
<p>返回你系统上可用的 CPU 的信息。</p>
<p>例如:</p>
<pre><code class="language-js">[{
    model: 'Intel(R) Core(TM)2 Duo CPU     P8600  @ 2.40GHz',
    speed: 2400,
    times: {
        user: 281685380,
        nice: 0,
        sys: 187986530,
        idle: 685833750,
        irq: 0
    }
}, {
    model: 'Intel(R) Core(TM)2 Duo CPU     P8600  @ 2.40GHz',
    speed: 2400,
    times: {
        user: 282348700,
        nice: 0,
        sys: 161800480,
        idle: 703509470,
        irq: 0
    }
}]
</code></pre>
<h4 id="osendianness"><code>os.endianness()</code></h4>
<p>返回 <code>BE</code> 或 <code>LE</code>，取决于 Node.js 是用 <a href="https://en.wikipedia.org/wiki/Endianness">Big Endian or Little Endian</a> 编译的。</p>
<h4 id="osfreemem"><code>os.freemem()</code></h4>
<p>返回代表系统中空闲内存的字节数。</p>
<h4 id="oshomedir"><code>os.homedir()</code></h4>
<p>返回到当前用户的主目录的路径。</p>
<p>例如：</p>
<pre><code class="language-js">'/Users/flavio'
</code></pre>
<h4 id="oshostname"><code>os.hostname()</code></h4>
<p>返回主机名。</p>
<h4 id="osloadavg"><code>os.loadavg()</code></h4>
<p>返回操作系统对负载平均值的计算结果。</p>
<p>它只在 Linux 和 MacOS 上返回一个有意义的值。</p>
<p>例如：</p>
<pre><code class="language-js">[ 3.68798828125, 4.00244140625, 11.1181640625 ]
</code></pre>
<h4 id="osnetworkinterfaces"><code>os.networkInterfaces()</code></h4>
<p>返回你系统中可用的网络接口的详细信息。</p>
<p>例如:</p>
<pre><code class="language-js">{
    lo0: [{
        address: '127.0.0.1',
        netmask: '255.0.0.0',
        family: 'IPv4',
        mac: 'fe:82:00:00:00:00',
        internal: true
    }, {
        address: '::1',
        netmask: 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff',
        family: 'IPv6',
        mac: 'fe:82:00:00:00:00',
        scopeid: 0,
        internal: true
    }, {
        address: 'fe80::1',
        netmask: 'ffff:ffff:ffff:ffff::',
        family: 'IPv6',
        mac: 'fe:82:00:00:00:00',
        scopeid: 1,
        internal: true
    }],
    en1: [{
        address: 'fe82::9b:8282:d7e6:496e',
        netmask: 'ffff:ffff:ffff:ffff::',
        family: 'IPv6',
        mac: '06:00:00:02:0e:00',
        scopeid: 5,
        internal: false
    }, {
        address: '192.168.1.38',
        netmask: '255.255.255.0',
        family: 'IPv4',
        mac: '06:00:00:02:0e:00',
        internal: false
    }],
    utun0: [{
        address: 'fe80::2513:72bc:f405:61d0',
        netmask: 'ffff:ffff:ffff:ffff::',
        family: 'IPv6',
        mac: 'fe:80:00:20:00:00',
        scopeid: 8,
        internal: false
    }]
}
</code></pre>
<h4 id="osplatform"><code>os.platform()</code></h4>
<p>返回 Node.js 编译时使用的平台：</p>
<ul>
<li><code>darwin</code></li>
<li><code>freebsd</code></li>
<li><code>linux</code></li>
<li><code>openbsd</code></li>
<li><code>win32</code></li>
<li>……more</li>
</ul>
<h4 id="osrelease"><code>os.release()</code></h4>
<p>返回一个字符串，用于识别操作系统的发行号。</p>
<h4 id="ostmpdir"><code>os.tmpdir()</code></h4>
<p>返回指定的临时文件夹的路径。</p>
<h4 id="ostotalmem"><code>os.totalmem()</code></h4>
<p>返回代表系统中可用的总内存的字节数。</p>
<h4 id="ostype"><code>os.type()</code></h4>
<p>识别操作系统：</p>
<ul>
<li><code>Linux</code></li>
<li><code>Darwin</code> on MacOS</li>
<li><code>Windows_NT</code> on Windows</li>
</ul>
<h4 id="osuptime"><code>os.uptime()</code></h4>
<p>返回计算机自上次重启以来的运行秒数。</p>
<h3 id="nodejs">Node.js 的事件模块</h3>
<p><code>events</code> 模块为我们提供了 <code>EventEmitter</code> 类，它是在 Node.js 中处理事件的关键。</p>
<p>我在这方面发表了一篇完整的 <a href="https://flaviocopes.com/node-event-emitter/">文章</a>，所以在这里我将只描述 API，而不进一步举例说明如何使用它。</p>
<pre><code class="language-js">const EventEmitter = require('events')
const door = new EventEmitter()
</code></pre>
<p>事件监听器吃自己的狗粮（译者注： dog food 是指自己写的代码），使用这些事件:</p>
<ul>
<li><code>newListener</code> 当一个监听器被添加</li>
<li><code>removeListener</code> 当一个监听器被移除</li>
</ul>
<p>当一个监听器被移除：</p>
<ul>
<li><code>emitter.addListener()</code></li>
<li><code>emitter.emit()</code></li>
<li><code>emitter.eventNames()</code></li>
<li><code>emitter.getMaxListeners()</code></li>
<li><code>emitter.listenerCount()</code></li>
<li><code>emitter.listeners()</code></li>
<li><code>emitter.off()</code></li>
<li><code>emitter.on()</code></li>
<li><code>emitter.once()</code></li>
<li><code>emitter.prependListener()</code></li>
<li><code>emitter.prependOnceListener()</code></li>
<li><code>emitter.removeAllListeners()</code></li>
<li><code>emitter.removeListener()</code></li>
<li><code>emitter.setMaxListeners()</code></li>
</ul>
<h4 id="emitteraddlistener"><code>emitter.addListener()</code></h4>
<p>别名 <code>emitter.on()</code>.</p>
<h4 id="emitteremit"><code>emitter.emit()</code></h4>
<p>发出一个事件。它按注册的顺序同步调用每个事件监听器。</p>
<h4 id="emittereventnames"><code>emitter.eventNames()</code></h4>
<p>返回一个字符串数组，代表在当前 EventListener 上注册的事件：</p>
<pre><code class="language-js">door.eventNames()
</code></pre>
<h4 id="emittergetmaxlisteners"><code>emitter.getMaxListeners()</code></h4>
<p>获取可以添加到 EventListener 对象中的最大监听器数量，默认为 10，但可以通过使用 <code>setMaxListeners()</code> 增加或减少：</p>
<pre><code class="language-js">door.getMaxListeners()
</code></pre>
<h4 id="emitterlistenercount"><code>emitter.listenerCount()</code></h4>
<p>获取作为参数传递的事件的监听数量：</p>
<pre><code class="language-js">door.listenerCount('open')
</code></pre>
<h4 id="emitterlisteners"><code>emitter.listeners()</code></h4>
<p>获取作为参数传递的事件的监听者数组：</p>
<pre><code class="language-js">door.listeners('open')
</code></pre>
<h4 id="emitteroff"><code>emitter.off()</code></h4>
<p>别名 <code>emitter.removeListener()</code>，在 Node 10 加入的。</p>
<h4 id="emitteron"><code>emitter.on()</code></h4>
<p>添加一个回调函数，当一个事件被发出时被调用。</p>
<p>使用案例:</p>
<pre><code class="language-js">door.on('open', () =&gt; {console.log('Door was opened')})
</code></pre>
<h4 id="emitteronce"><code>emitter.once()</code></h4>
<p>添加一个回调函数，在注册后第一次发出事件时被调用。这个回调函数只被调用一次，不会再被调用。</p>
<pre><code class="language-js">const EventEmitter = require('events')
const ee = new EventEmitter()
</code></pre>
<pre><code class="language-js">ee.once('my-event', () =&gt; {  
  //call callback function once
})
</code></pre>
<h4 id="emitterprependlistener"><code>emitter.prependListener()</code></h4>
<p>当你使用 <code>on</code> 或 <code>addListener</code> 添加监听器时，它在监听器队列中被最后添加，并被最后调用。使用 <code>prependListener</code>，它将在其他监听器之前被添加和调用。</p>
<h4 id="emitterprependoncelistener"><code>emitter.prependOnceListener()</code></h4>
<p>当你使用 <code>once</code> 添加一个监听器时，它在监听器队列中最后被添加，并最后被调用。使用 <code>prependOnceListener</code>，它将在其他监听器之前被添加和调用。</p>
<h4 id="emitterremovealllisteners"><code>emitter.removeAllListeners()</code></h4>
<p>移除监听某一特定事件的事件发射器对象的所有监听者：</p>
<pre><code class="language-js">door.removeAllListeners('open')
</code></pre>
<h4 id="emitterremovelistener"><code>emitter.removeListener()</code></h4>
<p>删除一个特定的监听器。你可以这样做，在添加回调函数时，将其保存到一个变量中，这样你以后就可以引用它了：</p>
<pre><code class="language-js">const doSomething = () =&gt; {
  door.on('open', doSomething)
  door.removeListener('open', doSomething)
}
</code></pre>
<h4 id="emittersetmaxlisteners"><code>emitter.setMaxListeners()</code></h4>
<p>设置一个人可以添加到 EventListener 对象中的最大监听器数量，默认为 10，但可以增加或减少:</p>
<pre><code class="language-js">door.setMaxListeners(50)
</code></pre>
<h3 id="nodejshttp">Node.js http 模块</h3>
<p>Node.js 的 <code>http</code> 模块提供了有用的函数和类来建立一个 HTTP 服务器。它是 Node.js 网络的一个关键模块。</p>
<p>它可以用以下方式引入：</p>
<pre><code class="language-js">const http = require('http')
</code></pre>
<p>该模块提供了一些属性和方法，以及一些类。</p>
<h4 id="properties">Properties</h4>
<h4 id="httpmethods"><code>http.METHODS</code></h4>
<p>此属性列出了所有支持的 HTTP 方法：</p>
<pre><code class="language-js">&gt; require('http').METHODS
[ 'ACL',  'BIND',  'CHECKOUT',  'CONNECT',  'COPY',  'DELETE',  'GET',  'HEAD',  'LINK',  'LOCK',  'M-SEARCH',  'MERGE',  'MKACTIVITY',  'MKCALENDAR',  'MKCOL',  'MOVE',  'NOTIFY',  'OPTIONS',  'PATCH',  'POST',  'PROPFIND',  'PROPPATCH',  'PURGE',  'PUT',  'REBIND',  'REPORT',  'SEARCH',  'SUBSCRIBE',  'TRACE',  'UNBIND',  'UNLINK',  'UNLOCK',  'UNSUBSCRIBE' ]
</code></pre>
<h4 id="httpstatus_codes"><code>http.STATUS_CODES</code></h4>
<p>此属性列出了所有的HTTP状态代码及其描述：</p>
<pre><code class="language-js">&gt; require('http').STATUS_CODES
{ '100': 'Continue',  '101': 'Switching Protocols',  '102': 'Processing',  '200': 'OK',  '201': 'Created',  '202': 'Accepted',  '203': 'Non-Authoritative Information',  '204': 'No Content',  '205': 'Reset Content',  '206': 'Partial Content',  '207': 'Multi-Status',  '208': 'Already Reported',  '226': 'IM Used',  '300': 'Multiple Choices',  '301': 'Moved Permanently',  '302': 'Found',  '303': 'See Other',  '304': 'Not Modified',  '305': 'Use Proxy',  '307': 'Temporary Redirect',  '308': 'Permanent Redirect',  '400': 'Bad Request',  '401': 'Unauthorized',  '402': 'Payment Required',  '403': 'Forbidden',  '404': 'Not Found',  '405': 'Method Not Allowed',  '406': 'Not Acceptable',  '407': 'Proxy Authentication Required',  '408': 'Request Timeout',  '409': 'Conflict',  '410': 'Gone',  '411': 'Length Required',  '412': 'Precondition Failed',  '413': 'Payload Too Large',  '414': 'URI Too Long',  '415': 'Unsupported Media Type',  '416': 'Range Not Satisfiable',  '417': 'Expectation Failed',  '418': 'I\'m a teapot',  '421': 'Misdirected Request',  '422': 'Unprocessable Entity',  '423': 'Locked',  '424': 'Failed Dependency',  '425': 'Unordered Collection',  '426': 'Upgrade Required',  '428': 'Precondition Required',  '429': 'Too Many Requests',  '431': 'Request Header Fields Too Large',  '451': 'Unavailable For Legal Reasons',  '500': 'Internal Server Error',  '501': 'Not Implemented',  '502': 'Bad Gateway',  '503': 'Service Unavailable',  '504': 'Gateway Timeout',  '505': 'HTTP Version Not Supported',  '506': 'Variant Also Negotiates',  '507': 'Insufficient Storage',  '508': 'Loop Detected',  '509': 'Bandwidth Limit Exceeded',  '510': 'Not Extended',  '511': 'Network Authentication Required' }
</code></pre>
<h4 id="httpglobalagent"><code>http.globalAgent</code></h4>
<p>指向 Agent 对象的全局实例，它是 <code>http.Agent</code> 类的一个实例。</p>
<p>它用于管理 HTTP 客户端的连接持久性和重用，是 Node.js HTTP 网络的一个关键组件。</p>
<p>在后面的 <code>http.Agent</code> 类描述中会有更多内容。</p>
<h4 id="methods">Methods</h4>
<h4 id="httpcreateserver"><code>http.createServer()</code></h4>
<p>返回一个 <code>http.Server</code> 类的新实例。</p>
<p>用法:</p>
<pre><code class="language-js">const server = http.createServer((req, res) =&gt; {})//handle every single request with this callback
</code></pre>
<h4 id="httprequest"><code>http.request()</code></h4>
<h4 id="httpget"><code>http.get()</code></h4>
<p>向服务器发出一个 HTTP 请求，创建一个 <code>http.ClientRequest</code> 类的实例。</p>
<h4 id="classes">Classes</h4>
<p>HTTP模块提供了5个类（classes）：</p>
<ul>
<li><code>http.Agent</code></li>
<li><code>http.ClientRequest</code></li>
<li><code>http.Server</code></li>
<li><code>http.ServerResponse</code></li>
<li><code>http.IncomingMessage</code></li>
</ul>
<h4 id="httpagent"><code>http.Agent</code></h4>
<p>Node创建了一个 <code>http.Agent</code> 类的全局实例来管理HTTP客户端的连接持久性和重复使用，这是 Node HTTP 网络的一个关键组成部分。</p>
<p>这个对象确保每一个向服务器发出的请求都是排队的，并且一个套接字被重复使用。</p>
<p>它还维护一个套接字池。这是性能方面的关键。</p>
<h4 id="httpclientrequest"><code>http.ClientRequest</code></h4>
<p>当 <code>http.request()</code> 或 <code>http.get()</code> 被调用时，一个 <code>http.ClientRequest</code> 对象被创建。</p>
<p>当收到一个响应时，<code>response</code> 事件会被调用，并以一个 <code>http.IncomingMessage</code> 实例作为参数。</p>
<p>响应的返回数据可以通过两种方式读取:</p>
<ul>
<li>你可以调用 <code>response.read()</code> 方法</li>
<li>在 <code>response</code> 事件处理程序中，你可以为 <code>data</code> 事件设置一个事件监听器，所以你可以监听流进的数据。</li>
</ul>
<h4 id="httpserver"><code>http.Server</code></h4>
<p>这个类通常在使用 <code>http.createServer()</code> 创建一个新的服务器时被实例化并返回。</p>
<p>一旦你有了一个服务器对象，你就可以访问它的方法：</p>
<ul>
<li><code>close()</code> 停止服务器接受新的连接</li>
<li><code>listen()</code> 启动HTTP服务器并监听连接</li>
</ul>
<h4 id="httpserverresponse"><code>http.ServerResponse</code></h4>
<p>由<code>http.Server</code>创建，并作为第二个参数传递给它所触发的 <code>request</code> 事件。</p>
<p>通常在代码中被称为 <code>res</code>：</p>
<pre><code class="language-js">const server = http.createServer((req, res) =&gt; { })//res is an http.ServerResponse object
</code></pre>
<p>你总是在处理程序中调用的方法是 <code>end()</code>，它关闭了响应，消息已经完成，服务器可以把它发送给客户端。它必须在每个响应中被调用。</p>
<p>这些方法是用来与 HTTP 头信息交互的：</p>
<ul>
<li><code>getHeaderNames()</code> 获得已经设置的 HTTP 头的名称列表</li>
<li><code>getHeaders()</code> 获得一份已经设置的 HTTP 头的副本</li>
<li><code>setHeader('headername', value)</code> 设置一个 HTTP 头的值</li>
<li><code>getHeader('headername')</code> 获取一个已经设置的 HTTP 头信息</li>
<li><code>removeHeader('headername')</code> 删除一个已经设置的 HTTP 头</li>
<li><code>hasHeader('headername')</code> 如果响应中设置了该头信息，则返回 true</li>
<li><code>headersSent()</code> 如果头信息已经被发送到客户端，则返回 true</li>
</ul>
<p>在处理完头信息后，你可以通过调用 <code>response.writeHead()</code> 将它们发送给客户端，它接受 statusCode 作为第一个参数、可选的状态信息和头信息对象。</p>
<p>要在响应体中向客户端发送数据，你可以使用 <code>write()</code>。它将发送缓冲的数据到 HTTP 响应流中。</p>
<p>如果使用 <code>response.writeHead()</code> 还没有发送头信息，它将首先发送头信息，并在请求中设置状态码和信息，你可以通过设置 <code>statusCode</code> 和 <code>statusMessage</code> 属性值来编辑:</p>
<pre><code class="language-js">response.statusCode = 500
response.statusMessage = 'Internal Server Error'
</code></pre>
<h4 id="httpincomingmessage"><code>http.IncomingMessage</code></h4>
<p>一个 "http.IncomingMessage" 对象是通过以下方式创建的：</p>
<ul>
<li><code>http.Server</code> 监听 <code>request</code> 事件</li>
<li><code>http.ClientRequest</code> 监听 "response" 事件</li>
</ul>
<p>它可以用来访问响应（response）：</p>
<ul>
<li>status，使用 <code>statusCode</code> 和 <code>statusMessage</code> 方法</li>
<li>headers，使用 <code>headers</code> 方法或 <code>rawHeaders</code></li>
<li>HTTP method 使用它的 <code>method</code> method</li>
<li>HTTP version 使用 <code>httpVersion</code> method</li>
<li>URL 使用 <code>url</code> method</li>
<li>使用 "socket "方法的底层套接字</li>
</ul>
<p>由于<code>http.IncomingMessage</code> 实现了可读流接口，所以数据可以使用流访问。</p>
<h3 id="nodejsstreams">Node.js流（Streams）</h3>
<p>流是支持 Node.js 应用程序的基本概念之一。</p>
<p>它们是一种有效处理读/写文件、网络通信或任何种类的端到端信息交换的方式。</p>
<p>流不是 Node.js 特有的概念。几十年前，它们就被引入到 Unix 操作系统中，程序之间可以通过管道操作符（<code>|</code>）传递流进行交互。</p>
<p>例如，在传统的方式中，当你告诉程序读取一个文件时，文件被读入内存，从头到尾，然后你处理它。</p>
<p>使用流，你会一块一块地读取它，处理它的内容，而不把它全部保留在内存中。</p>
<p>Node.js的 <a href="https://nodejs.org/api/stream.html">stream模块</a> 提供了所有流媒体API的基础。</p>
<h4 id="">为什么是流？</h4>
<p>流基本上提供了使用其他数据处理方法的两个主要优势：</p>
<ul>
<li><strong>内存效率</strong>: 你不需要在处理数据之前在内存中加载大量的数据</li>
<li><strong>时间效率</strong>: 一旦你有了数据，就开始处理，而不是等到整个数据负载可用时才开始，这需要的时间要少得多。</li>
</ul>
<h4 id="">一个流的例子</h4>
<p>一个典型的例子是从磁盘上读取文件的例子。</p>
<p>使用Node.js <code>fs</code> 模块，你可以读取一个文件，并在与你的 <code>http</code> 服务器建立新的连接时通过 HTTP 提供服务：</p>
<pre><code class="language-js">const http = require('http')
const fs = require('fs')
const server = http.createServer(function (req, res) {
  fs.readFile(__dirname + '/data.txt', (err, data) =&gt; {
    res.end(data) })
})
server.listen(3000)
</code></pre>
<p><code>readFile()</code> 读取文件的全部内容，并在完成后调用回调函数。</p>
<p>回调函数中的 <code>res.end(data)</code> 将返回文件内容给 HTTP 客户端。</p>
<p>如果文件很大，这个操作将花费相当多的时间。下面是用流写的同样的东西：</p>
<pre><code class="language-js">const http = require('http')
const fs = require('fs')
const server = http.createServer((req, res) =&gt; {  
  const stream = fs.createReadStream(__dirname + '/data.txt')
  stream.pipe(res)
})
server.listen(3000)
</code></pre>
<p>我们不是等到文件被完全读完，而是一旦有了准备好的数据块，就开始把它流向 HTTP 客户端。</p>
<h4 id="pipe">pipe()</h4>
<p>上面的例子使用了 <code>stream.pipe(res)</code> 一行：在文件流中调用了 <code>pipe()</code> 方法。</p>
<p>这段代码做了什么？它接收源文件，并将其输送到一个目的地。</p>
<p>你在源流上调用它，所以在本例中，文件流被管道到 HTTP 响应。</p>
<p><code>pipe()</code> 方法的返回值是目标流，这是一个非常方便的东西，让我们可以连锁调用多个 <code>pipe()</code>，像这样:</p>
<pre><code class="language-js">src.pipe(dest1).pipe(dest2)
</code></pre>
<p>这个结构做相同的事：</p>
<pre><code class="language-js">src.pipe(dest1)
dest1.pipe(dest2)
</code></pre>
<h4 id="nodejsstreamapi">Node.js 的流（stream） API</h4>
<p>由于它们的优势，许多 Node.js 核心模块提供了原生的流处理能力，最显著的是：</p>
<ul>
<li><code>process.stdin</code> 返回一个连接到 stdin 的流</li>
<li><code>process.stdout</code> 返回连接到 stdout 的流</li>
<li><code>process.stderr</code> 返回一个与 stderr 相连的流</li>
<li><code>fs.createReadStream()</code> 创建一个到文件的可读流</li>
<li><code>fs.createWriteStream()</code> 创建一个到文件的可写流</li>
<li><code>net.connect()</code> 发起一个基于流的连接</li>
<li><code>http.request()</code> 返回 http.ClientRequest 类的一个实例，这是一个可写流</li>
<li><code>zlib.createGzip()</code> 使用 gzip（一种压缩算法）将数据压缩到一个流中</li>
<li><code>zlib.createGunzip()</code> 解压一个 gzip 流</li>
<li><code>zlib.createDeflate()</code> 使用 deflate（一种压缩算法）将数据压缩到一个流中</li>
<li><code>zlib.createInflate()</code> 解压一个 deflate 流</li>
</ul>
<h4 id="">不同类型的流</h4>
<p>有四种类型的流：</p>
<ul>
<li><code>Readable</code>: 你可以用管道输送，但不能用管道进入（你可以接收数据，但不能向它发送数据）。当你向一个可读流推送数据时，它被缓冲，直到消费者开始读取数据。</li>
<li><code>Writable</code>: 你可以用管道进入，但不能用管道离开（你可以发送数据，但不能从它接收）</li>
<li><code>Duplex</code>: 一个既可以管入又可以管出的流，基本上是一个可读流和可写流的组合。</li>
<li><code>Transform</code>: 类似于 <code>Duplex</code>，但输出是其输入的变换</li>
</ul>
<h4 id="">如何创建一个可读流</h4>
<p>我们从 <code>stream</code> 模块获得 <code>可读(Readable)</code> 流，并初始化它：</p>
<pre><code class="language-js">const Stream = require('stream')
const readableStream = new Stream.Readable()
</code></pre>
<p>现在，流已经被初始化，我们可以向它发送数据了：</p>
<pre><code class="language-js">readableStream.push('hi!')
readableStream.push('ho!')
</code></pre>
<h4 id="">如何创建一个可写流</h4>
<p>为了创建一个可写流，我们扩展了基础的 <code>Writable</code> 对象，并实现了它的 <code>_write()</code> 方法。</p>
<p>首先创建一个流对象：</p>
<pre><code class="language-js">const Stream = require('stream')
const writableStream = new Stream.Writable()
</code></pre>
<p>然后执行 <code>_write</code>：</p>
<pre><code class="language-js">writableStream._write = (chunk, encoding, next) =&gt; {
  console.log(chunk.toString())
  next()
}
</code></pre>
<p>你现在可以用管道输送一个可读流：</p>
<pre><code class="language-js">process.stdin.pipe(writableStream)
</code></pre>
<h4 id="">如何从一个可读流中获取数据</h4>
<p>我们如何从一个可读流中读取数据？使用一个可写流：</p>
<pre><code class="language-js">const Stream = require('stream')
const readableStream = new Stream.Readable()
const writableStream = new Stream.Writable()
writableStream._write = (chunk, encoding, next) =&gt; { 
  console.log(chunk.toString())
  next()
}

readableStream.pipe(writableStream)

readableStream.push('hi!')
readableStream.push('ho!')
</code></pre>
<p>你也可以直接消费一个可读流，使用 <code>readable</code> 事件：</p>
<pre><code class="language-js">readableStream.on('readable', () =&gt; {
  console.log(readableStream.read())
})
</code></pre>
<h4 id="">如何向可写流发送数据</h4>
<p>使用流 <code>write()</code> 方法：</p>
<pre><code class="language-js">writableStream.write('hey!\n')
</code></pre>
<h4 id="">向一个可写的流发出信号，当你想停止写入</h4>
<p>使用 <code>end()</code> 方法：</p>
<pre><code class="language-js">const Stream = require('stream')
const readableStream = new Stream.Readable()
const writableStream = new Stream.Writable()
writableStream._write = (chunk, encoding, next) =&gt; {
  console.log(chunk.toString())    
  next()
}
readableStream.pipe(writableStream)
readableStream.push('hi!')
readableStream.push('ho!')
writableStream.end()
</code></pre>
<h3 id="mysqlnodejs">使用 MySQL 和 Node.js 的基础知识</h3>
<p>MySQL 是世界上最流行的关系型数据库之一。</p>
<p>Node.js 生态系统有几个不同的包，允许你与 MySQL 接口，存储数据，检索数据，等等。</p>
<p>我们将使用 <a href="https://github.com/mysqljs/mysql">mysqljs/mysql</a>，这个包在 GitHub 上有超过 12,000 颗星，已经存在多年。</p>
<h4 id="nodejsmysql">安装Node.js MySql包</h4>
<p>安装命令:</p>
<pre><code class="language-shell">npm install mysql
</code></pre>
<h4 id="">初始化与数据库的连接</h4>
<p>你首先要引入包：</p>
<pre><code class="language-js">const mysql = require('mysql')
</code></pre>
<p>并创建一个连接：</p>
<pre><code class="language-js">const options = {  
  user: 'the_mysql_user_name', 
  password: 'the_mysql_user_password',  
  database: 'the_mysql_database_name'
}

const connection = mysql.createConnection(options)
</code></pre>
<p>你通过调用以下命令启动一个新的连接：</p>
<pre><code class="language-js">connection.connect(err =&gt; {  
  if (err) {  
      console.error('An error occurred while connecting to the DB')    
      throw err  
  }
})
</code></pre>
<h4 id="">连接选项</h4>
<p>在上面的例子中，<code>options</code> 对象包含 3 个选项：</p>
<pre><code class="language-js">const options = {  
  user: 'the_mysql_user_name',  
  password: 'the_mysql_user_password',
  database: 'the_mysql_database_name'
}
</code></pre>
<p>你还可以使用很多，包括：</p>
<ul>
<li><code>host</code>, 数据库主机名，默认为 <code>localhost</code></li>
<li><code>port</code>, MySQL服务器端口号，默认为 3306</li>
<li><code>socketPath</code>, 用于指定 Unix 套接字，而不是主机和端口</li>
<li><code>debug</code>, 默认为禁用，可用于调试</li>
<li><code>trace</code>, 默认为启用，当发生错误时打印堆栈跟踪</li>
<li><code>ssl</code>, 用于设置与服务器的SSL连接（不在本教程范围内）</li>
</ul>
<h4 id="select">执行一个 SELECT 查询</h4>
<p>现在你已经准备好在数据库上执行一个 SQL 查询。查询一旦执行，将调用一个回调函数，其中包含一个最终的错误、结果和字段（fields）：</p>
<pre><code class="language-js">connection.query('SELECT * FROM todos', (error, todos, fields) =&gt; {  
  if (error) { 
    console.error('An error occurred while executing the query')    
    throw error  
  }  
  console.log(todos)
})
</code></pre>
<p>你可以传入将被自动转义的值：</p>
<pre><code class="language-js">const id = 223
connection.query('SELECT * FROM todos WHERE id = ?', [id], (error, todos, fields) =&gt; {  
  if (error) { 
    console.error('An error occurred while executing the query')    
    throw error  
  }  
  console.log(todos)
})
</code></pre>
<p>要传递多个值，只需在你作为第二个参数传递的数组中放入更多元素即可：</p>
<pre><code class="language-js">const id = 223const author = 'Flavio'
connection.query('SELECT * FROM todos WHERE id = ? AND author = ?', [id, author], (error, todos, fields) =&gt; {  
  if (error) {
    console.error('An error occurred while executing the query')    
    throw error  
  }  
  console.log(todos)
})
</code></pre>
<h4 id="insert">执行一个INSERT 语句</h4>
<p>你可以传递一个对象：</p>
<pre><code class="language-js">const todo = {  thing: 'Buy the milk'  author: 'Flavio'}
connection.query('INSERT INTO todos SET ?', todo, (error, results, fields) =&gt; {  
  if (error) {    
    console.error('An error occurred while executing the query')    
    throw error  
  }
})
</code></pre>
<p>如果表有一个 "自动增量" 的主键，其值将在 "results.insertId" 值中返回:</p>
<pre><code class="language-js">const todo = {  thing: 'Buy the milk'  author: 'Flavio'}
connection.query('INSERT INTO todos SET ?', todo, (error, results, fields) =&gt; {  
  if (error) {    
    console.error('An error occurred while executing the query')    
    throw error  
  }
}  
const id = results.resultId 
console.log(id))
</code></pre>
<h4 id="">关闭连接</h4>
<p>当你需要终止与数据库的连接时，你可以调用 <code>end()</code> 方法:</p>
<pre><code class="language-js">connection.end()
</code></pre>
<p>这可以确保任何未决的查询被发送，并且连接被优雅地终止。</p>
<h3 id="">开发环境和生产环境之间的区别</h3>
<p>你可以为生产和开发环境进行不同的配置。</p>
<p>Node.js假定它总是在开发环境中运行。你可以通过设置 <code>NODE_ENV=production</code> 环境变量向 Node.js 发出信号，表明你正在生产环境中运行。</p>
<p>这通常是通过执行以下命令来完成的:</p>
<pre><code class="language-js">export NODE_ENV=production
</code></pre>
<p>在 Shell 中，但最好把它放在你的 Shell 配置文件中（比如 Bash shell 的 <code>.bash_profile</code> ），因为否则在系统重启的情况下，这个设置会失效。</p>
<p>你也可以通过在你的应用程序初始化命令前加上环境变量来应用它:</p>
<pre><code class="language-shell">NODE_ENV=production node app.js
</code></pre>
<p>这个环境变量是一个惯例，在外部库中也被广泛使用。</p>
<p>将环境设置为 <code>production</code> 通常可以确保以下：</p>
<ul>
<li>日志记录保持在最小的、必要的水平上</li>
<li>更多的缓存级别，以优化性能</li>
</ul>
<p>例如 <a href="https://pugjs.org/api/express.html">Pug</a>，Express 使用的模板库，如果 <code>NODE_ENV</code> 没有设置为 <code>production</code>，则在开发（development）模式下进行编译。在开发模式下，Express 视图在每个请求中都被编译，而在生产（production）模式下，它们被缓存起来。还有很多例子。</p>
<p>Express 提供了特定于环境的配置钩子，这些钩子根据<code>NODE_ENV</code>变量值自动调用：</p>
<pre><code class="language-js">app.configure('development', () =&gt; {})//...
app.configure('production', () =&gt; {})//...
app.configure('production', 'staging', () =&gt; {})//...
</code></pre>
<p>例如，你可以用它来为不同的模式设置不同的错误处理程序：</p>
<pre><code class="language-js">app.configure('development', () =&gt; {
  app.use(express.errorHandler({ dumpExceptions: true, showStack: true }));
})

app.configure('production', () =&gt; {
  app.use(express.errorHandler())
})
</code></pre>
<h3 id="">结语</h3>
<p>我希望对 Node.js 的介绍，能帮助你开始使用它，或者帮助你掌握它的一些概念。希望你现在知道的足够多，可以开始创造一些好东西！</p>
<!--kg-card-end: markdown--> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 如何使用 Node、Express 和 MongoDB 构建一个 RESTful API ]]>
                </title>
                <description>
                    <![CDATA[ 原文：How to Build a RESTful API Using Node, Express, and MongoDB [https://www.freecodecamp.org/news/build-a-restful-api-using-node-express-and-mongodb/] ，作者：Nishant Kumar [https://www.freecodecamp.org/news/author/nishant-kumar/] 在这篇文章中，我们将使用 Node、Express 和 MongoDB 构建一个 RESTful API。我们将为创建数据、读取数据、更新数据和删除数据（基本 CRUD 操作）创建端点（endpoints）。 但在我们开始之前，请确保你的系统中已经安装了 Node。如果没有，请到 https://nodejs.org/en/download/  下载并安装它。 让我们先做一下基本设置 在一个空文件夹中，运行以下命令： npm init 这个命令会问你各种细节，比如你的项目名称、作者、存储库等等。然后它将在该文件夹中生成一个 pa ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/build-a-restful-api-using-node-express-and-mongodb/</link>
                <guid isPermaLink="false">622f06b9e8c932065fba0076</guid>
                
                    <category>
                        <![CDATA[ RESTful API ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Node.js ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Express.js ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ luojiyin ]]>
                </dc:creator>
                <pubDate>Mon, 14 Mar 2022 09:00:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2022/03/How-to-Build-a-Weather-Application-using-React--65-.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>原文：<a href="https://www.freecodecamp.org/news/build-a-restful-api-using-node-express-and-mongodb/">How to Build a RESTful API Using Node, Express, and MongoDB</a>，作者：<a href="https://www.freecodecamp.org/news/author/nishant-kumar/">Nishant Kumar</a></p><!--kg-card-begin: markdown--><p>在这篇文章中，我们将使用 Node、Express 和 MongoDB 构建一个 RESTful API。我们将为创建数据、读取数据、更新数据和删除数据（基本 CRUD 操作）创建端点（endpoints）。</p>
<p>但在我们开始之前，请确保你的系统中已经安装了 Node。如果没有，请到 <a href="https://nodejs.org/en/download/">https://nodejs.org/en/download/</a> 下载并安装它。</p>
<h2 id="">让我们先做一下基本设置</h2>
<p>在一个空文件夹中，运行以下命令：</p>
<pre><code class="language-shell">npm init
</code></pre>
<p>这个命令会问你各种细节，比如你的项目名称、作者、存储库等等。然后它将在该文件夹中生成一个 <strong>package.json</strong> 文件。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/02/Screenshot-2022-02-19-130254.jpeg" alt="Screenshot-2022-02-19-130254" width="600" height="400" loading="lazy"></p>
<pre><code class="language-json">{
  "name": "rest-api-express-mongo",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" &amp;&amp; exit 1"
  },
  "author": "",
  "license": "ISC"
}
</code></pre>
<p>Package.json file</p>
<p>这个 Package.json 文件将包含所有的脚本，如如何运行应用程序，或如何测试应用程序，以及所有的依赖。</p>
<p>我们现在需要安装一些依赖项。</p>
<pre><code class="language-shell">npm i express mongoose nodemon dotenv
</code></pre>
<p>这里，</p>
<ol>
<li>Express 将被用于中间件，以创建各种 CRUD 端点</li>
<li>Mongoose 用于使用各种查询来管理 MongoDB 中的数据</li>
<li>Nodemon 用于在我们每次保存文件时重新启动我们的服务器</li>
<li>Dotenv 管理 <strong>.env</strong> 文件</li>
</ol>
<p>因此，请继续安装它们。</p>
<p>在它们完成安装后，创建一个名为 <strong>index.js.</strong> 的文件，这将是我们应用程序的入口。</p>
<p>而在这个文件中，让我们添加 Express 和 Mongoose，并运行该文件。</p>
<pre><code class="language-js">const express = require('express');
const mongoose = require('mongoose');
</code></pre>
<p>现在，将 Express 的内容转移到一个名为 <strong>app</strong> 的新常量中。</p>
<pre><code class="language-js">const express = require('express');
const mongoose = require('mongoose');

const app = express();
</code></pre>
<p>现在，让我们修改这个文件，在 3000 端口监听。</p>
<pre><code class="language-js">const express = require('express');
const mongoose = require('mongoose');

const app = express();

app.use(express.json());

app.listen(3000, () =&gt; {
    console.log(`Server Started at ${3000}`)
})
</code></pre>
<p>现在，服务器被设置在 <strong>端口3000</strong>。让我们写脚本来启动我们的服务器。我们还添加了 <strong>app.use</strong>。在这里面，我们有一个代码片段，允许我们接受 JSON 格式的数据。</p>
<p>在package.json文件中，添加一个脚本，内容如下：</p>
<pre><code class="language-js">"scripts": {
    "start": "nodemon index.js"
},
</code></pre>
<p>这意味着我们可以<strong>使用 npm start 启动我们的服务器</strong>，它将使用我们之前安装的 Nodemon 包运行。</p>
<p>在终端中输入 npm start，我们将在终端中看到以下输出:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/02/Screenshot-2022-02-19-132326.jpeg" alt="Screenshot-2022-02-19-132326" width="600" height="400" loading="lazy"></p>
<h2 id="mongodb">如何配置 MongoDB 数据库</h2>
<p>现在，让我们来配置 mongoDB 数据库。前往 <a href="https://account.mongodb.com/account/login">https://account.mongodb.com/account/login</a> 并创建你的账户，如果你已经有一个账户，则可以登录。</p>
<p>登录后，我们要创建一个数据库。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/02/Screenshot-2022-02-19-132848.jpeg" alt="Screenshot-2022-02-19-132848" width="600" height="400" loading="lazy"></p>
<p>因此，创建一个 <strong>Free Shared Cluster</strong>。</p>
<p>它将会要求你输入用户名和密码，输入它们。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/02/Screenshot-2022-02-19-132958.jpeg" alt="Screenshot-2022-02-19-132958" width="600" height="400" loading="lazy"></p>
<p>然后，添加你的 IP 地址。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/02/Screenshot-2022-02-19-133131.jpeg" alt="Screenshot-2022-02-19-133131" width="600" height="400" loading="lazy"></p>
<p>点击完成并关闭。</p>
<p>我们的集群将需要一些时间来完成，所以让我们等待吧。同时，在项目文件夹中创建一个名为 <strong>.env</strong> 的文件。</p>
<p>并在集群主页中，点击连接按钮。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/02/Screenshot-2022-02-19-133319.jpeg" alt="Screenshot-2022-02-19-133319" width="600" height="400" loading="lazy"></p>
<p>将出现以下窗口：</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/02/Screenshot-2022-02-19-133404.jpeg" alt="Screenshot-2022-02-19-133404" width="600" height="400" loading="lazy"></p>
<p>点击 MongoDB Compass，它将返回以下字符串。同时，下载并安装 MongoDB Compass。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/02/Screenshot-2022-02-19-133516.jpeg" alt="Screenshot-2022-02-19-133516" width="600" height="400" loading="lazy"></p>
<p>将你的用户名和密码添加到这个你以前使用过的字符串中。最后的连接字符串将看起来像这样:</p>
<pre><code class="language-js">mongodb+srv://nishant:********@cluster0.xduyh.mongodb.net/testDatabase
</code></pre>
<p>这里，nishant 是用户名，其次是密码，最后是数据库名称。</p>
<p>所以，把这个字符串粘贴到 <strong>.env</strong> 文件中。</p>
<pre><code class="language-js">DATABASE_URL = mongodb+srv://nishant:*******@cluster0.xduyh.mongodb.net/testDatabase
</code></pre>
<p>现在在 MongoDB Compass 中，也添加这个字符串。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/02/Screenshot-2022-02-19-134347.jpeg" alt="Screenshot-2022-02-19-134347" width="600" height="400" loading="lazy"></p>
<p>然后，点击 <code>Connect</code>。</p>
<p>在这里，我们将得到两个数据库，这是默认的。第三个将在以后自动创建。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/02/Screenshot-2022-02-19-134435.jpeg" alt="Screenshot-2022-02-19-134435" width="600" height="400" loading="lazy"></p>
<p>现在，让我们在脚本文件 index.js 中导入我们的 <strong>.env</strong> 文件的内容。</p>
<pre><code class="language-js">require('dotenv').config();

const mongoString = process.env.DATABASE_URL
</code></pre>
<p>在这里，我们将字符串存储到一个名为 <strong>mongoString.</strong> 的变量中。</p>
<p>现在，让我们使用 Mongoose 将数据库连接到我们的服务器。</p>
<pre><code class="language-js">mongoose.connect(mongoString);
const database = mongoose.connection
</code></pre>
<p>现在，我们必须根据我们的数据库连接是成功还是失败，抛出一个成功或错误信息。</p>
<pre><code class="language-js">database.on('error', (error) =&gt; {
    console.log(error)
})

database.once('connected', () =&gt; {
    console.log('Database Connected');
})
</code></pre>
<p>这里，<strong>database.on</strong> 意味着它将连接到数据库，如果连接失败，将抛出任何错误。而 <strong>database.once</strong> 意味着它将只运行一次。如果它成功了，它将显示一条信息：数据库已连接。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/02/Screenshot-2022-02-19-135414-1.jpeg" alt="Screenshot-2022-02-19-135414-1" width="600" height="400" loading="lazy"></p>
<p>以下是到此为止的全部代码：</p>
<pre><code class="language-js">require('dotenv').config();

const express = require('express');
const mongoose = require('mongoose');
const mongoString = process.env.DATABASE_URL;

mongoose.connect(mongoString);
const database = mongoose.connection;

database.on('error', (error) =&gt; {
    console.log(error)
})

database.once('connected', () =&gt; {
    console.log('Database Connected');
})
const app = express();

app.use(express.json());

app.listen(3000, () =&gt; {
    console.log(`Server Started at ${3000}`)
})
</code></pre>
<h2 id="endpoints">如何为端点（Endpoints）创建我们的路由</h2>
<p>创建一个名为 routes 的文件夹，并在里面制作一个名为 routes.js 的文件。</p>
<p>将此文件导入我们的主脚本文件 index.js 中。</p>
<pre><code class="language-js">const routes = require('./routes/routes');
</code></pre>
<p>另外，让我们使用这个路由文件（routes.js）。</p>
<pre><code class="language-js">const routes = require('./routes/routes');

app.use('/api', routes)
</code></pre>
<p>这里，这个 app.use 需要两样东西。一个是基础端点（base endpoint），另一个是路由的内容。现在，我们所有的端点将从'/api'开始。</p>
<p>我们会得到一个错误，因为我们在路由文件里面没有任何东西。所以，让我们添加它们。</p>
<pre><code class="language-js">const express = require('express');

const router = express.Router()

module.exports = router;
</code></pre>
<p>在这里，我们使用 Express 中的 Router，并且我们也使用 module.exports 导出了它。现在，我们的应用程序可以正常工作了。</p>
<h2 id="endpoints">如何编写我们的端点（Endpoints）</h2>
<p>现在，让我们在这个路由文件中写入我们的端点。我们将有五条路由用于以下 actions：</p>
<ol>
<li>将数据发布到数据库</li>
<li>从数据库中获取所有数据</li>
<li>获取基于 ID 的数据</li>
<li>基于 ID 更新数据</li>
<li>根据 ID 删除数据</li>
</ol>
<p>因此，让我们为这些 actions 创建路由：</p>
<pre><code class="language-js">//Post Method
router.post('/post', (req, res) =&gt; {
    res.send('Post API')
})

//Get all Method
router.get('/getAll', (req, res) =&gt; {
    res.send('Get All API')
})

//Get by ID Method
router.get('/getOne/:id', (req, res) =&gt; {
    res.send('Get by ID API')
})

//Update by ID Method
router.patch('/update/:id', (req, res) =&gt; {
    res.send('Update by ID API')
})

//Delete by ID Method
router.delete('/delete/:id', (req, res) =&gt; {
    res.send('Delete by ID API')
})
</code></pre>
<p>我们有五个方法，使用 REST 方法的 Post、Get、Patch 和 Delete。</p>
<p>这个路由器把路由作为第一个参数。然后，在第二个参数中，它正在接受一个回调。</p>
<p>在回调中，我们有一个 res 和一个 req。res 表示响应，req 表示请求。我们使用 res 来向我们的客户端，如 Postman，或任何前端客户端发送响应。而我们使用 req 来接收来自客户端应用程序（如 Postman）或任何前端客户端的请求。</p>
<p>然后在回调 body 中，我们要打印一条消息，说明是响应 API 消息。</p>
<p>保存这个，然后打开 Postman 来检查端点（endpoints）。如果你没有，请下载 <a href="https://www.postman.com/downloads/">Postman</a>。它是测试 API 端点的一个好工具。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/02/Screenshot-2022-02-19-141237.jpeg" alt="Screenshot-2022-02-19-141237" width="600" height="400" loading="lazy"></p>
<p>在地址栏中添加这个地址，然后点击发送，或按回车键。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/02/Screenshot-2022-02-19-141328.jpeg" alt="Screenshot-2022-02-19-141328" width="600" height="400" loading="lazy"></p>
<p>我们将在 Postman 的正文中得到这个消息，因为我们只是使用 res.send. 发送一个消息。</p>
<p>现在，让我们从一个客户应用中获取一个响应。让我们简单地打印一个 ID。</p>
<p>我们必须首先改变 <strong>getOne</strong> 函数。我们使用 <strong>req.params.id</strong> 获取 ID，然后使用 <strong>res.send.</strong> 将其发送到客户端应用程序。</p>
<pre><code class="language-js">//Get by ID Method
router.get('/getOne/:id', (req, res) =&gt; {
    res.send(req.params.id)
})
</code></pre>
<pre><code class="language-js">localhost:3000/api/getOne/1000
</code></pre>
<p>在地址栏中添加这个端点（endpoint）。这里，我们使用 <strong>getOne</strong> 端点，后面是 ID。然后，点击发送。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/02/Screenshot-2022-02-19-142619.jpeg" alt="Screenshot-2022-02-19-142619" width="600" height="400" loading="lazy"></p>
<p>我们将在 Postman 的响应 body 中获得 ID。</p>
<h2 id="">如何创建模型</h2>
<p>现在，让我们创建一个模型，它将定义我们的数据库结构。</p>
<p>创建一个名为 model 的文件夹，里面有一个名为 model.js 的文件。</p>
<pre><code class="language-js">const mongoose = require('mongoose');

const dataSchema = new mongoose.Schema({
    name: {
        required: true,
        type: String
    },
    age: {
        required: true,
        type: Number
    }
})

module.exports = mongoose.model('Data', dataSchema)
</code></pre>
<p>在这里，我们有一个定义数据库结构的模式，它有一个 <strong>name</strong> 和一个 <strong>age</strong> 属性。这两个字段都有类型，而且都是必填的。</p>
<p>然后，我们简单地导出模式模型。</p>
<p>现在，在 <strong>routes.js</strong> 文件中导入这个模型。</p>
<pre><code class="language-js">const Model = require('../models/model');
</code></pre>
<h2 id="post">如何向数据库 post 数据</h2>
<p>让我们使用刚刚创建的模型创建要发布（post）的数据体。</p>
<pre><code class="language-js">router.post('/post', (req, res) =&gt; {
    const data = new Model({
        name: req.body.name,
        age: req.body.age
    })
})
</code></pre>
<p>我们的 name 和 agg 是接受来自 <strong>req body</strong> 的 name 和 age。我们从客户端应用如<strong>Postman</strong>，或任何前端客户端如 <strong>React</strong> 或 <strong>Angular.</strong> 获得这些数据。</p>
<p>我们还将创建一个<strong>try-catch</strong>块来处理成功信息和错误。</p>
<pre><code class="language-js">//Post Method
router.post('/post', (req, res) =&gt; {
    const data = new Model({
        name: req.body.name,
        age: req.body.age
    })

    try{

    }
    catch(error){
        
    }
})
</code></pre>
<p>在尝试块中，我们使用 <strong>data.save()</strong> 来保存 <strong>data</strong>。然后，我们将数据存储在一个叫做 <strong>dataToSave</strong> 的常量中。</p>
<p>我们还将成功的消息与数据一起发送到响应体中（response body）。</p>
<p>在 catch 块中，我们将接收任何错误，如果我们得到任何错误。</p>
<pre><code class="language-js">//Post Method
router.post('/post', (req, res) =&gt; {
    const data = new Model({
        name: req.body.name,
        age: req.body.age
    })

    try {
        const dataToSave = data.save();
        res.status(200).json(dataToSave)
    }
    catch (error) {
        res.status(400).json({message: error.message})
    }
})
</code></pre>
<p>现在，让我们从 Postman 添加一些数据。但在这之前，这个函数需要异步工作。所以，我们将使用 async-await。</p>
<pre><code class="language-js">router.post('/post', async (req, res) =&gt; {
    const data = new Model({
        name: req.body.name,
        age: req.body.age
    })

    try {
        const dataToSave = await data.save();
        res.status(200).json(dataToSave)
    }
    catch (error) {
        res.status(400).json({message: error.message})
    }
})
</code></pre>
<p>如果我们在正文中添加数据并点击发送，我们将得到以下结果：</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/02/Screenshot-2022-02-19-145714.jpeg" alt="Screenshot-2022-02-19-145714" width="600" height="400" loading="lazy"></p>
<p>它也在生成一个唯一的 ID。打开 MongoDB Compass 应用程序，你会看到数据库和你刚刚创建的这条记录。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/02/Screenshot-2022-02-19-150007.jpeg" alt="Screenshot-2022-02-19-150007" width="600" height="400" loading="lazy"></p>
<h2 id="">如何获得所有数据</h2>
<p>获取数据也很简单，只需几行代码：</p>
<pre><code class="language-js">router.get('/getAll', async (req, res) =&gt; {
    try{
        const data = await Model.find();
        res.json(data)
    }
    catch(error){
        res.status(500).json({message: error.message})
    }
})
</code></pre>
<p>这里，我们使用 <strong>Model.find</strong> 方法从数据库中获取所有数据。然后，我们将其以 JSON 格式返回。如果我们有一个错误，我们也会得到它。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/02/Screenshot-2022-02-19-150423.jpeg" alt="Screenshot-2022-02-19-150423" width="600" height="400" loading="lazy"></p>
<p>如果我们在 Postman 中调用这个端点（endpoint），我们将在 Postman 主体中得到一个对象的数组。</p>
<h2 id="id">如何根据 ID 获取数据</h2>
<p>这个方法也很简单。我们只需要在一个叫做 <strong>findById</strong> 的方法中传递文档的 ID，也就是 <strong>req.params.id</strong>。</p>
<pre><code class="language-js">//Get by ID Method
router.get('/getOne/:id', async (req, res) =&gt; {
    try{
        const data = await Model.findById(req.params.id);
        res.json(data)
    }
    catch(error){
        res.status(500).json({message: error.message})
    }
})
</code></pre>
<p>如果我们点击发送，我们将根据 ID 获得数据。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/02/Screenshot-2022-02-19-150808.jpeg" alt="Screenshot-2022-02-19-150808" width="600" height="400" loading="lazy"></p>
<h2 id="id">如何根据 ID 来更新和删除数据</h2>
<p>首先，让我们使用 <strong>补丁（patch）</strong> 方法来针对更新方法。</p>
<pre><code class="language-js">//Update by ID Method
router.patch('/update/:id', async (req, res) =&gt; {
    try {
        const id = req.params.id;
        const updatedData = req.body;
        const options = { new: true };

        const result = await Model.findByIdAndUpdate(
            id, updatedData, options
        )

        res.send(result)
    }
    catch (error) {
        res.status(400).json({ message: error.message })
    }
})
</code></pre>
<p>在这里，我们有三个参数传递给 <strong>findByIdAndUpdate</strong> 方法，我们用它来通过 ID 找到一个文档并更新它。</p>
<p>其中 <strong>req.params.id</strong> 是常量 id，<strong>updatedData</strong> 包含 req.body，还有 <strong>options</strong>，它指定了是否在 body 中返回更新的数据。</p>
<p>现在我们来测试一下。只要粘贴一个特定文件的 ID，然后点击发送，也要改变端点（endpoints）。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/02/Screenshot-2022-02-19-152717.jpeg" alt="Screenshot-2022-02-19-152717" width="600" height="400" loading="lazy"></p>
<p>我们正在使用一个 ID 进行更新，而且它正在被更新。</p>
<p>删除也很简单，让我们来实现它：</p>
<pre><code class="language-js">//Delete by ID Method
router.delete('/delete/:id', async (req, res) =&gt; {
    try {
        const id = req.params.id;
        const data = await Model.findByIdAndDelete(id)
        res.send(`Document with ${data.name} has been deleted..`)
    }
    catch (error) {
        res.status(400).json({ message: error.message })
    }
})
</code></pre>
<p>我们在这里获取 ID，然后使用 Model.findByIdAndDelete 来删除该字段，同时传递 ID。</p>
<p>我们将更新的数据存储在一个常量 <strong>data</strong> 中。</p>
<p>在响应中，我们将得到这样的消息：具有特定名称的文档已经被删除。</p>
<p>如果我们测试一下，我们会得到以下结果：</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/02/Screenshot-2022-02-19-153557.jpeg" alt="Screenshot-2022-02-19-153557" width="600" height="400" loading="lazy"></p>
<p>所以，所有五个方法都完成了。我们可以发布数据和获取所有的数据（也基于 ID）。我们还可以更新它们和删除它们。</p>
<h3 id="">谢谢你阅读本文</h3>
<p>在这篇文章中，你了解了如何使用 Node、Express 和 MongoDB 设计和开发一个 RESTful API。</p>
<p>现在你可以使用这些端点来构建一个全栈应用程序，使用 Vanilla JavaScript、React、Angular、Next 或 Vue.js。</p>
<p>你也可以看看我关于同一主题的视频，<a href="https://youtu.be/paxagc55loU">RESTful APIs - 使用 Node、Express 和 MongoDB 构建 RESTful API</a></p>
<p>欢迎从 <a href="https://github.com/nishant-666/Rest-Api-Express-MongoDB">GitHub</a> 下载代码并进行实验。</p>
<blockquote>
<p>祝你学习愉快！</p>
</blockquote>
<!--kg-card-end: markdown--> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Node.js Async/Await 教程——附异步 JavaScript 示例 ]]>
                </title>
                <description>
                    <![CDATA[ 当你第一次学习JavaScript时，最难理解的概念之一是该语言的异步处理（asynchronous processing）模式。对于我们大多数人来说，学习异步编程看起来差不多是这样的： 如果你第一次使用async时不是这样，请认为自己是个天才。 尽管它很难学，但如果你想使用JavaScript和Node.js构建网络应用程序和服务器，异步编程是至关重要的，因为JS代码默认是异步的。 异步编程基础知识 那么，究竟什么是异步处理模型，或“非阻塞I/O”模型（如果你是Node.js用户，你可能已经听说过）？ 这里有一个很长的，请不要看的描述：在异步处理模型中，当你的应用程序引擎与外部各方（如文件系统或网络）进行交互时，它不会等到从这些各方得到一个结果，才做下一步。相反，它继续进行后续的任务，只有在得到结果的信号后才回到之前的外部各方进行处理。 为了理解Node.js的默认异步处理模型，我们来看看一个假设的圣诞老人工作室。在任何工作开始之前，圣诞老人将不得不阅读来自世界各地的孩子们的每一封可爱的信。 然后，他将弄清所要求的礼物，将物品名称翻译成精灵语 [https://e ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/node-js-async-await-tutorial-with-asynchronous-javascript-examples/</link>
                <guid isPermaLink="false">658bf641576bb403fc79125f</guid>
                
                    <category>
                        <![CDATA[ Node.js ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ luojiyin ]]>
                </dc:creator>
                <pubDate>Fri, 25 Feb 2022 10:13:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2023/12/5f9c9a39740569d1a4ca2455.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>原文：</strong> <a href="https://www.freecodecamp.org/news/node-js-async-await-tutorial-with-asynchronous-javascript-examples/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">Node.js Async Await Tutorial – With Asynchronous JavaScript Examples</a>
      </p><!--kg-card-begin: markdown--><p>当你第一次学习JavaScript时，最难理解的概念之一是该语言的异步处理（asynchronous processing）模式。对于我们大多数人来说，学习异步编程看起来差不多是这样的：</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/05/async.png" alt="async" width="600" height="400" loading="lazy"></p>
<p>如果你第一次使用async时不是这样，请认为自己是个天才。</p>
<p>尽管它很难学，但如果你想使用JavaScript和Node.js构建网络应用程序和服务器，异步编程是至关重要的，因为JS代码<strong>默认是异步的</strong>。</p>
<h2 id="">异步编程基础知识</h2>
<p>那么，究竟什么是异步处理模型，或“非阻塞I/O”模型（如果你是Node.js用户，你可能已经听说过）？</p>
<p>这里有一个很长的，请不要看的描述：在异步处理模型中，当你的应用程序引擎与外部各方（如文件系统或网络）进行交互时，它不会等到从这些各方得到一个结果，才做下一步。相反，它继续进行后续的任务，只有在得到结果的信号后才回到之前的外部各方进行处理。</p>
<p>为了理解Node.js的默认异步处理模型，我们来看看一个假设的圣诞老人工作室。在任何工作开始之前，圣诞老人将不得不阅读来自世界各地的孩子们的每一封可爱的信。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/05/santa-01.png" alt="Santa reading letter for workshop" width="600" height="400" loading="lazy"></p>
<p>然后，他将弄清所要求的礼物，将物品名称翻译成<a href="https://en.wikipedia.org/wiki/Elvish_languages">精灵语</a>，然后将指令传递给我们每个勤劳的精灵，他们有不同的专长：红色的擅长木制玩具，蓝色的擅长填充玩具，绿色的精通做机器人玩具。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/05/santa-02.png" alt="Santa passing instruction to Red" width="600" height="400" loading="lazy"></p>
<p>今年，由于<a href="https://en.wikipedia.org/wiki/COVID-19_pandemic">COVID-19大流行</a>，只有一半的圣诞老人的精灵可以到他的车间来帮忙。不过，因为他很聪明，圣诞老人决定不等每个精灵准备完礼物（也就是同步工作），而是继续翻译和传递他那堆信中的指示。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/05/santa-03.png" alt="Santa passing instruction to Blue" width="600" height="400" loading="lazy"></p>
<p>诸如此类......</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/05/santa-04.png" alt="Santa continue passing out instructions" width="600" height="400" loading="lazy"></p>
<p>当他正准备读另一封信时，红色精灵通知圣诞老人，他已经完成了准备好了第一份礼物。然后，圣诞老人从红色精灵手中接过礼物，并把它放在一边。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/05/santa-05.png" alt="Santa receiving Red's present" width="600" height="400" loading="lazy"></p>
<p>然后他继续翻译和传递下一封信。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/05/santa-06.png" alt="Santa passing instruction to Green" width="600" height="400" loading="lazy"></p>
<p>由于他只需要包装一个预先制作好的飞行机器人，绿精灵可以迅速完成准备工作并将礼物交给圣诞老人。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/05/santa-07.png" alt="Santa receiving Green's present" width="600" height="400" loading="lazy"></p>
<p>经过一整天艰苦的异步工作，圣诞老人和精灵们设法完成了所有的礼物准备工作。由于他改进了异步工作模式，尽管受到大流行病的重创，圣诞老人的工作室还是在创纪录的时间内完成了工作。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/05/santa-08.png" alt="Santa's gotten all the presents" width="600" height="400" loading="lazy"></p>
<p>这就是异步或非阻塞I/O处理模型的基本思想。现在让我们看看它在Node.js中是如何具体完成的。</p>
<h2 id="nodejs">Node.js的事件循环</h2>
<p>你可能听说过Node.js是单线程的。然而，确切地说，只有Node.js中的事件循环是单线程的，它与后台C++工作线程池交互。Node.js的处理模式有四个重要组成部分:</p>
<ul>
<li>事件队列（Event Queue）: 在程序中声明的任务，或通过<a href="https://nodejs.org/en/knowledge/getting-started/control-flow/what-are-callbacks/">回调 callbacks</a>从处理线程池中返回。（在我们的圣诞老人工作室里，相当于给圣诞老人的那堆信）</li>
<li>事件循环（Event Loop）: Node.js的主线程，促进事件队列和工人线程池进行操作--包括异步和同步。（这里是圣诞老人 🎅）</li>
<li>（后台线程池）Background thread pool: 这些线程做任务的实际处理，这可能是I/O阻塞（例如调用和等待外部API的响应）。（这些是我们车间里勤奋的精灵 🧝🧝‍♀️🧝‍♂️。）</li>
</ul>
<p>你可以将这种处理模式可视化，如下所示：</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/05/processing-model.png" alt="processing-model" width="600" height="400" loading="lazy"></p>
<p>图片来源：c-sharpcorner.com</p>
<p>让我们看看一个实际的代码片段，看看这些代码的作用：</p>
<pre><code class="language-js">console.log("Hello");
https.get("https://httpstat.us/200", (res) =&gt; {
  console.log(`API returned status: ${res.statusCode}`);
});
console.log("from the other side");
</code></pre>
<p>如果我们执行上面这段代码，我们会在标准输出中得到这个结果：</p>
<pre><code class="language-bash">Hello
from the other side
API returned status: 200
</code></pre>
<p>那么Node.js引擎是如何执行上述代码片段的呢？它从调用栈中的三个函数开始：</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/05/execution-01-1.png" alt="Processing starts with 3 functions in the call stack" width="600" height="400" loading="lazy"></p>
<p>然后，“Hello” 被打印到控制台，相应的函数调用从堆栈中删除。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/05/execution-02-1.png" alt="Hello console log removed from stack" width="600" height="400" loading="lazy"></p>
<p>然后，对<code>https.get</code>的函数调用（即对相应的URL进行获取请求）被执行，并被委托给工人线程池，并附加一个回调。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/05/execution-03.png" alt="https.get delegated to worker pool" width="600" height="400" loading="lazy"></p>
<p>对<code>console.log</code>的下一个函数调用被执行，<code>from the other side</code>被打印到控制台。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/05/execution-04.png" alt="Next console.log get executed" width="600" height="400" loading="lazy"></p>
<p>现在网络调用已经返回了一个响应，然后回调函数的调用将进入回调队列（callback queue）中。请注意，这一步可能发生在紧接着的上一步之前（即 <code>从另一边</code>得到打印），尽管通常情况下不是这样的。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/05/execution-05.png" alt="Network call completes and callback queued" width="600" height="400" loading="lazy"></p>
<p>然后回调被放在我们的调用栈（call stack）中：</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/05/execution-06.png" alt="Callback put inside call stack" width="600" height="400" loading="lazy"></p>
<p>然后我们会在控制台（console）看到 “API返回状态：200”，像这样：</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/05/execution-07.png" alt="Status code printed out" width="600" height="400" loading="lazy"></p>
<p>通过促进回调队列（callback queue）和调用栈（call stack），Node.js中的事件循环以异步方式有效地执行我们的JavaScript代码。</p>
<h2 id="javascriptnodejsasyncawait">JavaScript和Node.js的同步编程的发展历史 async/await</h2>
<p>现在你对异步执行和Node.js事件循环的内部工作有了很好的理解，让我们深入了解JavaScript中的async/await。我们将看看它是如何工作的，从最初的回调驱动（callback-driven）的实现到最新闪目耀人的async/await关键字。</p>
<h3 id="javascriptcallbacks">JavaScript中的回调（Callbacks）</h3>
<p>OG处理JavaScript引擎的异步性的方法是通过回调。回调基本上是在同步或I/O阻塞操作结束后执行的函数。</p>
<p>这种模式的一个直接例子是内置的<code>setTimeout</code>函数，它将在执行回调之前等待一定数量的毫秒。</p>
<pre><code class="language-js">setTimeout(2000, () =&gt; {
  console.log("Hello");
});
</code></pre>
<p>虽然将回调加到阻塞操作上很方便，但这种模式也带来了一些问题：</p>
<ul>
<li>回调地狱（Callback hell）</li>
<li>控制反转（不是好的那种！）。</li>
</ul>
<h4 id="">什么是回调地狱？</h4>
<p>让我们再看看圣诞老人和他的精灵们的例子。为了准备一份礼物，圣诞老人的工作室必须进行几个不同的步骤（每个步骤都要用<code>setTimeout</code>模拟不同的时间）：</p>
<pre><code class="language-js">function translateLetter(letter, callback) {
  return setTimeout(2000, () =&gt; {
    callback(letter.split("").reverse().join(""));
  });
}
function assembleToy(instruction, callback) {
  return setTimeout(3000, () =&gt; {
    const toy = instruction.split("").reverse().join("");
    if (toy.includes("wooden")) {
      return callback(`polished ${toy}`);
    } else if (toy.includes("stuffed")) {
      return callback(`colorful ${toy}`);
    } else if (toy.includes("robotic")) {
      return callback(`flying ${toy}`);
    }
    callback(toy);
  });
}
function wrapPresent(toy, callback) {
  return setTimeout(1000, () =&gt; {
    callback(`wrapped ${toy}`);
  });
}
</code></pre>
<p>这些步骤需要按照特定的顺序进行：</p>
<pre><code class="language-js">translateLetter("wooden truck", (instruction) =&gt; {
  assembleToy(instruction, (toy) =&gt; {
    wrapPresent(toy, console.log);
  });
});
// 结果是“wrapped polished wooden truck”
</code></pre>
<p>由于我们这样做，在这个过程中增加更多的步骤将意味着把内部的回调推到右边，并最终进入回调地狱，如图所示：</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/05/callback-hell.jpeg" alt="Callback Hell" width="600" height="400" loading="lazy"></p>
<p>回调看起来是有顺序的，但有时执行的顺序并不遵循你屏幕上显示的内容。有了多层嵌套的回调，你很容易失去对整个程序流程的大局观，产生更多的错误，或者只是在写代码时变得更慢。</p>
<p>那么你如何解决这个问题呢？简单地将嵌套的回调模块化为命名的函数，你将有一个很好的左对齐的程序，很容易阅读。</p>
<pre><code class="language-js">function assembleCb(toy) {
  wrapPresent(toy, console.log);
}
function translateCb(instruction) {
  assembleToy(instruction, assembleCb);
}
translateLetter("wooden truck", translateCb);
</code></pre>
<h4 id="inversionofcontrol">控制反转（Inversion of Control）</h4>
<p>回调模式的另一个问题是，你并没有决定高阶函数如何执行你的回调。他们可能在函数的结尾处执行，这是传统的做法，但他们也可能在函数的开始处执行，或者多次执行。</p>
<p>基本上，你只能任由依赖关系所有者摆布，而且你可能永远不知道他们什么时候会破坏你的代码。</p>
<p>为了解决这个问题，作为一个依赖性用户，你能做的并不多。然而，如果你自己曾经是一个依赖关系的所有者，请一定要：</p>
<ul>
<li>坚持传统的回调签名，将错误作为第一个参数</li>
<li>只在高阶函数的末尾执行一次回调</li>
<li>记录绝对需要的任何不合常规的东西，并始终以向后兼容为目标</li>
</ul>
<h3 id="javascriptpromises">JavaScript中的Promises</h3>
<p><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise">Promises</a> 是为了解决上述回调的问题而创建的。Promises确保了JavaScript用户：</p>
<ul>
<li>坚持用他们的签名<code>resolve</code>和<code>reject</code>函数进行特定的约定。</li>
<li>将回调函数链接到一个排列整齐的、自上而下的流程。</li>
</ul>
<p>我们之前关于圣诞老人工作室准备礼物的例子可以用这样的promises来重写：</p>
<pre><code class="language-js">function translateLetter(letter) {
  return new Promise((resolve, reject) =&gt; {
    setTimeout(2000, () =&gt; {
      resolve(letter.split("").reverse().join(""));
    });
  });
}
function assembleToy(instruction) {
  return new Promise((resolve, reject) =&gt; {
    setTimeout(3000, () =&gt; {
      const toy = instruction.split("").reverse().join("");
      if (toy.includes("wooden")) {
        return resolve(`polished ${toy}`);
      } else if (toy.includes("stuffed")) {
        return resolve(`colorful ${toy}`);
      } else if (toy.includes("robotic")) {
        return resolve(`flying ${toy}`);
      }
      resolve(toy);
    });
  });
}
function wrapPresent(toy) {
  return new Promise((resolve, reject) =&gt; {
    setTimeout(1000, () =&gt; {
      resolve(`wrapped ${toy}`);
    });
  });
}
</code></pre>
<p>顺利地进行了一系列的步骤：</p>
<pre><code class="language-js">translateLetter("wooden truck")
  .then((instruction) =&gt; {
    return assembleToy(instruction);
  })
  .then((toy) =&gt; {
    return wrapPresent(toy);
  })
  .then(console.log);
// This would produce the exact same present: wrapped polished wooden truck
</code></pre>
<p>然而，promises 也不是没有问题的。我们链条中每个节点的数据都有不同的作用域，只能访问从紧邻的前一个步骤或父作用域传递过来的数据。</p>
<p>例如，我们的礼物包装步骤可能想使用翻译步骤的数据：</p>
<pre><code class="language-js">function wrapPresent(toy, instruction) {
  return Promise((resolve, reject) =&gt; {
    setTimeout(1000, () =&gt; {
      resolve(`wrapped ${toy} with instruction: "${instruction}`);
    });
  });
}
</code></pre>
<p>这倒是<a href="https://livebook.manning.com/book/c-plus-plus-concurrency-in-action/chapter-3/1">一个典型的 "内存共享 "的线程问题</a>。为了解决这个问题，我们应该使用<code>Promise.all</code>和<a href="https://blog.golang.org/codelab-share">"通过通信共享数据，而不是通过共享数据进行通信"</a>，而不是使用父级范围内的变量。</p>
<pre><code class="language-js">translateLetter("wooden truck")
  .then((instruction) =&gt; {
    return Promise.all([assembleToy(instruction), instruction]);
  })
  .then((toy, instruction) =&gt; {
    return wrapPresent(toy, instruction);
  })
  .then(console.log);
// This would produce the present: wrapped polished wooden truck with instruction: "kcurt nedoow"
</code></pre>
<h3 id="javascriptasyncawait">JavaScript中的Async/Await</h3>
<p>最后但绝对不是最不重要的，最靓的仔是async/await。它非常容易使用，但也有一些风险。</p>
<p>Async/await解决了Promise的内存共享问题，把所有的东西都放在同一个范围内。我们之前的例子可以很容易地改写成这样：</p>
<pre><code class="language-js">(async function main() {
  const instruction = await translateLetter("wooden truck");
  const toy = await assembleToy(instruction);
  const present = await wrapPresent(toy, instruction);
  console.log(present);
})();
// This would produce the present: wrapped polished wooden truck with instruction: "kcurt nedoow"
</code></pre>
<p>然而，尽管用async/await写异步代码很容易，但也很容易犯错误，造成性能漏洞。</p>
<p>现在让我们把我们的例子圣诞老人工作室的场景本地化，以包装礼物并把它们装到雪橇上。</p>
<pre><code class="language-js">function wrapPresent(toy) {
  return Promise((resolve, reject) =&gt; {
    setTimeout(5000 * Math.random(), () =&gt; {
      resolve(`wrapped ${toy}`);
    });
  });
}
function loadPresents(presents) {
  return Promise((resolve, reject) =&gt; {
    setTimeout(5000, () =&gt; {
      let itemList = "";
      for (let i = 0; i &lt; presents.length; i++) {
        itemList += `${i}. ${presents[i]}\n`;
      }
    });
  });
}
</code></pre>
<p>你可能犯的一个常见错误是这样执行步骤：</p>
<pre><code class="language-js">(async function main() {
  const presents = [];
  presents.push(await wrapPresent("wooden truck"));
  presents.push(await wrapPresent("flying robot"));
  presents.push(await wrapPresent("stuffed elephant"));
  const itemList = await loadPresents(presents);
  console.log(itemList);
})();
</code></pre>
<p>但是，圣诞老人是否需要 <code>等待</code>每件礼物被逐一包装好后再装车？当然不需要。礼物应该是同时包装的。你可能会经常犯这个错误，因为你很容易写出<code>await</code>，而没有考虑到这个关键字的阻塞性（blocking nature）。</p>
<p>为了解决这个问题，我们应该把礼物包装的步骤捆绑在一起，一次性执行：</p>
<pre><code class="language-js">(async function main() {
  const presents = await Promise.all([
    wrapPresent("wooden truck"),
    wrapPresent("flying robot"),
    wrapPresent("stuffed elephant"),
  ]);
  const itemList = await loadPresents(presents);
  console.log(itemList);
})();
</code></pre>
<p>以下是一些建议的步骤，以解决你的Node.js代码中的并发性能问题：</p>
<ul>
<li>在你的代码中找出有多个连续等待的hotspots</li>
<li>检查它们是否相互依赖（即一个函数使用另一个函数返回的数据）</li>
<li>用<code>Promise.all</code>使独立的函数调用同时进行</li>
</ul>
<h2 id="">打包（文章，而不是圣诞礼物😂）</h2>
<p>祝贺你读到了本文的结尾，我已经尽力让这篇文章变得更短。这篇文章，但JavaScript中的异步话题实在是太广泛了。</p>
<p>下面是一些关键的收获：</p>
<ul>
<li>将你的JavaScript回调模块化以避免回调地狱</li>
<li>坚持使用<a href="https://gist.github.com/sunnycmf/b2ad4f80a3b627f04ff2">JS回调的惯例</a></li>
<li>在使用promises的时候，通过<code>Promise.all</code>来共享数据</li>
<li>要注意async/await代码的性能影响</li>
</ul>
<p>We ❤️ JavaScript :)</p>
<h2 id="">感谢阅读</h2>
<p>最后，如果你喜欢我的文章，请到<a href="https://blog.stanleynguyen.me/">我的博客</a>了解类似的文章，并关注<a href="https://twitter.com/stanley_ngn">我的推特</a>。🎉</p>
<!--kg-card-end: markdown--> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 如何在 Ubuntu 上安装 Node.js 并将 npm 更新到最新版本 ]]>
                </title>
                <description>
                    <![CDATA[ 英文：How to Install Node.js on Ubuntu and Update npm to the Latest Version [https://www.freecodecamp.org/news/how-to-install-node-js-on-ubuntu-and-update-npm-to-the-latest-version/] ，作者：Adebola Adeniran [https://www.freecodecamp.org/news/author/adebola/] 如果你尝试使用 apt-package manager 安装最新版本的 Node，你最终会得到 v10.19.0。这是 Ubuntu 应用商店中的最新版本，但不是 NodeJS 的最新发布版本。 这是因为当一个软件的新版本发布时，Ubuntu 团队可能需要几个月的时间才能在官方 Ubuntu 商店中进行测试和发布。因此，要获得任何软件的最新版本，我们可能不得不使用开发人员发布的私有包。 在本教程中，我们要做的是获取 v12.18.1（LTS - 具有长期支持）或 v14.4  的 N ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/how-to-install-node-js-on-ubuntu-and-update-npm-to-the-latest-version/</link>
                <guid isPermaLink="false">62affe8115ee740853c33fe4</guid>
                
                    <category>
                        <![CDATA[ Node.js ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Miya Liu ]]>
                </dc:creator>
                <pubDate>Tue, 01 Feb 2022 04:30:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2022/06/Slice-3-2-.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>英文：<a href="https://www.freecodecamp.org/news/how-to-install-node-js-on-ubuntu-and-update-npm-to-the-latest-version/">How to Install Node.js on Ubuntu and Update npm to the Latest Version</a>，作者：<a href="https://www.freecodecamp.org/news/author/adebola/">Adebola Adeniran</a></p><p>如果你尝试使用 apt-package manager 安装最新版本的 Node，你最终会得到 <strong>v10.19.0</strong>。这是 Ubuntu 应用商店中的最新版本，但不是 NodeJS 的最新发布版本。</p><p>这是因为当一个软件的新版本发布时，Ubuntu 团队可能需要几个月的时间才能在官方 Ubuntu 商店中进行测试和发布。因此，要获得任何软件的最新版本，我们可能不得不使用开发人员发布的私有包。</p><p>在本教程中，我们要做的是获取 <strong>v12.18.1</strong>（LTS - 具有长期支持）或 <strong>v14.4</strong> 的 Node。要获取最新版本，我们可以使用 <strong>nodesource</strong> 或 <strong>nvm</strong>（Node 版本管理器）。我将向你展示如何使用这两种方法。</p><p>此处的所有命令都将使用 Ubuntu CLI/终端运行。</p><h2 id="-nvm-"><strong>使用 NVM——</strong>我的首选方法</h2><p>我喜欢 nvm，因为它允许我为不同的项目使用不同的 Node 版本。</p><p>有时，你可能正在与使用不同版本 Node 的人合作项目，你需要将 Node 版本切换到项目所需的版本。为此，nvm 是最好的工具。</p><h2 id="-nvm"><strong>安装 NVM</strong></h2><p><code>curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.35.3/install.sh | bash</code></p><p>要检查 nvm 是否已安装，请键入 <code>nvm --version</code>。如果你得到一个像 <code>0.35.3</code> 这样的版本号，那么你就知道 nvm 安装成功了。</p><p><strong>重新启动终端以使更改生效。</strong></p><h2 id="-nodejs"><strong>安装 NodeJS</strong></h2><p>接下来，让我们安装 Nodejs 版本 14.4。</p><p>只需运行 <code>nvm install 14.4.0</code>。</p><p>你可以使用类似的命令来安装所需的任何版本的 node，例如 <code>nvm install 12.18.1</code>。</p><p>此命令会自动安装 nodejs 以及最新的 npm 版本，即 <code>v6.14.5</code>。</p><p>如果你需要切换 Node 版本，可以简单地运行 <code>nvm use &lt;版本号&gt;</code>，例如 <code>nvm use v12.18.1</code>。</p><p>要列出使用 nvm 安装的不同 Node 版本，请运行 <code>nvm ls</code>。</p><h2 id="-nodesource"><strong>安装 Nodesource</strong></h2><p>运行以下命令告诉 Ubuntu 我们要从 nodesource 安装 Nodejs。</p><p><code>curl -sL https://deb.nodesource.com/setup_14.x | sudo -E bash -</code></p><p><strong>注意</strong> v14.4.0 是 Node 的最新版本，但目前没有 LTS - 为其提供长期支持。要使用 LTS 安装最新版本的 Node，请将上面命令中的 <code>14</code> 更改为 <code>12</code>。</p><p>系统可能会提示你输入 root 用户的密码。输入它并按回车键。</p><h2 id="-nodejs-1"><strong>安装 NodeJS</strong></h2><p>一旦我们完成了 Nodesource 的设置，我们现在可以安装 Nodejs v14.4。<br>运行 <code>sudo apt-get install -y nodejs</code>。</p><p>完成后，我们可以检查是否安装了最新版本的 Node。只需在终端中输入 <code>nodejs -v</code>，它就会返回 <code>v14.4.0</code>。</p><p>此时你应该已经自动安装了 npm。要检查你拥有的 npm 版本，请运行 <code>npm version</code>。如果你没有获得包含最新版本的 npm 6.14.5 的对象，<code>{ npm: '6.14.5' }</code>，那么你可以通过运行以下命令手动更新 npm：</p><p><code>npm install -g npm@latest</code></p><p>如果你遇到任何由于 npm 未安装而无法更新的问题，你可以先使用 <code>sudo apt-get install -y npm</code> 安装 npm，然后运行上面的命令进行更新。</p><p>要运行某些 npm 包，我们还需要运行 <code>sudo apt install build-essential</code>。</p><p>就是这些！</p><p>你已经在你的 Ubuntu 机器上安装了最新版本的 NodeJS 和 NPM。</p><p>去构建伟大的产品吧:)</p> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 通关 Node.js 的 Event Loop 执行顺序，一篇就够！ ]]>
                </title>
                <description>
                    <![CDATA[ 先说明，本文针对的是node.js运行时，由uv实现的event loop。 所有理论依据来源于 node.js源码。（版本略） 0x00 总有面试官要刁难朕 我们不妨看一下这样的题目 console.log(1) setTimeout(() => {   console.log(2) }, 0) Promise.resolve().then(() => { 	console.log(3) }).then(() => { 	console.log(4) }) console.log(5) 请问上面代码的打印结果？ ▇▇▇▇▇▇▇▇▇▇  <--- 刮开查看答案 对吧，无数次被这种装X面试题恶心。 > 小声哔哔：谁项目里会这样写代码？ 不过恶心归恶心，不管有没有实用性，透过这些题目来弄清楚技术的真相，是没有坏处的。 我们的目标是：以后还有类似的题目，不管千变万化，直接通关。 0x01 没有银弹，还是要拿源码说话 为了证明不是胡说八道，先贴出关键源码。 // 来自 deps/uv/src/unix/core.c while (r != 0 && loop->st ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/node-js-event-loop/</link>
                <guid isPermaLink="false">5e6e4fbbca1efa04e196bbac</guid>
                
                    <category>
                        <![CDATA[ Node.js ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ 开发者小蓝 ]]>
                </dc:creator>
                <pubDate>Wed, 24 Nov 2021 09:00:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2021/04/photo-1584320427708-7931cfa241ec.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>先说明，本文针对的是<code>node.js</code>运行时，由<code>uv</code>实现的<code>event loop</code>。</p><p>所有理论依据来源于 <code>node.js</code>源码。（版本略）</p><h3 id="0x00-">0x00 总有面试官要刁难朕</h3><p>我们不妨看一下这样的题目</p><pre><code>console.log(1)

setTimeout(() =&gt; {
  console.log(2)
}, 0)

Promise.resolve().then(() =&gt; {
	console.log(3)
}).then(() =&gt; {
	console.log(4)
})

console.log(5)

请问上面代码的打印结果？
▇▇▇▇▇▇▇▇▇▇  &lt;--- 刮开查看答案
</code></pre><p>对吧，无数次被这种装X面试题恶心。</p><blockquote>小声哔哔：谁项目里会这样写代码？</blockquote><h2 id="-">不过恶心归恶心，不管有没有实用性，透过这些题目来弄清楚技术的真相，是没有坏处的。</h2><p></p><p><strong>我们的目标是：以后还有类似的题目，不管千变万化，直接通关。</strong></p><figure class="kg-card kg-image-card"><img src="https://lanhaooss.oss-cn-shenzhen.aliyuncs.com/images/WechatIMG1730.jpeg" class="kg-image" alt="WechatIMG1730" width="828" height="828" loading="lazy"></figure><h3 id="0x01-">0x01 没有银弹，还是要拿源码说话</h3><p>为了证明不是胡说八道，先贴出关键源码。</p><pre><code>// 来自 deps/uv/src/unix/core.c
while (r != 0 &amp;&amp; loop-&gt;stop_flag == 0) {
    uv__update_time(loop); 
    uv__run_timers(loop); // ⭐️ timer
    ran_pending = uv__run_pending(loop); // ⭐️ 上一个循环一些没来得及做完的事
    uv__run_idle(loop); // ⭐️ 底层用，暂时不懂
    uv__run_prepare(loop); // ⭐️ 底层用，暂时不懂

	/*
	* 忽略几行不重要的
    */
    
    uv__io_poll(loop, timeout); // ⭐️io, network or file system 等等
    uv__run_check(loop); // ⭐️ setImmediate
    uv__run_closing_handles(loop); // ⭐️ event on('close')

    if (mode == UV_RUN_ONCE) {
     // 这里不重要
    }

    r = uv__loop_alive(loop);
    if (mode == UV_RUN_ONCE || mode == UV_RUN_NOWAIT)
     // 这里不重要
  }
</code></pre><p>然后我们开始逐个去了解。</p><h2 id="timer">timer</h2><p>这部分主要是检查有没有可以执行的定时器，包括但不限于<code>setTimeout``setInterval</code>。</p><p>这里的的具体实现在<code>deps/uv/src/unix/timer.c</code>，简单说就是使用一个最小堆(小顶堆), 把时间最接近的一个取出来，判断当前时间是否可以执行。</p><h2 id="pending">pending</h2><p>这个阶段是执行 上一个循环<code>poll阶段</code>还没来得及处理的callback。</p><p>这句话，在下面介绍<code>poll阶段</code>的时候才回过头来理解。</p><h2 id="idle-prepare">idle + prepare</h2><p>按文档说是底层预留的，暂时我还没研究清楚。请忽略。</p><h2 id="poll">poll</h2><p><strong>关键！</strong>这个阶段处理的，就是我们比较熟悉的<code>network , fs</code>之类的异步操作回调。就是说你去请求一个远程的接口，那么回调函数会在<code>poll</code>阶段执行。</p><p>然后就是跟上面<code>pending</code>的关联。</p><p>由于<code>uv__io_poll</code>代码有点长就不贴了，有兴趣自己去看。</p><p>一般来说，我们的每一个阶段，都会处理完已经就绪的所有callback，如果<code>poll</code>阶段触发大量的 callback，就会占用很多的时间。</p><p>我们的<code>uv</code>当然是不会设计成这样的，所以，它会从<code>timer</code>里拿到最小的(未来最快到达的)一个定时器的时间，作为<code>poll</code>阶段的 <code>timeout</code>。</p><p>如果<code>timeout</code>到了，还有callback没开始执行的，对不起，请到<code>pending</code>队列里。</p><p>可能是<code>uv</code>认为，<code>poll</code>阶段的callback，相对来说对“准时”不太敏感，所以通过这样尽量确保<code>timer</code>的执行不会误差太多。</p><h2 id="check">check</h2><p>为什么叫做<code>check</code>我也不清楚。</p><p>但是这个阶段将会运行我们 <code>setImmediate</code>注册的回调。</p><p>很震惊吧，<code>setImmediate</code>完全就不是<code>timer</code>那一族的~~~~</p><h2 id="closing_handles">closing_handles</h2><p>执行<code>close</code>事件注册的回调，放在循环的最后一个阶段，也是合情合理。</p><h2 id="0x03-">0x03 那么我们练习一下</h2><blockquote>关于process.nextTick</blockquote><blockquote>nextTick 是个复杂的实现，需要另外开一篇来讲解。</blockquote><blockquote>为了方便下面的练习，我暂时先把结论放出来。</blockquote><blockquote>nextTick会直接追加在每一个阶段末尾，就是说，如果<code>timer</code>阶段的回调里有<code>process.nextTick</code>，通过这个来注册的回调，会在紧接着的<code>pending</code>之前就执行。</blockquote><p>✏️ 题目一</p><pre><code>setTimeout(() =&gt; {
  console.log('A')
}, 0)

setImmediate(() =&gt; {
  console.log('B')
})
</code></pre><p>答案：</p><pre><code>AB 或 BA
</code></pre><p>解释：</p><blockquote>首先这里的第一个知识点，是timer的第二个参数，取值范围是 [1, 2^31 - 1]。也就是说，这个 0 会被当成 1 处理。</blockquote><blockquote>然后根据运行环境的差异，如果进入到当前循环前，已经过去了 1ms ,那就打印 AB。</blockquote><blockquote>否则，如果在 1ms 内就开始了本次循环，那<code>timer</code>还没准备后，就会在下一次循环触发，自然就打印 BA。</blockquote><p>✏️ 题目二</p><pre><code>const fs = require('fs')

fs.readFile(__filename, () =&gt; {
  setTimeout(() =&gt; {
    console.log('A')
  }, 0)

  setImmediate(() =&gt; {
    console.log('B')
  })
})
</code></pre><p>答案：</p><pre><code>BA 
</code></pre><p>解释</p><blockquote>知识点在于<code>fs.readFile</code>，这个是 io操作，它的整个回调会在<code>poll</code>阶段执行。<br>而<code>poll</code>之后马上进入<code>check</code>，所以正好先执行了刚注册的<code>setImmediate</code>。</blockquote><blockquote><code>setTimeout</code>自然就要等到下一个循环的<code>timer</code>阶段。</blockquote><p>✏️ 题目三，这个划重点</p><pre><code>setImmediate(() =&gt; {
  console.log('1')
  setImmediate(() =&gt; {
    console.log('2')
  })
  process.nextTick(() =&gt; {
    console.log('nextTick')
  })
})

setImmediate(() =&gt; {
  console.log('3')
})
</code></pre><p>答案：</p><pre><code>1 3 nextTick 2
</code></pre><p>解释：</p><blockquote>首先，最外层的两个<code>setImmediate</code>会顺序注册到同一个<code>check</code>阶段，而上面提到<code>nextTick</code>会直接追加到当前阶段末尾，所以是<code>1 3 nextTick</code>而不是<code>1 nextTick 3</code> 。</blockquote><blockquote>而内层的<code>setImmediate</code>会注册到下一次循环的<code>check</code>阶段，所以 <code>2</code>最后打印。</blockquote><blockquote>请细品。</blockquote><h2 id="0x04-promise"><strong>0x04 继续练习之前，讲讲 promise</strong></h2><p>和 <code>process.nextTick</code>类似，<code>promise</code>的回调也是在当前阶段的末尾追加。</p><p>不过有意思的是，<code>process.nextTick</code>拥有更高的优先级。</p><p>这个实现细节，也是需要另外一篇文章来讲解（挖坑+1）。。。。</p><h3 id="0x05-">0x05 继续练习吧</h3><p>✏️ 题目四</p><pre><code>const promise = Promise.resolve()

promise.then(() =&gt; {
  console.log('A')
})

process.nextTick(() =&gt; {
  console.log('B')
})
</code></pre><p>答案：</p><pre><code>BA
</code></pre><p>解释</p><blockquote>无需解释，先记住二者的优先级。</blockquote><p>✏️ 题目五</p><pre><code>setTimeout(() =&gt; {
  console.log(1)
}, 0)

new Promise((resolve, reject) =&gt; {
  console.log(2)
  for (let i = 0; i &lt; 10000; i++) {
    i === 9999 &amp;&amp; resolve()
  }
  console.log(3)
}).then(() =&gt; {
  console.log(4)
})
console.log(5)
</code></pre><p>答案：</p><pre><code>2 3 5 4 1
</code></pre><p>解释</p><blockquote>这里有个知识点，<code>new Promise</code>的参数是同步执行的。</blockquote><blockquote>所以 <code>2 3 5</code>都是同步顺序输出的。</blockquote><blockquote>然后 <code>then</code> 在一个同步的for循环后触发，会追加到本阶段末尾，所以<code>4</code>紧接着输出。</blockquote><blockquote>最后是<code>setTimeout</code>，会在下一个循环的<code>timer</code>阶段执行，输出 <code>1</code></blockquote><p><strong>🐸 BOSS戦</strong></p><pre><code>setImmediate(() =&gt; {
  console.log(1)
  setTimeout(() =&gt; {
    console.log(2)
  }, 100)
  setImmediate(() =&gt; {
    console.log(3)
  })
  process.nextTick(() =&gt; {
    console.log(4)
  })
})
process.nextTick(() =&gt; {
  console.log(5)
  setTimeout(() =&gt; {
    console.log(6)
  }, 100)
  setImmediate(() =&gt; {
    console.log(7)
  })
  process.nextTick(() =&gt; {
    console.log(8)
  })
})
console.log(9)
</code></pre><p>答案：</p><pre><code>9 5 8 1 7 4 3 6 2
</code></pre><p>解释：</p><blockquote>你已经是一个成熟的程序员了，试着用上面的知识自己来解释吧。</blockquote><blockquote>Tips 可以尝试画出来，一共经过了多少个<strong>循环</strong>， 每个循环的每个<strong>阶段</strong>执行了什么。</blockquote> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 如何在 Node.js 中配置 Babel ]]>
                </title>
                <description>
                    <![CDATA[ Node.js 是目前最流行的后端技术之一。为了帮助你学习如何有效地使用它，在本文中，我们将使用 Node 创建一个简单的服务器，并在我们的代码中配置 Babel。 但在我们深入研究构建服务器之前，让我们更多地了解 Babel 是什么。 什么是 Babel Babel 是一个 JavaScript 编译器。它是一种流行的工具，可帮助你使用 JavaScript 编程语言的最新功能。 为什么在 Node.js 中使用 Babel 你是否曾经打开过使用 Node.js/Express 构建的后端仓库——你首先看到的是 ES6 导入和导出语句以及其他一些很酷的 ES6 语法特性？ Babel 让这一切成为可能。请记住，Babel 是一种流行的工具，可让你使用 JavaScript 的最新功能。现在许多框架都在底层使用 Babel 来编译它们的代码。 例如，如果没有像 Babel 这样的编译器的帮助，Node 就不能使用 ES6 导入和导出语句以及 ES6 语法的其他一些很酷的特性。 因此，在本教程中，我将向你展示如何快速配置你的 Node 应用程序以兼容大多数 ES6 语法。  ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/setup-babel-in-nodejs/</link>
                <guid isPermaLink="false">611b785a020863066536abfb</guid>
                
                    <category>
                        <![CDATA[ Node.js ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Babel ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Miya Liu ]]>
                </dc:creator>
                <pubDate>Wed, 15 Sep 2021 10:00:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2021/09/CREATE.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Node.js 是目前最流行的后端技术之一。为了帮助你学习如何有效地使用它，在本文中，我们将使用 Node 创建一个简单的服务器，并在我们的代码中配置 Babel。</p><p>但在我们深入研究构建服务器之前，让我们更多地了解 Babel 是什么。</p><h2 id="-babel">什么是 Babel</h2><p>Babel 是一个 JavaScript 编译器。它是一种流行的工具，可帮助你使用 JavaScript 编程语言的最新功能。</p><h2 id="-node-js-babel"><strong>为什么在 Node.js 中使用 Babel</strong></h2><p>你是否曾经打开过使用 Node.js/Express 构建的后端仓库——你首先看到的是 ES6 导入和导出语句以及其他一些很酷的 ES6 语法特性？</p><p>Babel 让这一切成为可能。请记住，Babel 是一种流行的工具，可让你使用 JavaScript 的最新功能。现在许多框架都在底层使用 Babel 来编译它们的代码。</p><p>例如，如果没有像 Babel 这样的编译器的帮助，Node 就不能使用 ES6 导入和导出语句以及 ES6 语法的其他一些很酷的特性。</p><p>因此，在本教程中，我将向你展示如何快速配置你的 Node 应用程序以兼容大多数 ES6 语法。</p><p>很棒吧？我们开始吧！</p><h2 id="-"><strong>准备</strong></h2><p>本教程假设你具备以下条件：</p><ul><li>Node.js 基础知识</li><li>你的电脑上已安装 Node</li><li>你选择的任何代码或文本编辑器</li></ul><h2 id="--1"><strong>开始</strong></h2><p>让我们配置一个基本的 Node 应用程序，我们将在本教程中使用它。</p><p>新建一个文件夹。在本教程中，我将调用我的 node-babel。现在将文件夹添加到工作区，并打开你的终端。</p><p>让我们初始化应用程序并创建一个 package.json 文件：</p><pre><code class="language-js">npm init</code></pre><p>此命令将显示一些我们希望保持原样的配置步骤。因此，在整个配置过程中按 enter 键或 return 键都可以正常工作。</p><p>完成后，创建一个名为 “index.js” 的新文件，它将作为我们的入口点。</p><h3 id="-babel-1"><strong>如何配置和安装 Babel</strong></h3><p>现在，我们将安装 Babel 系列的三个包，它们是：</p><pre><code class="language-js">@babel/cli, @babel/core and @babel/preset-env</code></pre><p>我们使用以下命令安装软件包：</p><pre><code class="language-js">npm install --save-dev @babel/cli @babel/core @babel/preset-env</code></pre><p>我们想使用 <strong><strong>--save-dev</strong></strong> 来安装它们作为模块开发的依赖项。</p><p>所以当你完成安装，创建一个名为 <strong><strong>.babelrc</strong></strong> 的新文件来配置 Babel。</p><pre><code class="language-js">touch .babelrc</code></pre><p>这个文件将包含我们想要添加到 Babel 的所有选项。所以现在，让我们使用我通常在我的应用程序中用于开发的配置。你可以复制它并添加到你的程序：</p><pre><code class="language-js">{
  "presets": [
    ["@babel/env", {
      "targets": {
        "node": "current"
      }
    }]
  ],
  "plugins": [
    "@babel/plugin-proposal-class-properties",
    "@babel/plugin-proposal-object-rest-spread"
  ]
}</code></pre><p>上面的配置是我用来告诉 Babel 的，是的，我不仅要使用我的导入和导出语句，还要使用 class 特性以及 ES6 中的 rest 和 spread 运算符。</p><p>厉害吧？让我们继续。</p><h3 id="--2"><strong>如何配置一个简单的服务器</strong></h3><p>现在打开我们之前创建的 “index.js” 文件，并添加以下代码以生成一个简单的服务器：</p><pre><code class="language-js">import http from 'http';

const server = http.createServer((req, res) =&gt; {
  res.end('Hello from the server');
}).listen(4001);

console.log('Server is up and running');

export default server;</code></pre><p>使用上面的示例代码，服务器将侦听端口 4001，然后在我们访问该端口时向我们发送 “Hello from the server” 响应。</p><h3 id="package-json-"><strong>Package.json 脚本配置</strong></h3><p>我们现在有一个简单的服务器。要运行它，我们必须在使用 Node.js 运行之前转译我们的代码。为此，请打开 “<strong><strong>package.json</strong></strong>” 文件并添加此构建和启动脚本：</p><pre><code class="language-js">  "scripts": {
+   "build": "babel index.js -d dist",
    "start": "npm run build &amp;&amp; node dist/index.js"
  }
</code></pre><p>很棒，现在我们用以下命令启动服务器：</p><pre><code class="language-js">npm start</code></pre><p>访问 localhost:4001，你应该会收到以下响应：</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2021/08/Screenshot_16.png" class="kg-image" alt="Screenshot_16" width="600" height="400" loading="lazy"></figure><h2 id="-nodemon-">如何使用 Nodemon 监视和重启你的服务器</h2><p>为了防止每次更改应用程序时自己重新启动服务器，我们需要安装 nodemon。你可以使用以下命令将 nodemon 安装到你的应用程序，以将其安装为开发依赖项：</p><pre><code class="language-js">npm install --save-dev nodemon
</code></pre><p>然后我们重新配置我们的 package.json 脚本：</p><pre><code class="language-js">  "scripts": {
    "build": "babel index.js -d dist",
    "start": "npm run build &amp;&amp; nodemon dist/index.js"
  }
</code></pre><p>太棒了，现在这是我们 Node 应用程序的最终代码，以及运行 “npm start” 启动服务器时应该得到的结果。</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2021/08/Screenshot_13.png" class="kg-image" alt="Screenshot_13" width="600" height="400" loading="lazy"></figure><p>从上图可以看出，我们的服务器已启动并正在运行。你现在可以使用 ES6 语法中的 import 和 export 语句以及 ES6 提供的其他很棒的功能，例如 Node 应用程序中的 rest 和 spread 运算符。</p><h2 id="--3">总结</h2><p>在本教程中，我们学习了如何使用 Babel 在 Node 应用程序中使用 ES6 语法。</p><p>请注意，你可以在 .babelrc 文件中添加更多配置，不限于我们在本教程中介绍的内容，你可以随意调整或更改它。</p><p>你可以在这里找到示例代码：<a href="https://github.com/Veri5ied/node-babel">https://github.com/Veri5ied/node-babel</a>。</p><p>编程愉快！</p><p>原文：<a href="https://www.freecodecamp.org/news/setup-babel-in-nodejs/">How to Setup Babel in Node.js</a>，作者：<a href="https://www.freecodecamp.org/news/author/alvin/">Alvin Okoro</a></p> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 通过 Node.js、MongoDB、Fastify 和 Swagger 构建高性能的 REST API 应用 ]]>
                </title>
                <description>
                    <![CDATA[ 作为 web 开发者，对构建高可用 REST API 应该并不陌生。 高可用 API 有如下衡量标准：  * 速度（API 响应时间）  * 文档（清晰简洁的文档，能够很好的描述 API）  * 架构和持续性（代码可维护可扩展） 这篇教程我们将要通过 Node.js、MongoDB、Fastify 和 Swagger 搭建高可用后端架构。 源代码已经托管到了 GitHub 3 [https://github.com/siegfriedgrimbeek/fastify-api] 上。 开始之前 你应该具有初级\中极 JavaScript 知识，了解 Node.js 和 MongoDB，知道什么是 REST APIS。 下面是相关链接：  * JavaScript 2 [https://developer.mozilla.org/bm/docs/Web/JavaScript]  * Node.js 1  ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/how-to-build-blazing-fast-rest-apis-with-node-js-mongodb-fastify-and-swagger/</link>
                <guid isPermaLink="false">5d22dc7bfbfdee429dc5ec28</guid>
                
                    <category>
                        <![CDATA[ Node.js ]]>
                    </category>
                
                    <category>
                        <![CDATA[ MongoDB ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ ZhichengChen ]]>
                </dc:creator>
                <pubDate>Wed, 03 Mar 2021 02:00:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2019/07/1-3.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>作为 web 开发者，对构建高可用 REST API 应该并不陌生。</p><p>高可用 API 有如下衡量标准：</p><ul><li>速度（API 响应时间）</li><li>文档（清晰简洁的文档，能够很好的描述 API）</li><li>架构和持续性（代码可维护可扩展）</li></ul><p>这篇教程我们将要通过 Node.js、MongoDB、Fastify 和 Swagger 搭建高可用后端架构。</p><p>源代码已经托管到了 <a href="https://github.com/siegfriedgrimbeek/fastify-api" rel="nofollow noopener">GitHub 3</a> 上。</p><figure class="kg-card kg-image-card"><img src="http://img.youtube.com/vi/VxY_cz0VQXE/0.jpg" class="kg-image" alt="0" width="480" height="360" loading="lazy"></figure><h2 id="-">开始之前</h2><p>你应该具有初级\中极 JavaScript 知识，了解 Node.js 和 MongoDB，知道什么是 REST APIS。</p><p>下面是相关链接：</p><ul><li><a href="https://developer.mozilla.org/bm/docs/Web/JavaScript" rel="nofollow noopener">JavaScript 2</a></li><li><a href="https://codeburst.io/the-only-nodejs-introduction-youll-ever-need-d969a47ef219" rel="nofollow noopener">Node.js 1</a></li><li><a href="https://docs.mongodb.com/manual/introduction/" rel="nofollow noopener">MongoDB 1</a></li><li><a href="https://restful.io/an-introduction-to-api-s-cee90581ca1b" rel="nofollow noopener">REST APIS 3</a></li></ul><h3 id="--1">涉及的技术</h3><ul><li><a href="https://www.fastify.io/" rel="nofollow noopener">Fastify 3</a></li><li><a href="https://mongoosejs.com/" rel="nofollow noopener">Mongoose</a></li><li><a href="https://swagger.io/" rel="nofollow noopener">Swagger 1</a></li></ul><p>可以在标签页中打开这些链接，随时查阅。</p><h3 id="--2">需要安装的环境：</h3><ul><li><a href="https://nodejs.org/en/" rel="nofollow noopener">NodeJS/NPM</a></li><li><a href="https://docs.mongodb.com/manual/installation/" rel="nofollow noopener">MongoDB</a></li><li><a href="https://www.getpostman.com/" rel="nofollow noopener">Postman</a></li></ul><p>除此之外，还需要 <a href="https://ourcodeworld.com/articles/read/200/top-7-best-free-web-development-ide-for-javascript-html-and-css" rel="nofollow noopener">IDE</a> 和 终端，我在 Mac 用的是 <a href="https://www.iterm2.com/" rel="nofollow noopener">iTerm2</a> ，在 Windows 上用的是 <a href="https://hyper.is/" rel="nofollow noopener">Hyper</a>。</p><h2 id="--3">让我们开始吧</h2><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/forum/uploads/default/optimized/1X/aa6ed96ac8cca8995c4d15ff3f8377b3dec98024_2_690x421.png" class="kg-image" alt="1_Jw9V__6jYhm2amP74D_0lw" width="600" height="400" loading="lazy"></figure><p>打开终端初始化项目，执行下面的代码：</p><pre><code class="language-bash">mkdir fastify-api
cd fastify-api
mkdir src
cd src
touch index.js
npm init
</code></pre><p>上面的代码，创建了两个新目录，然后切换到了新建的路径下，创建了一个 <code>index.js</code> 文件并通过 <a href="https://www.npmjs.com/" rel="nofollow noopener">npm</a> 初始化项目。</p><p>在初始化新项目的时候会提示输入一些值，这些值可以留空在稍后更新。</p><p>完成后，<code>src</code> 目录下生成了 <a href="https://alligator.io/nodejs/package-json/" rel="nofollow noopener">package.json</a>。在这个文件里你可以更改工程初始化时留空的值。</p><p>接下来安装将要用到的所有的依赖库：</p><pre><code>npm i nodemon mongoose fastify fastify-swagger boom
</code></pre><p>下面是在它们的官网引用的介绍：</p><h3 id="nodemon"><a href="https://github.com/remy/nodemon" rel="nofollow noopener">nodemon</a></h3><p>nodemon 是 node.js 应用的开发工具，当检测到目录下的文件改变时它会自动重启 node 应用。</p><p>使用 nodemon 不需要更改代码，它是 <code>node</code> 命令的封装，直接在执行脚本的时候用 <code>nodemon</code>替换 <code>node</code> 即可。</p><p>把下面的代码添加到 <code>package.json</code> 文件里来使用 nodemon：</p><pre><code class="language-json">"start": "./node_modules/nodemon/bin/nodemon.js ./src/index.js"
</code></pre><p>这时 <code>package.json</code> 文件应该这样：</p><pre><code class="language-json">{
  "name": "fastify-api",
  "version": "1.0.0",
  "description": "A blazing fast REST APIs with Node.js, MongoDB, Fastify and Swagger.",
  "main": "index.js",
  "scripts": {
  "start": "./node_modules/nodemon/bin/nodemon.js ./src/index.js",
  "test": "echo \"Error: no test specified\" &amp;&amp; exit 1"
},
  "author": "Siegfried Grimbeek &lt;siegfried.grimbeek@gmail.com&gt; (www.siegfriedgrimbeek.co.za)",
  "license": "ISC",
  "dependencies": {
  "boom": "^7.2.2",
  "fastify": "^1.13.0",
  "fastify-swagger": "^0.15.3",
  "mongoose": "^5.3.14",
  "nodemon": "^1.18.7"
  }
}
</code></pre><h3 id="mongoose"><a href="https://mongoosejs.com/" rel="nofollow noopener">mongoose</a></h3><p>Mongoose 提供了一个直截了当、基于 schema 的应用数据 model 解决方案。它包括了内置类型转换、校验、构建查询、业务逻辑钩子等功能，可直接使用。</p><h3 id="fastify-3"><a href="https://www.fastify.io/" rel="nofollow noopener">fastify 3</a></h3><p>Fastify 是一个高度专注于以最少的开销和强大的插件架构提供最佳开发者体验的 web 框架。Fastify 受到Hapi 和 Express 的启发，是目前最快的 web 框架之一。</p><h3 id="fastify-swagger-1"><a href="https://github.com/fastify/fastify-swagger" rel="nofollow noopener">fastify-swagger 1</a></h3><p><a href="https://swagger.io/" rel="nofollow noopener">Swagger 1</a> 文档生成器速度很快。它通过 routes 里声明的 schema 生成符合 swagger 规范的文档。</p><h3 id="boom"><a href="https://github.com/hapijs/boom" rel="nofollow noopener">boom</a></h3><p>boom 提供一个返回 HTTP 错误的工具集。</p><h2 id="--4">启动服务开始创建第一个路由</h2><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/forum/uploads/default/optimized/1X/1533a0dfd2222a09f334de96f18e57535a6bfbe4_2_690x211.png" class="kg-image" alt="1_rocnESJrNWsRGXMygLfDCQ" width="600" height="400" loading="lazy"></figure><p>在 <code>index.js</code> 里添加如下代码：</p><pre><code class="language-javascript">// Require the framework and instantiate it
const fastify = require('fastify')({
  logger: true
})

// Declare a route
fastify.get('/', async (request, reply) =&gt; {
  return { hello: 'world' }
})

// Run the server!
const start = async () =&gt; {
  try {
    await fastify.listen(3000)
    fastify.log.info(`server listening on ${fastify.server.address().port}`)
  } catch (err) {
    fastify.log.error(err)
    process.exit(1)
  }
}
start()
</code></pre><p>在这里引入了 Fastify 框架，声明了第一个 route 并在 <code>port 3000</code> 上启动了服务，代码很容易理解但是要注意初始化 Fastify 时传入了可选的对象：</p><pre><code class="language-javascript">// Require the fastify framework and instantiate it
const fastify = require('fastify')({
  logger: true
})
</code></pre><p>上面的代码开启了默认关闭的 Fastify 内置日志功能。</p><p>在终端的 <code>src</code> 目录下运行下面的代码：</p><pre><code>npm start
</code></pre><p>浏览器访问 <a href="http://localhost:3000/" rel="nofollow noopener">http://localhost:3000/</a> 会看到 <code>{hello:world}</code>。</p><p>回到 <code>index.js</code> 开始配置数据库。</p><h2 id="-mongodb-model">启动 MongoDB 创建 model</h2><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/forum/uploads/default/optimized/1X/9b7ce6e492e1515078b340108a231189f9cfb3c7_2_690x186.png" class="kg-image" alt="1_Ce0gUe0LbnhL7ebnDGTp5w" width="600" height="400" loading="lazy"></figure><p>安装 MongoDB，打开终端运行以下命令启动 MongoDB。</p><pre><code>mongod
</code></pre><p>在 MongoDB 里不需要手动创建数据库。只需要在配置的时候指定数据库名，当数据存储的时候， MongoDB 会自动创建数据库。</p><p>在 <code>index.js</code> 里添加如下代码：</p><pre><code class="language-javascript">...
// Require external modules
const mongoose = require('mongoose')
// Connect to DB
mongoose.connect(‘mongodb://localhost/mycargarage’)
 .then(() =&gt; console.log(‘MongoDB connected…’))
 .catch(err =&gt; console.log(err))
...
</code></pre><p>在上面的代码里我们引入了 Mongoose 来连接 MongoDB 数据库。数据库名是 <code>mycargarage</code>，如果一切顺利，在终端里会看到 <code>MongoDB connected...</code>。</p><p>注意不需要手动重启 app，因为我们之前添加了 <code>Nodemon</code> 库。</p><p>现在数据库已经运行，可以创建第一个 Model 了。在 <code>src</code> 路径下创建一个名为 <code>models</code> 的新文件夹，在里面创建一个 <code>Car.js</code> 文件，添加如下代码：</p><pre><code class="language-javascript">// External Dependancies
const mongoose = require('mongoose')

const carSchema = new mongoose.Schema({
  title: String,
  brand: String,
  price: String,
  age: Number,
  services: {
    type: Map,
    of: String
  }
})

module.exports = mongoose.model('Car', carSchema)
</code></pre><p>上面的代码声明的 <code>carSchema</code> 包含了和 cars 相关的所有信息。除了两个基本数据类型: <code>String</code>和 <code>Number</code>。还用到了相对比较陌生的 <code>Map</code>，可以在<a href="https://thecodebarbarian.com/whats-new-in-mongoose-5.1-map-support.html" rel="nofollow noopener">这里 2</a>阅读更多。接着导出了 <code>carSchema</code>，以便在 app 里使用。</p><p>也可以把 routes、controllers 和 config 的代码都放到 <code>index.js</code> 文件里，但是为了代码可维护，这里每个组件都有它自己的文件夹。</p><h2 id="-car-controller">创建 car controller</h2><p>在 <code>src</code> 下创建 <code>controllers</code> 文件夹，在文件夹里创建 <code>carController.js</code> 文件：</p><pre><code class="language-javascript">// External Dependancies
const boom = require('boom')

// Get Data Models
const Car = require('../models/Car')

// Get all cars
exports.getCars = async (req, reply) =&gt; {
  try {
    const cars = await Car.find()
    return cars
  } catch (err) {
    throw boom.boomify(err)
  }
}

// Get single car by ID
exports.getSingleCar = async (req, reply) =&gt; {
  try {
    const id = req.params.id
    const car = await Car.findById(id)
    return car
  } catch (err) {
    throw boom.boomify(err)
  }
}

// Add a new car
exports.addCar = async (req, reply) =&gt; {
  try {
    const car = new Car(req.body)
    return car.save()
  } catch (err) {
    throw boom.boomify(err)
  }
}

// Update an existing car
exports.updateCar = async (req, reply) =&gt; {
  try {
    const id = req.params.id
    const car = req.body
    const { ...updateData } = car
    const update = await Car.findByIdAndUpdate(id, updateData, { new: true })
    return update
  } catch (err) {
    throw boom.boomify(err)
  }
}

// Delete a car
exports.deleteCar = async (req, reply) =&gt; {
  try {
    const id = req.params.id
    const car = await Car.findByIdAndRemove(id)
    return car
  } catch (err) {
    throw boom.boomify(err)
  }
}
</code></pre><p>carController.js</p><p>代码看起来有点长，其实很简单。</p><ul><li>引入了 boom 来处理错误：<code>boom.boomify(err)</code>。</li><li>导出了所有的函数以便在 route 里引入。</li><li>每个函数都是异步的，使用了 await 表达式，这样在执行异步函数的时会候暂停运行直到程序调用传入的 Promise 的 resolve，然后继续异步函数的执行并返回 resolved 值。<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function" rel="nofollow noopener">点击点解更多</a></li><li>每个函数代码都在 try/catch 语句里。<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/try...catch" rel="nofollow noopener">点击了解更多</a></li><li>每个函数都有两个参数：<code>req</code> (request) 和 <code>reply</code>。教程里只用到了 request 参数，用来访问 request body、 参数以及处理数据。<a href="https://www.fastify.io/docs/latest/Reply/" rel="nofollow noopener">点击了解更多</a></li></ul><p>注意代码 31 行：</p><p><code>const car = new Car({ …req.body })</code></p><p>用到了扩展运算符。<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax" rel="nofollow noopener">点击了解更多</a></p><p>注意代码 42 行：</p><p><code>const { …updateData } = car</code></p><p>使用了 JavaScript 解构赋值和扩展运算符。<a href="https://codeburst.io/use-es2015-object-rest-operator-to-omit-properties-38a3ecffe90" rel="nofollow noopener">点击了解更多</a></p><p>另外，代码还用到了一些标准的 Mongoose 方法来操作数据库。</p><p>可能你已经迫不及待的想要运行你的 API 测试一下了，但是在开始之前，还需要把 controller 连接到 routes，把 routes 连接到 app 上。</p><h2 id="-routes">创建导入 routes</h2><p>同样，在项目根目录下创建 <code>routes</code> 文件夹。在文件夹内创建 <code>index.js</code> 文件，文件代码如下：</p><pre><code class="language-javascript">// Import our Controllers
const carController = require('../controllers/carController')

const routes = [
  {
    method: 'GET',
    url: '/api/cars',
    handler: carController.getCars
  },
  {
    method: 'GET',
    url: '/api/cars/:id',
    handler: carController.getSingleCar
  },
  {
    method: 'POST',
    url: '/api/cars',
    handler: carController.addCar,
    // schema: documentation.addCarSchema
  },
  {
    method: 'PUT',
    url: '/api/cars/:id',
    handler: carController.updateCar
  },
  {
    method: 'DELETE',
    url: '/api/cars/:id',
    handler: carController.deleteCar
  }
]

module.exports = routes
</code></pre><p>在这里我们引入了 controller 并且把每个函数分发到了相应的 routes 上。</p><p>每个 route 都是由 method、url、和 handler 组成，访问路由会调用相应 app 函数。</p><p>上面 routes 里出现的的 <code>:id</code> 是向路由里传递参数的一个常见做法，它允许以下面这种形式传入 id：</p><p><code>http://127.0.0.1:3000/api/cars/5bfe30b46fe410e1cfff2323</code></p><h2 id="-api">文件建立关联，测试 API</h2><p>现在大部分功能都已完成，需要做的只是把他们连接起来，启动 API 服务。导入 routes，在 <code>index.js</code> 文件里添加如下代码：</p><pre><code class="language-javascript">const routes = require(‘./routes’)
</code></pre><p>接下来需要遍历 routes 数组，然后在 Fastify 里面初始化。可以用下面的代码实现，、在 <code>index.js</code> 文件里添加：</p><pre><code class="language-javascript">routes.forEach((route, index) =&gt; {
 fastify.route(route)
})
</code></pre><p>现在准备好测试了！</p><p>API 测试有一个很棒的工具 <a href="https://www.getpostman.com/" rel="nofollow noopener">Postman</a>。我们在request 的 body 里以 raw object 做为参数。</p><p>查询所有 cars:</p><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/forum/uploads/default/optimized/1X/b8ef6416f1451a59f99557a690d914f0232df755_2_690x466.png" class="kg-image" alt="1_YoxRE054Q7qgrAxaCgrzLw" width="600" height="400" loading="lazy"></figure><p>查询指定 car：</p><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/forum/uploads/default/optimized/1X/a4d08c342d764657aebfe9536d192257fb7fc423_2_690x464.png" class="kg-image" alt="1_YoxRE054Q7qgrAxaCgrzLw" width="600" height="400" loading="lazy"></figure><p>添加新 car：</p><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/forum/uploads/default/optimized/1X/1ece5dae70e481415588dcc0feb1c65ba6919db4_2_554x500.png" class="kg-image" alt="1_YoxRE054Q7qgrAxaCgrzLw" width="600" height="400" loading="lazy"></figure><p><em>services 看起来是空的，实际上数据已经添加到了数据库里了</em></p><p>更新 car：</p><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/forum/uploads/default/optimized/1X/b8e1c24297cc2d79d4e4decec34cfedadf557af9_2_556x500.png" class="kg-image" alt="1_YoxRE054Q7qgrAxaCgrzLw" width="600" height="400" loading="lazy"></figure><p>删除 car：</p><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/forum/uploads/default/optimized/1X/18370567d9cdde5da8a352e6b55e192b754b995e_2_690x467.png" class="kg-image" alt="1_YoxRE054Q7qgrAxaCgrzLw" width="600" height="400" loading="lazy"></figure><p>现在一个全功能 API 已具备，还差文档，别怕，用 Swagger 可以方便的生成。</p><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/forum/uploads/default/original/1X/39dcf4afafe6f83e48de459b3be2cef029e8b92f.png" class="kg-image" alt="1_7iaXjYojG6kWxLTZaW1x4Q" width="600" height="400" loading="lazy"></figure><h2 id="-swagger">添加 Swagger</h2><p>现在创建 config 文件夹。在里面创建 <code>swagger.js</code> 文件，文件代码如下：</p><pre><code class="language-javascript">exports.options = {
  routePrefix: '/documentation',
  exposeRoute: true,
  swagger: {
    info: {
      title: 'Fastify API',
      description: 'Building a blazing fast REST API with Node.js, MongoDB, Fastify and Swagger',
      version: '1.0.0'
    },
    externalDocs: {
      url: 'https://swagger.io',
      description: 'Find more info here'
    },
    host: 'localhost',
    schemes: ['http'],
    consumes: ['application/json'],
    produces: ['application/json']
  }
}
</code></pre><p>上面的代码是将要传入到 fastify-swagger 插件的 option object，在<code>index.js</code> 里添加如下代码：</p><pre><code class="language-javascript">// Import Swagger Options
const swagger = require(‘./config/swagger’)
// Register Swagger
fastify.register(require(‘fastify-swagger’), swagger.options)
</code></pre><p>在启动服务之后还需要添加如下代码：</p><pre><code class="language-javascript">...
await fastify.listen(3000)
fastify.swagger()
fastify.log.info(`listening on ${fastify.server.address().port}`)
...
</code></pre><p>如果一切顺利访问 <a href="http://localhost:3000/documentation" rel="nofollow noopener">http://localhost:3000/documentation</a>，效果如下：</p><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/forum/uploads/default/optimized/1X/9ad7aa5a1cc3e2f2212f17121ea926d5fb83c29a_2_690x440.png" class="kg-image" alt="1_HV-5eImCMs7LtiLodTz7CQ" width="600" height="400" loading="lazy"></figure><p>是不是很简单？现在有了随着 API 自动更新的文档，可以任意在 routes 里面添加信息了<a href="https://github.com/fastify/fastify-swagger" rel="nofollow noopener">点击此处阅读更多</a></p><h2 id="--5">接下来</h2><p>现在基本 API 已经成型，可以扩展成能想象的任何应用，有一万种可能。</p><p>接下在的教程里，我们将要集成 GraphQL，最后会集成前端的 Vue.js。</p><p>原文链接：<a href="https://www.freecodecamp.org/news/how-to-build-blazing-fast-rest-apis-with-node-js-mongodb-fastify-and-swagger-114e062db0c9/">How to build blazing fast REST APIs with Node.js, MongoDB, Fastify and Swagger</a>，作者：<a href="https://www.freecodecamp.org/news/author/siegfried/">Siegfried Grimbeek</a></p> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 什么是 npm —— 写给初学者的编程教程 ]]>
                </title>
                <description>
                    <![CDATA[ 前言 本篇文章可以作为 npm（Node.js 最喜欢的伙伴）的一个基本学习指南。 自 2009 年以来，Node.js 一直席卷全球。成千上万个系统基于 Node.js 构建，促使开发者在社区宣称“JavaScript 正在吞噬软件”。 Node 成功的主要因素之一是它广受欢迎的软件包管理器——npm，因为 npm 使 JavaScript 开发人员可以快速方便地共享软件包， 例如 lodash [https://www.npmjs.com/package/lodash] 和 moment [https://www.npmjs.com/package/moment]。 在我撰写这篇文章时，npm 已帮助发布了 130 万个软件包，每周下载量超过 160 亿次！ 这些数字对于任何软件工具来说都非常厉害。 所以，现在让我们讨论一下 npm 到底是什么。 NPM 是什么 npm（“Node 包管理器”）是 JavaScript 运行时 Node.js 的默认程序包管理器。 它也被称为“Ninja Pumpkin Mutants”，“Nonprofit Pizza Makers”， ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/what-is-npm-a-node-package-manager-tutorial-for-beginners/</link>
                <guid isPermaLink="false">5f84642c5f583f0565090aa6</guid>
                
                    <category>
                        <![CDATA[ NPM ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Node.js ]]>
                    </category>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ 古月 ]]>
                </dc:creator>
                <pubDate>Mon, 12 Oct 2020 13:11:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2020/10/cover-4.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <h2 id="-">前言</h2><p>本篇文章可以作为 npm（Node.js 最喜欢的伙伴）的一个基本学习指南。</p><p>自 2009 年以来，Node.js 一直席卷全球。成千上万个系统基于 Node.js 构建，促使开发者在社区宣称“JavaScript 正在吞噬软件”。</p><p>Node 成功的主要因素之一是它广受欢迎的软件包管理器——npm，因为 npm 使 JavaScript 开发人员可以快速方便地共享软件包， 例如 <a href="https://www.npmjs.com/package/lodash">lodash</a> &nbsp;和 <a href="https://www.npmjs.com/package/moment">moment</a>。</p><p>在我撰写这篇文章时，npm 已帮助发布了 130 万个软件包，每周下载量超过 160 亿次！ 这些数字对于任何软件工具来说都非常厉害。 所以，现在让我们讨论一下 npm 到底是什么。</p><h2 id="npm-">NPM 是什么</h2><p>npm（“Node 包管理器”）是 JavaScript 运行时 Node.js 的默认程序包管理器。</p><p>它也被称为“Ninja Pumpkin Mutants”，“Nonprofit Pizza Makers”，以及许多其他随机名称，你可以在 &nbsp;<a href="https://github.com/npm/npm-expansions">npm-expansions</a> &nbsp;上探索这些名称。</p><p>npm 由两个主要部分组成:</p><ul><li>用于发布和下载程序包的 CLI（命令行界面）工具</li><li>托管 JavaScript 程序包的 &nbsp;<a href="https://www.npmjs.com/">在线存储库</a></li></ul><p>为了更直观地解释，我们可以将存储库 &nbsp;<a href="https://npmjs.com/">npmjs.com</a> &nbsp;视为一个物流集散中心，该中心从卖方（npm 包裹的作者）那里接收货物的包裹，并将这些货物分发给买方（npm 包裹的用户）。</p><p>为了促进此过程，<a href="https://npmjs.com/">npmjs.com</a> &nbsp;物流集散中心雇用了一群勤劳的袋熊（npm CLI），他们将被分配给每个 &nbsp;<a href="https://npmjs.com/">npmjs.com</a> &nbsp;用户作为私人助理。 因此，dependencies（依赖项）会如下传递给 JavaScript 开发人员：</p><figure class="kg-card kg-image-card"><img src="https://camo.githubusercontent.com/26f5c4e6537732f5e6a5c36c4f0f54ebefd70f19/68747470733a2f2f7777772e66726565636f646563616d702e6f72672f6e6577732f636f6e74656e742f696d616765732f323032302f30362f776f6d6261742d696e7374616c6c2e706e67" class="kg-image" alt="68747470733a2f2f7777772e66726565636f646563616d702e6f72672f6e6577732f636f6e74656e742f696d616765732f323032302f30362f776f6d6261742d696e7374616c6c2e706e67" width="600" height="400" loading="lazy"></figure><p>发布 JS 软件包的过程如下：</p><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/news/content/images/2020/10/image-5.png" class="kg-image" alt="image-5" width="600" height="400" loading="lazy"></figure><p>让我们看看这只袋熊如何协助想要在项目中使用 JavaScript 包的开发人员。下面我们还将看到它们（npm CLI）如何帮助开源向导将其出色的库推向世界。</p><h2 id="package-json">package.json</h2><p>每个 JavaScript 项目（无论是 Node.js 还是浏览器应用程序）都可以被当作 npm 软件包，并且通过 &nbsp;<code>package.json</code> &nbsp;来描述项目和软件包信息。</p><p>我们可以将 &nbsp;<code>package.json</code> &nbsp;视为快递盒子上的运输信息。</p><p>当运行 &nbsp;<code>npm init</code> &nbsp;初始化 JavaScript/Node.js 项目时，将生成 &nbsp;<code>package.json</code> &nbsp;文件，文件内的内容(基本元数据)由开发人员提供：</p><ul><li><code>name</code>：JavaScript 项目或库的名称。</li><li><code>version</code>：项目的版本。通常，在应用程序开发中，由于没有必要对开源库进行版本控制，因此经常忽略这一块。但是，仍可以用它来定义版本。</li><li><code>description</code>：项目的描述。</li><li><code>license</code>：项目的许可证。</li></ul><h3 id="npm-scripts">npm scripts</h3><p><code>package.json</code> &nbsp;还支持一个 &nbsp;<code>scripts</code> &nbsp;属性，可以把它当作在项目本地运行的命令行工具。例如，一个 npm 项目的 &nbsp;<code>scripts</code>部分可能看起来像这样：</p><pre><code class="language-json">{
  "scripts": {
    "build": "tsc",
    "format": "prettier --write **/*.ts",
    "format-check": "prettier --check **/*.ts",
    "lint": "eslint src/**/*.ts",
    "pack": "ncc build",
    "test": "jest",
    "all": "npm run build &amp;&amp; npm run format &amp;&amp; npm run lint &amp;&amp; npm run pack &amp;&amp; npm test"
  }
}
</code></pre><p><code>eslint</code>，<code>prettier</code>，<code>ncc</code>，<code>jest</code> &nbsp;不是安装为全局可执行文件，而是安装在项目本地的 <code>node_modules/.bin/</code> 中。</p><p>最新引入的 &nbsp;<a href="https://www.freecodecamp.org/news/npm-vs-npx-whats-the-difference/">npx</a> &nbsp;使我们可以像在全局安装程序一样运行这些 &nbsp;<code>node_modules</code> &nbsp;项目作用域命令，方法是在其前面加上 &nbsp;<code>npx ...</code>（即<code>npx prettier --write ** / *。ts</code>）。</p><h3 id="dependencies-vs-devdependencies">dependencies vs devDependencies</h3><p>这两个以键值对象的形式出现，其中 npm 库的名称为键，其<a href="https://semver.org/">语义格式</a>版本为值。 大家可以看看 <a href="https://github.com/actions/typescript-action">Github 的 TypeScript 操作模板</a>中的示例：</p><pre><code class="language-json">{
  "dependencies": {
    "@actions/core": "^1.2.3",
    "@actions/github": "^2.1.1"
  },
  "devDependencies": {
    "@types/jest": "^25.1.4",
    "@types/node": "^13.9.0",
    "@typescript-eslint/parser": "^2.22.0",
    "@zeit/ncc": "^0.21.1",
    "eslint": "^6.8.0",
    "eslint-plugin-github": "^3.4.1",
    "eslint-plugin-jest": "^23.8.2",
    "jest": "^25.1.0",
    "jest-circus": "^25.1.0",
    "js-yaml": "^3.13.1",
    "prettier": "^1.19.1",
    "ts-jest": "^25.2.1",
    "typescript": "^3.8.3"
  }
}</code></pre><p>这些依赖通过带有 &nbsp;<code>--save</code> &nbsp;或 &nbsp;<code>--save-dev</code> &nbsp;标志的 &nbsp;<code>npm install</code> &nbsp;命令安装。 它们分别用于生产和开发/测试环境。 在下一节中，我们将更深入地研究这些软件包的安装。</p><p>同时，理解语义版本前面的符号非常重要（假设你已经阅读 <a href="https://semver.org/">semver</a> 的 &nbsp;<code>major.minor.patch</code> &nbsp;模型）：</p><ul><li><code>^</code>：表示最新的次版本，例如， <code>^1.0.4</code> &nbsp;可能会安装主版本系列 &nbsp;<code>1</code> &nbsp;的最新次版本 <code>1.3.0</code>。</li><li><code>〜</code>：表示最新的补丁程序版本，与 &nbsp;<code>^</code> &nbsp;类似， <code>〜1.0.4</code> &nbsp;可能会安装次版本系列 <code>1.0</code> &nbsp;的最新次版本<code>1.0.7</code>。</li></ul><p>所有这些确切的软件包版本都将记录在 &nbsp;<code>package-lock.json</code> &nbsp;文件中。</p><h3 id="package-lock-json">package-lock.json</h3><p>该文件描述了 npm JavaScript 项目中使用的依赖项的确切版本。如果 &nbsp;<code>package.json</code> &nbsp;是通用的描述性标签，则 &nbsp;<code>package-lock.json</code> &nbsp;是成分表。</p><p>就像我们通常不会读取食品包装袋上的成分表（除非你太无聊或需要知道）一样，<code>package-lock.json</code> &nbsp;并不会被开发人员一行一行进行读取。</p><p><code>package-lock.json</code> &nbsp;通常是由 &nbsp;<code>npm install</code> &nbsp;命令生成的，也可以由我们的 NPM CLI 工具读取，以确保使用 &nbsp;<code>npm ci</code> &nbsp;复制项目的构建环境。</p><h2 id="-npm">用户如何使用 NPM</h2><p>从前面提到的 130 万个发布的软件包中，有 160 亿次下载，可以推断出，大多数 npm 用户都朝这个方向使用 npm。所以，了解如何使用这个强大的工具会很有帮助。</p><h3 id="npm-install">npm install</h3><p>这是现在我们开发 JavaScript/Node.js 应用程序时最常用的命令。</p><p>默认情况下，<code>npm install &lt;package-name&gt;</code> &nbsp;将安装带有 &nbsp;<code>^</code> &nbsp;版本号的软件包的最新版本。npm 项目上下文中的 &nbsp;<code>npm install</code> &nbsp;将根据 &nbsp;<code>package.json</code> &nbsp;规范将软件包下载到项目的 &nbsp;<code>node_modules</code> &nbsp;文件夹中，从而升级软件包的版本（并重新生成 &nbsp;<code>package-lock.json</code> &nbsp;）。 <code>npm install &lt;package-name&gt;</code> &nbsp;可以基于 &nbsp;<code>^</code> &nbsp;和 &nbsp;<code>〜</code> &nbsp;版本匹配。</p><p>如果要在全局上下文中安装程序包，可以在机器的任何地方使用它，则可以指定全局标志 &nbsp;<code>-g</code>（例如 &nbsp;<a href="https://github.com/tapio/live-server">live-server</a>）。</p><p>npm 使安装 JavaScript 软件包非常容易，以至于经常错误地使用此命令。 导致一些程序员对 npm 开这样的玩笑：</p><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/news/content/images/2020/10/image-6.png" class="kg-image" alt="image-6" width="600" height="400" loading="lazy"></figure><p>但是，npm 包太大、太深这样的问题可以通过 &nbsp;<code>--production</code> &nbsp;标志来拯救！在上一节中，我们讨论了分别用于生产和开发/测试环境的 &nbsp;<code>dependencies</code> &nbsp;和 &nbsp;<code>devDependencies</code> &nbsp;。 这个 &nbsp;<code>--production</code> &nbsp;标志是如何在 &nbsp;<code>node_modules</code> &nbsp;中进行区别的。</p><p>通过将此标志附加到 &nbsp;<code>npm install</code> &nbsp;命令，我们将仅从 &nbsp;<code>dependencies</code> &nbsp;安装软件包，从而将 &nbsp;<code>node_modules</code> &nbsp;的大小大大减小到应用程序正常运行所必需的大小。——不应该将 &nbsp;<code>devDependencies</code> &nbsp;引入生产环境！</p><h3 id="npm-ci">npm ci</h3><p>因此，如果 &nbsp;<code>npm install --production</code> &nbsp;对于生产环境是最佳选项，那么是否必须有一个对本地环境，测试环境最合适的选项？</p><p>答案是 &nbsp;<code>npm ci</code>。</p><p>就像如果 &nbsp;<code>package_lock.json</code> &nbsp;尚不存在于项目中一样，无论何时调用 &nbsp;<code>npm install</code> &nbsp;都会生成它，<code>npm ci</code> &nbsp;会消耗该文件来下载项目所依赖的每个软件包的确切版本。 </p><p>这样，无论是用于本地开发的笔记本电脑还是 Github Actions 等 CI（持续集成）构建环境，我们都可以确保项目上下文在不同机器上保持完全相同。</p><h3 id="npm-audit">npm audit</h3><p>随着越来越多的软件包发布，并且易于安装，因此 npm 软件包容易受到恶意作者的恶意攻击，例如<a href="https://medium.com/@jsoverson/how-two-malicious-npm-packages-targeted-sabotaged-one-other-fed7199099c8">这些</a>。</p><p>意识到生态系统存在问题，npm.js 组织提出了 &nbsp;<code>npm audit</code> &nbsp;的<a href="https://blog.npmjs.org/post/173719309445/npm-audit-identify-and-fix-insecure">主意</a>。 他们维护了一个安全漏洞列表，开发人员可以使用 &nbsp;<code>npm audit</code> &nbsp;命令来审核项目中的依赖项。</p><p><code>npm audit</code> &nbsp;为开发人员提供了有关漏洞以及是否有要修复的版本的信息，例如：</p><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/news/content/images/2020/10/image-8.png" class="kg-image" alt="image-8" width="600" height="400" loading="lazy"></figure><p>如果补救措施在下一个不间断的版本升级中可用，则可以使用 &nbsp;<code>npm audit fix</code> &nbsp;来自动升级受影响的依赖项的版本。</p><h2 id="-npm-1">作者如何使用 NPM</h2><p>我们已经了解了作为用户，如何通过 NPM CLI 有效得使用 NPM，但是作为作者又如何使用呢？</p><h3 id="npm-publish">npm publish</h3><p>将软件包发送到 &nbsp;<a href="https://npmjs.com/">npmjs.com</a> &nbsp;非常容易，因为我们只需要运行 &nbsp;<code>npm publish</code> &nbsp;。 棘手的部分（并非专门针对 npm 软件包作者）是确定软件包的版本。</p><p>根据 &nbsp;<a href="https://semver.org/">semver.org</a> &nbsp;的经验法则：</p><ol><li>当你进行不兼容的 API 更改时使用 MAJOR 版本</li><li>以向后兼容的方式添加功能时使用 MINOR 版本</li><li>进行向后兼容的 bug 修复时使用 PATCH 版本</li></ol><p>在发布软件包时，遵循上述规则尤为重要，可以确保你不会破坏任何人的代码，因为 npm 中匹配的默认版本是<code>^</code>（又称下一个次版本）。</p><h2 id="-npm-javascript-node-js-">❤️ &nbsp;npm &nbsp;❤️ &nbsp;JavaScript &nbsp;❤️ &nbsp;Node.js &nbsp;❤️</h2><p>了解了以上知识，我们就可以开始有效地使用 npm 并指挥可爱的袋熊大军啦！</p><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/news/content/images/2020/10/image-9.png" class="kg-image" alt="image-9" width="600" height="400" loading="lazy"></figure><p>原文：<a href="https://www.freecodecamp.org/news/what-is-npm-a-node-package-manager-tutorial-for-beginners/">What is npm? A Node Package Manager Tutorial for Beginners</a>，作者：Stanley Nguyen</p> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 三步带你开发一个短链接生成平台 ]]>
                </title>
                <description>
                    <![CDATA[ 前段时间在开发【葡萄城社区】公众号时有一个功能是需要用网页授权认证地址生成二维码，但类似像下面这样的网址url即便是看也觉得很头疼了。 用这个地址生成的二维码也是密密麻麻，虽不影响微信长按扫码，一旦二维码尺寸缩一点点，图片马上就会糊掉，导致摄像头直接扫码会难以识别。 那这种情况下， 我们自然就会想到如果使用短链接减少url的字符，生成的码自然就会变得容易识别了，同时还会使url更美观且易于转发。现在市面上可用的就是微博的t.cn和一些第三方的生成短链接工具，但这两类工具都有一些使用上的问题，例如：t.cn现在的规则是会出现一个中转页不会直接跳转，而第三方的工具因为是一个公共平台，有时可能会因一些不良信息导致整个平台无法访问。 那与其这样，不如我们自己来实现一个短链接平台吧，实现一个短链接平台原理上也非常简单，搞定两部分就行了：1.保存长短链接的对应关系。2.通过短链接查询长连接并重定向。 为了高效，我这使用的是node和mongodb，下面我们就来开始动手吧。 首先，我们先创建一个express工程： express -e demo    change directory ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/shorter-url/</link>
                <guid isPermaLink="false">5f6d4a1f027c3105323f551e</guid>
                
                    <category>
                        <![CDATA[ URL ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Node.js ]]>
                    </category>
                
                    <category>
                        <![CDATA[ MongoDB ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Willie.ji ]]>
                </dc:creator>
                <pubDate>Fri, 25 Sep 2020 09:22:20 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2020/09/radek-grzybowski-eBRTYyjwpRY-unsplash.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>前段时间在开发【葡萄城社区】公众号时有一个功能是需要用网页授权认证地址生成二维码，但类似像下面这样的网址url即便是看也觉得很头疼了。</p><figure class="kg-card kg-image-card"><img src="https://chinese.freecodecamp.org/news/content/images/2020/09/image-9.png" class="kg-image" alt="image-9" width="634" height="115" loading="lazy"></figure><p>用这个地址生成的二维码也是密密麻麻，虽不影响微信长按扫码，一旦二维码尺寸缩一点点，图片马上就会糊掉，导致摄像头直接扫码会难以识别。</p><p>那这种情况下， 我们自然就会想到如果使用短链接减少url的字符，生成的码自然就会变得容易识别了，同时还会使url更美观且易于转发。现在市面上可用的就是微博的t.cn和一些第三方的生成短链接工具，但这两类工具都有一些使用上的问题，例如：t.cn现在的规则是会出现一个中转页不会直接跳转，而第三方的工具因为是一个公共平台，有时可能会因一些不良信息导致整个平台无法访问。</p><p>那与其这样，不如我们自己来实现一个短链接平台吧，实现一个短链接平台原理上也非常简单，搞定两部分就行了：1.保存长短链接的对应关系。2.通过短链接查询长连接并重定向。</p><p>为了高效，我这使用的是node和mongodb，下面我们就来开始动手吧。</p><p>首先，我们先创建一个express工程：</p><!--kg-card-begin: markdown--><pre><code>express -e demo

   change directory:
     &gt; cd demo

   install dependencies:
     &gt; npm install

   run the app:
     &gt; SET DEBUG=demo:* &amp; npm start
</code></pre>
<!--kg-card-end: markdown--><p>然后进入demo目录并安装express必要依赖：</p><!--kg-card-begin: markdown--><pre><code>npm install
</code></pre>
<!--kg-card-end: markdown--><p>同时通过 <code>npm</code> 安装我们需要用到的 <code>mongoose</code> 和 <code>shortid</code> 和 <code>body-parser</code>：</p><!--kg-card-begin: markdown--><pre><code>npm install mongoose
npm install shortid
npm install body-parser
</code></pre>
<!--kg-card-end: markdown--><p>下面分别对使用到的这三个包简单说明一下：</p><ul><li>在这个应用中，我们使用了 mongodb，之所以选择它是因为执行高效且低开销，所以执行起来也很高效，不过如果使用其他数据库也是没问题的。这里的<strong>mongoose</strong>就是npm的一个包，主要是为程序提供连接mongodb并增删查改的功能。</li><li>通过使用<strong>shortid</strong>可以生成一个指定字符不重复的编码，便于我们生成类似xxx.com/ngTsfdgh 类似红字部分的编码。</li><li>由于我们生成短链接部分的api使用的是post方法，使用<strong>body-parser</strong>可以多扩展一种body编码类型解析能力。</li></ul><h2 id="-mongodb-">首先设置MongoDB的连接信息</h2><!--kg-card-begin: markdown--><pre><code>module.exports = {
    mongo_base_connection_url: 'mongodb://localhost:27017',
    mongo_db: 'mongodb://localhost:27017/shorturl',
    mongo_options: {
        autoReconnect: true,
        keepAlive: true,
        reconnectTries: 50,
        reconnectInterval: 1000,
        connectTimeoutMS: 5000000,
    }
}
console.log("Connection local mongo db");
</code></pre>
<!--kg-card-end: markdown--><h2 id="-">数据库模型定义</h2><p>因为我们的对应关系是需要通过短链接查询长连接，所以这里我们主要以存储短链接和长连接为主，另外大家也可以根据自己需要添加链接点击统计之类的字段，方便后期统计。</p><!--kg-card-begin: markdown--><pre><code>var mongoose = require('mongoose');
var Schema = mongoose.Schema;
 
var urlSchema = new Schema({
  shortUrl: String,
  longUrl: String
});
 
module.exports = mongoose.model('UrlTable', urlSchema);
</code></pre>
<!--kg-card-end: markdown--><h2 id="-express-">定义express路由</h2><p>因为这个应用我们只有生成和Redirect两个功能，所以这里只有两个页面即可完成所有工作。</p><!--kg-card-begin: markdown--><pre><code>var index = require('./routes/index');
var url = require('./routes/url');
app.use('/', index);
app.use('/url', url);
</code></pre>
<!--kg-card-end: markdown--><h4 id="--1">生成短链接页面</h4><!--kg-card-begin: markdown--><pre><code>const express = require("express");
const router = express.Router();
const shortId = require('shortid');
const UrlTable = require('../models/urltable');
const mongoose = require('mongoose');
var setup = require('../dbconfig/db');
 
router.post('/', function(req, res, next) {
    var params = req.body;
    var url = params.longUrl;
shortId.characters(' 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ^*')
    var shortid = shortId.generate();
    var objurl = { shortUrl: shortid, longUrl: url};
    mongoose.connect(setup.mongo_db, setup.mongo_options);
    UrlTable.create(objurl, function (err, objurl) {
      //if (err) console.log(err);
      res.send("http://localhost:3000/" + shortid);
    });
    return;
});
</code></pre>
<!--kg-card-end: markdown--><p>指定生成 <code>shortId</code> 字符的范围并生成：</p><!--kg-card-begin: markdown--><pre><code>shortId.characters('0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ')

var shortid = shortId.generate();
</code></pre>
<!--kg-card-end: markdown--><p>为数据库构建符合要求的数据模型：</p><!--kg-card-begin: markdown--><pre><code>var objurl = { shortUrl: shortid, longUrl: url};
</code></pre>
<!--kg-card-end: markdown--><p>最后，连接数据库并保存后将短链接结果返回客户端：</p><!--kg-card-begin: markdown--><pre><code>mongoose.connect(setup.mongo_db, setup.mongo_options);
UrlTable.create(objurl, function (err, objurl) {
//if (err) console.log(err);
res.send("http://localhost:3000/" + shortid);
});
</code></pre>
<!--kg-card-end: markdown--><h4 id="--2">短链接跳转页面</h4><!--kg-card-begin: markdown--><pre><code>const express = require("express");
const router = express.Router();
const UrlTable = require('../models/urltable');
const mongoose = require('mongoose');
var setup = require('../dbconfig/db');
 
router.get('/:shortUrl', function (req, res, next) {
    var shortUrl = req.params.shortUrl;
    mongoose.connect(setup.mongo_db, setup.mongo_options);
    UrlTable.findOne({ shortUrl:shortUrl }).then((result) =&gt; {
       //待添加错误处理
    res.redirect(result.longUrl);
    })
});
 
module.exports = router;
</code></pre>
<!--kg-card-end: markdown--><p>这个页面为了便于快速跳转，我们就使用get接收参数，这个页面功能就很简单了，接参查询并跳转。</p><p>接收短链接码：</p><!--kg-card-begin: markdown--><pre><code>var shortUrl = req.params.shortUrl;
</code></pre>
<!--kg-card-end: markdown--><p>连接数据库查询并跳转：</p><!--kg-card-begin: markdown--><pre><code>mongoose.connect(setup.mongo_db, setup.mongo_options);

    UrlTable.findOne({ shortUrl:shortUrl }).then((result) =&gt; {

           //待添加错误处理

      res.redirect(result.longUrl);

 })
</code></pre>
<!--kg-card-end: markdown--><p>后期大家可以对一些错误异常处理，数据统计等做一些增强，这里就不做补充了。</p><p>下面让我们启用应用开始测试吧。</p><h2 id="--3">启动应用并测试</h2><!--kg-card-begin: markdown--><pre><code>npm start
</code></pre>
<!--kg-card-end: markdown--><p>启动后，默认的访问端口为3000，我们首先测试下短链接生成页，这里我们post一个名为longUrl的长链接参数，数据对象为：</p><p>{"longUrl" : "<a href="https://demo.grapecity.com.cn/spreadjs/SpreadJSTutorial/features/tables/basic-table/purejs">https://demo.grapecity.com.cn/spreadjs/SpreadJSTutorial/features/tables/basic-table/purejs</a>"}</p><figure class="kg-card kg-image-card"><img src="https://i.v2ex.co/Kne07O7El.gif" class="kg-image" alt="Kne07O7El" width="833" height="572" loading="lazy"></figure><p>成功升成了如下短链接：</p><p><a href="http://localhost:3000/iGE6ZlDmh">http://localhost:3000/iGE6ZlDmh</a></p><p>我们只要通过访问短链接能正常跳转至保存的长连接即可。</p><figure class="kg-card kg-image-card"><img src="https://i.v2ex.co/mcZ74Zv2l.gif" class="kg-image" alt="mcZ74Zv2l" width="788" height="634" loading="lazy"></figure><p>这样就测试通过了，其实代码量不大，原理也很简单。大家如果自己有较短的域名的话，上线后会让链接变得更短、更美观，这样一个属于我们自己短链接生成平台就开发完成了。下面附上源码，执行npm install 即可自动安装所有依赖，如果大家有问题，可通过评论区告诉我。</p><p><a href="https://files.cnblogs.com/files/powertoolsteam/shorturl.zip">源码下载&gt;&gt;</a></p><p>我们在<a href="https://www.grapecity.com.cn/">葡萄城官网</a>发布更多文章，欢迎阅读！</p> ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
