<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Node.js on Refrain</title><link>https://refrain-blog.pages.dev/tags/node.js/</link><description>Recent content in Node.js on Refrain</description><generator>Hugo -- gohugo.io</generator><language>zh-cn</language><lastBuildDate>Fri, 09 Oct 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://refrain-blog.pages.dev/tags/node.js/index.xml" rel="self" type="application/rss+xml"/><item><title>先把 JavaScript 练顺，再学 TypeScript：一条 Node.js 学习路线</title><link>https://refrain-blog.pages.dev/posts/javascript-to-typescript-nodejs-learning-path/</link><pubDate>Fri, 09 Oct 2026 00:00:00 +0000</pubDate><guid>https://refrain-blog.pages.dev/posts/javascript-to-typescript-nodejs-learning-path/</guid><description>&lt;p&gt;学 JavaScript 时，很多人会遇到同一类卡点：代码能照着写，却说不清变量为什么在这里访问不到；回调里的 &lt;code&gt;this&lt;/code&gt; 为什么变了；异步错误该在哪里处理；几层循环能不能换成更清楚的数据处理流程。&lt;/p&gt;
&lt;p&gt;这些问题通常不是多背几条语法就能解决的。更顺的学习顺序是：先理解 JavaScript 如何管理变量、函数和数据，再掌握异步编程；接着用 TypeScript 描述数据结构和函数边界；最后把它们用在 Node.js 后端项目里。&lt;/p&gt;
&lt;p&gt;这篇文章会按这条顺序展开。刚开始学编程也可以读；如果变量、条件、循环和函数还不熟，就把每一阶段的练习时间拉长，不必追赶日历上的天数。&lt;/p&gt;
&lt;p&gt;先分清三个名字：JavaScript 是编程语言；Node.js 让 JavaScript 能在浏览器之外运行，因此可以用它写后端服务；TypeScript 在 JavaScript 上增加类型检查，开发时帮助发现一部分错误，最终运行的通常仍是生成的 JavaScript。&lt;/p&gt;
&lt;h2 id="先看完整路线"&gt;先看完整路线&lt;/h2&gt;
&lt;p&gt;这条路线分成四段：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;阶段&lt;/th&gt;
&lt;th&gt;时间参考&lt;/th&gt;
&lt;th&gt;学习重点&lt;/th&gt;
&lt;th&gt;阶段目标&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;现代 JavaScript&lt;/td&gt;
&lt;td&gt;3 天&lt;/td&gt;
&lt;td&gt;作用域、箭头函数、异步、数组方法&lt;/td&gt;
&lt;td&gt;能读懂并写出清楚的现代 JS 代码&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TypeScript&lt;/td&gt;
&lt;td&gt;3 天&lt;/td&gt;
&lt;td&gt;基础类型、对象类型、泛型、类&lt;/td&gt;
&lt;td&gt;能给函数和数据结构加上合适的类型&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Node.js 与 Koa&lt;/td&gt;
&lt;td&gt;4 天&lt;/td&gt;
&lt;td&gt;模块、事件循环、文件与 HTTP、路由和中间件&lt;/td&gt;
&lt;td&gt;能开发一个简单的后端 API&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;项目与部署&lt;/td&gt;
&lt;td&gt;4 天&lt;/td&gt;
&lt;td&gt;数据库、验证、认证、日志和部署&lt;/td&gt;
&lt;td&gt;完成并运行一个小型服务&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;这是一份约两周的高强度参考计划，适合已经会一点编程、每天能集中学习几小时的人。第一次接触编程时，按理解程度放慢更有效，计划本身不是考试期限。&lt;/p&gt;
&lt;h2 id="第一段先把现代-javascript-的基本功练扎实"&gt;第一段：先把现代 JavaScript 的基本功练扎实&lt;/h2&gt;
&lt;h3 id="变量作用域与闭包"&gt;变量、作用域与闭包&lt;/h3&gt;
&lt;p&gt;作用域决定变量在哪里能被访问。现代 JavaScript 里，通常优先用 &lt;code&gt;const&lt;/code&gt; 声明不会重新赋值的变量，需要重新赋值时用 &lt;code&gt;let&lt;/code&gt;。&lt;code&gt;var&lt;/code&gt; 有函数作用域和变量提升等旧行为，初学时容易带来意外结果，读旧代码时再专门识别它。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#ebdbb2;background-color:#282828;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#fe8019"&gt;const&lt;/span&gt; siteName &lt;span style="color:#fe8019"&gt;=&lt;/span&gt; &lt;span style="color:#b8bb26"&gt;&amp;#34;Refrain&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#fe8019"&gt;let&lt;/span&gt; visitCount &lt;span style="color:#fe8019"&gt;=&lt;/span&gt; &lt;span style="color:#d3869b"&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;visitCount &lt;span style="color:#fe8019"&gt;+=&lt;/span&gt; &lt;span style="color:#d3869b"&gt;1&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;const&lt;/code&gt; 固定的是变量绑定，不代表对象内容冻结。例如 &lt;code&gt;const user = { name: &amp;quot;小林&amp;quot; }&lt;/code&gt; 之后，&lt;code&gt;user.name&lt;/code&gt; 仍可以修改，只是不能把 &lt;code&gt;user&lt;/code&gt; 重新指向另一个对象。&lt;/p&gt;
&lt;p&gt;闭包可以先理解为：函数记得它创建时周围的变量。它适合保存小段私有状态：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#ebdbb2;background-color:#282828;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#fe8019"&gt;function&lt;/span&gt; createCounter() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#fe8019"&gt;let&lt;/span&gt; count &lt;span style="color:#fe8019"&gt;=&lt;/span&gt; &lt;span style="color:#d3869b"&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#fe8019"&gt;return&lt;/span&gt; () =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; count &lt;span style="color:#fe8019"&gt;+=&lt;/span&gt; &lt;span style="color:#d3869b"&gt;1&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#fe8019"&gt;return&lt;/span&gt; count;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; };
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#fe8019"&gt;const&lt;/span&gt; nextCount &lt;span style="color:#fe8019"&gt;=&lt;/span&gt; createCounter();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;nextCount(); &lt;span style="color:#928374;font-style:italic"&gt;// 1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;nextCount(); &lt;span style="color:#928374;font-style:italic"&gt;// 2
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;练习时，可以观察 &lt;code&gt;let&lt;/code&gt; 和 &lt;code&gt;var&lt;/code&gt; 在循环中的区别，再试着写一个计数器，确认外部代码只能通过返回的函数改变计数。&lt;/p&gt;
&lt;h3 id="箭头函数与-this"&gt;箭头函数与 &lt;code&gt;this&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;箭头函数让短函数和回调更紧凑：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#ebdbb2;background-color:#282828;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#fe8019"&gt;const&lt;/span&gt; square &lt;span style="color:#fe8019"&gt;=&lt;/span&gt; number =&amp;gt; number &lt;span style="color:#fe8019"&gt;*&lt;/span&gt; number;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#fe8019"&gt;const&lt;/span&gt; labels &lt;span style="color:#fe8019"&gt;=&lt;/span&gt; [&lt;span style="color:#d3869b"&gt;1&lt;/span&gt;, &lt;span style="color:#d3869b"&gt;2&lt;/span&gt;, &lt;span style="color:#d3869b"&gt;3&lt;/span&gt;].map(number =&amp;gt; &lt;span style="color:#b8bb26"&gt;`第 &lt;/span&gt;&lt;span style="color:#b8bb26"&gt;${&lt;/span&gt;number&lt;span style="color:#b8bb26"&gt;}&lt;/span&gt;&lt;span style="color:#b8bb26"&gt; 项`&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;它与普通函数有一个重要区别：箭头函数不会创建自己的 &lt;code&gt;this&lt;/code&gt;，而是沿用外层的 &lt;code&gt;this&lt;/code&gt;。因此，它很适合在回调里保留外层上下文；定义对象自身的方法时，通常使用方法简写，让 &lt;code&gt;this&lt;/code&gt; 指向调用它的对象：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#ebdbb2;background-color:#282828;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#fe8019"&gt;const&lt;/span&gt; user &lt;span style="color:#fe8019"&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; name&lt;span style="color:#fe8019"&gt;:&lt;/span&gt; &lt;span style="color:#b8bb26"&gt;&amp;#34;小林&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; greet() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#fe8019"&gt;return&lt;/span&gt; &lt;span style="color:#b8bb26"&gt;`你好，我是&lt;/span&gt;&lt;span style="color:#b8bb26"&gt;${&lt;/span&gt;&lt;span style="color:#fe8019"&gt;this&lt;/span&gt;.name&lt;span style="color:#b8bb26"&gt;}&lt;/span&gt;&lt;span style="color:#b8bb26"&gt;`&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;学习目标不是把所有函数都改成箭头函数，而是能根据 &lt;code&gt;this&lt;/code&gt; 的需要选择写法。&lt;/p&gt;
&lt;h3 id="promiseasyncawait-与错误处理"&gt;Promise、&lt;code&gt;async/await&lt;/code&gt; 与错误处理&lt;/h3&gt;
&lt;p&gt;读取文件、访问接口等操作不会立刻得到结果。Promise 用来表示异步操作最终成功或失败的结果。&lt;code&gt;async/await&lt;/code&gt; 是组织这类流程的一种更直观的写法：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#ebdbb2;background-color:#282828;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#fe8019"&gt;async&lt;/span&gt; &lt;span style="color:#fe8019"&gt;function&lt;/span&gt; loadUser(id) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#fe8019"&gt;const&lt;/span&gt; response &lt;span style="color:#fe8019"&gt;=&lt;/span&gt; &lt;span style="color:#fe8019"&gt;await&lt;/span&gt; fetch(&lt;span style="color:#b8bb26"&gt;`/api/users/&lt;/span&gt;&lt;span style="color:#b8bb26"&gt;${&lt;/span&gt;id&lt;span style="color:#b8bb26"&gt;}&lt;/span&gt;&lt;span style="color:#b8bb26"&gt;`&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#fe8019"&gt;if&lt;/span&gt; (&lt;span style="color:#fe8019"&gt;!&lt;/span&gt;response.ok) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#fe8019"&gt;throw&lt;/span&gt; &lt;span style="color:#fe8019"&gt;new&lt;/span&gt; &lt;span style="color:#fabd2f"&gt;Error&lt;/span&gt;(&lt;span style="color:#b8bb26"&gt;&amp;#34;用户信息读取失败&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#fe8019"&gt;return&lt;/span&gt; response.json();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;loadUser(&lt;span style="color:#d3869b"&gt;1&lt;/span&gt;).&lt;span style="color:#fe8019"&gt;catch&lt;/span&gt;(error =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; console.error(error);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;});
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;await&lt;/code&gt; 会暂停当前这个 &lt;code&gt;async&lt;/code&gt; 函数，等待结果；它不会让整个 JavaScript 程序停下来。顺序依赖的操作可以一个接一个 &lt;code&gt;await&lt;/code&gt;；彼此独立的操作则可以考虑用 &lt;code&gt;Promise.all&lt;/code&gt; 并行等待。无论用哪种写法，都要想清楚失败时由谁处理。&lt;/p&gt;
&lt;h3 id="用数组方法表达数据处理"&gt;用数组方法表达数据处理&lt;/h3&gt;
&lt;p&gt;数组方法能把“转换、筛选、查找、汇总”分别写清楚：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;map&lt;/code&gt;：把每一项转换成新值。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;filter&lt;/code&gt;：留下符合条件的项。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;find&lt;/code&gt;：找出第一个符合条件的项；找不到时结果是 &lt;code&gt;undefined&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;reduce&lt;/code&gt;：把多项累积成一个结果，例如总数或分组对象。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;比如，计算已付款订单的总额：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#ebdbb2;background-color:#282828;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#fe8019"&gt;const&lt;/span&gt; paidTotal &lt;span style="color:#fe8019"&gt;=&lt;/span&gt; orders
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .filter(order =&amp;gt; order.paid)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .reduce((sum, order) =&amp;gt; sum &lt;span style="color:#fe8019"&gt;+&lt;/span&gt; order.amount, &lt;span style="color:#d3869b"&gt;0&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;链式调用能让数据流一眼可见，但不必为了“函数式”把简单逻辑绕复杂。把步骤命名清楚、方便检查，比少写几行更重要。&lt;/p&gt;
&lt;p&gt;第一阶段可以用小任务收尾：学生成绩管理、商品库存检查、异步数据处理，以及对输入数据做验证和转换。每次练习先让程序跑起来，再修改输入、观察结果，并尝试处理边界情况。&lt;/p&gt;
&lt;h2 id="第二段用-typescript-把数据说清楚"&gt;第二段：用 TypeScript 把数据说清楚&lt;/h2&gt;
&lt;p&gt;TypeScript 在 JavaScript 的基础上加入类型检查。开发时，编辑器和编译器可以提前指出一部分类型不匹配的问题；最终运行的仍是 JavaScript。类型能帮助发现错误，但它不能代替测试，也不会自动验证来自网络的原始数据。&lt;/p&gt;
&lt;h3 id="从基础类型开始"&gt;从基础类型开始&lt;/h3&gt;
&lt;p&gt;先认识常用类型：&lt;code&gt;string&lt;/code&gt;、&lt;code&gt;number&lt;/code&gt;、&lt;code&gt;boolean&lt;/code&gt;、数组、联合类型和可选属性。下面的 &lt;code&gt;User&lt;/code&gt; 描述了一个用户对象：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#ebdbb2;background-color:#282828;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-ts" data-lang="ts"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#fe8019"&gt;interface&lt;/span&gt; User {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; id: &lt;span style="color:#fabd2f"&gt;number&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; name: &lt;span style="color:#fabd2f"&gt;string&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; email?: &lt;span style="color:#fabd2f"&gt;string&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#fe8019"&gt;function&lt;/span&gt; formatUser(user: &lt;span style="color:#fabd2f"&gt;User&lt;/span&gt;)&lt;span style="color:#fe8019"&gt;:&lt;/span&gt; &lt;span style="color:#fabd2f"&gt;string&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#fe8019"&gt;return&lt;/span&gt; &lt;span style="color:#b8bb26"&gt;`&lt;/span&gt;&lt;span style="color:#b8bb26"&gt;${&lt;/span&gt;user.name&lt;span style="color:#b8bb26"&gt;}&lt;/span&gt;&lt;span style="color:#b8bb26"&gt;（编号：&lt;/span&gt;&lt;span style="color:#b8bb26"&gt;${&lt;/span&gt;user.id&lt;span style="color:#b8bb26"&gt;}&lt;/span&gt;&lt;span style="color:#b8bb26"&gt;）`&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;问号表示 &lt;code&gt;email&lt;/code&gt; 可以不存在。对确实允许多种形态的值，可以用联合类型，例如 &lt;code&gt;number | string&lt;/code&gt;。拿到联合类型后，先判断它是哪一种，再使用对应操作。&lt;/p&gt;
&lt;p&gt;刚开始不要把 &lt;code&gt;any&lt;/code&gt; 当成省事的默认值：它会让类型检查失去作用。不确定数据是什么时，&lt;code&gt;unknown&lt;/code&gt; 通常更合适，因为使用前还需要先检查它。&lt;/p&gt;
&lt;h3 id="interfacetype-与泛型"&gt;&lt;code&gt;interface&lt;/code&gt;、&lt;code&gt;type&lt;/code&gt; 与泛型&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;interface&lt;/code&gt; 常用于描述对象有哪些属性；&lt;code&gt;type&lt;/code&gt; 可以给对象类型命名，也能表达联合类型等组合关系：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#ebdbb2;background-color:#282828;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-ts" data-lang="ts"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#fe8019"&gt;interface&lt;/span&gt; Product {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#fe8019"&gt;readonly&lt;/span&gt; id: &lt;span style="color:#fabd2f"&gt;number&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; name: &lt;span style="color:#fabd2f"&gt;string&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; price: &lt;span style="color:#fabd2f"&gt;number&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#fe8019"&gt;type&lt;/span&gt; ProductId &lt;span style="color:#fe8019"&gt;=&lt;/span&gt; &lt;span style="color:#fabd2f"&gt;number&lt;/span&gt; &lt;span style="color:#fe8019"&gt;|&lt;/span&gt; &lt;span style="color:#fabd2f"&gt;string&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;readonly&lt;/code&gt; 表示这个属性创建后不应再被重新赋值。&lt;code&gt;interface&lt;/code&gt; 和 &lt;code&gt;type&lt;/code&gt; 有不少重叠用法，初学时先能读懂、能准确表达数据，再逐渐了解扩展方式和适用场景。&lt;/p&gt;
&lt;p&gt;泛型让函数在复用时保留传入的数据类型。可以把它想成一个带类型标签的容器：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#ebdbb2;background-color:#282828;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-ts" data-lang="ts"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#fe8019"&gt;function&lt;/span&gt; first&amp;lt;&lt;span style="color:#fb4934"&gt;T&lt;/span&gt;&amp;gt;(items: &lt;span style="color:#fabd2f"&gt;T&lt;/span&gt;[])&lt;span style="color:#fe8019"&gt;:&lt;/span&gt; T &lt;span style="color:#fe8019"&gt;|&lt;/span&gt; &lt;span style="color:#fe8019"&gt;undefined&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#fe8019"&gt;return&lt;/span&gt; items[&lt;span style="color:#d3869b"&gt;0&lt;/span&gt;];
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#fe8019"&gt;const&lt;/span&gt; firstName &lt;span style="color:#fe8019"&gt;=&lt;/span&gt; first([&lt;span style="color:#b8bb26"&gt;&amp;#34;小林&amp;#34;&lt;/span&gt;, &lt;span style="color:#b8bb26"&gt;&amp;#34;小周&amp;#34;&lt;/span&gt;]); &lt;span style="color:#928374;font-style:italic"&gt;// string | undefined
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#fe8019"&gt;const&lt;/span&gt; firstScore &lt;span style="color:#fe8019"&gt;=&lt;/span&gt; first([&lt;span style="color:#d3869b"&gt;88&lt;/span&gt;, &lt;span style="color:#d3869b"&gt;95&lt;/span&gt;]); &lt;span style="color:#928374;font-style:italic"&gt;// number | undefined
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;同一个函数能处理不同类型，同时让调用者知道返回值是什么。像 &lt;code&gt;Array&amp;lt;string&amp;gt;&lt;/code&gt; 和 &lt;code&gt;Promise&amp;lt;User&amp;gt;&lt;/code&gt; 这样的写法，也是在说明容器里装着什么类型的数据。&lt;/p&gt;
&lt;h3 id="类与综合练习"&gt;类与综合练习&lt;/h3&gt;
&lt;p&gt;基础类型、对象类型和泛型掌握后，再学习类、构造函数、继承、&lt;code&gt;public&lt;/code&gt; / &lt;code&gt;private&lt;/code&gt; / &lt;code&gt;protected&lt;/code&gt; 等访问修饰符。装饰器可以先作为选学内容，不必在入门时急着掌握。&lt;/p&gt;
&lt;p&gt;最有用的综合练习，是把已经写过的 JavaScript 小程序逐步改成 TypeScript：给函数参数和返回值加类型，给 API 请求与响应定义结构，再用类型错误来检查数据是在哪里变得不一致。这样能看见类型系统怎样帮助组织代码，而不只是记住一串语法。&lt;/p&gt;
&lt;p&gt;入门时可以安装 Node.js 的当前长期支持版本，然后在练习目录安装 TypeScript：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#ebdbb2;background-color:#282828;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;npm init -y
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;npm install --save-dev typescript
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;npx tsc --init
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;npx tsc
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;在编辑器里把鼠标停在变量上查看推导出的类型，再根据编译错误回到对应代码检查。先写一个能工作的 JavaScript 版本，再逐步补类型，通常比一开始就试图设计一套很复杂的类型更容易。&lt;/p&gt;
&lt;h2 id="第三四段把语言知识放进后端项目"&gt;第三、四段：把语言知识放进后端项目&lt;/h2&gt;
&lt;p&gt;理解 JavaScript 和 TypeScript 的基础后，就可以继续学 Node.js。建议先弄清模块系统（CommonJS 与 ES Modules）、事件循环、文件系统和 HTTP 模块，再进入 Koa：路由如何接收请求、中间件如何串联、错误如何统一处理。&lt;/p&gt;
&lt;p&gt;之后接上数据库，练习连接、增删改查和数据验证；可以用 MongoDB 与 Mongoose 作为练习组合。可以从 Todo API 或简单博客 API 开始，再逐步加入用户认证、日志、环境变量和部署。Docker 可以放到应用本身能够运行之后学习，这样更容易分辨问题来自代码、配置还是运行环境。&lt;/p&gt;
&lt;p&gt;一个可完成的小项目，可以包含这些部分：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;设计几个 REST API 路由，并说明每个请求接收什么、返回什么。&lt;/li&gt;
&lt;li&gt;把请求参数、数据库记录和响应结果分别定义成类型。&lt;/li&gt;
&lt;li&gt;加入必要的输入校验和统一错误处理。&lt;/li&gt;
&lt;li&gt;将配置放入环境变量，记录必要的运行日志。&lt;/li&gt;
&lt;li&gt;本地运行并验证，再学习如何部署。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;类型定义能帮助代码保持一致；处理外部输入时，仍要在运行时校验。客户端发来的 JSON 不会因为你写了一个 TypeScript 类型就自动变得可信。&lt;/p&gt;
&lt;h2 id="怎样判断自己学会了"&gt;怎样判断自己学会了&lt;/h2&gt;
&lt;p&gt;不要只用“看完了几节”衡量进度。可以用下面这些任务自测：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;能说明 &lt;code&gt;let&lt;/code&gt;、&lt;code&gt;const&lt;/code&gt; 和 &lt;code&gt;var&lt;/code&gt; 的主要区别，并读懂一个闭包计数器。&lt;/li&gt;
&lt;li&gt;能解释箭头函数的 &lt;code&gt;this&lt;/code&gt; 从哪里来，知道什么时候该用普通对象方法。&lt;/li&gt;
&lt;li&gt;能用 &lt;code&gt;async/await&lt;/code&gt; 写异步流程，并处理失败情况。&lt;/li&gt;
&lt;li&gt;能用 &lt;code&gt;map&lt;/code&gt;、&lt;code&gt;filter&lt;/code&gt;、&lt;code&gt;find&lt;/code&gt; 和 &lt;code&gt;reduce&lt;/code&gt; 完成简单的数据转换与统计。&lt;/li&gt;
&lt;li&gt;能给函数参数、返回值、对象和 API 数据定义类型。&lt;/li&gt;
&lt;li&gt;能看懂 &lt;code&gt;Promise&amp;lt;User&amp;gt;&lt;/code&gt; 这样的类型，并写出一个简单泛型函数。&lt;/li&gt;
&lt;li&gt;能把这些知识组合成一个小 API，并说明数据从请求到响应经过了什么步骤。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;学习时可以重复一个简单循环：先读一个例子，猜它会输出什么；再亲手运行，修改输入，观察差异；遇到错误时读错误信息并定位原因；最后用自己的话记下“我原来误解了什么”。每天写几行真正运行过的代码，比一次读完很多说明更容易留下长期记忆。&lt;/p&gt;
&lt;h2 id="延伸阅读"&gt;延伸阅读&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript"&gt;MDN JavaScript 指南&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.typescriptlang.org/docs/"&gt;TypeScript 官方文档&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nodejs.org/docs/latest/api/"&gt;Node.js 官方文档&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://koajs.com/"&gt;Koa 官方文档&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;从 JavaScript 到 TypeScript，再到 Node.js 后端，不必试图一次学完所有内容。先把变量、函数、异步和数组处理练熟，再用类型把代码边界说明白，最后通过一个小项目把知识连起来。能解释自己的代码、能定位它的问题，才是这条路线真正要到达的地方。&lt;/p&gt;</description></item></channel></rss>