<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Posts on Refrain</title><link>https://refrain-blog.pages.dev/posts/</link><description>Recent content in Posts 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/posts/index.xml" rel="self" type="application/rss+xml"/><item><title>从 Python 基础到 FastAPI：一步步搭出可靠的后端 API</title><link>https://refrain-blog.pages.dev/posts/python-to-fastapi-backend-learning-path/</link><pubDate>Fri, 09 Oct 2026 00:00:00 +0000</pubDate><guid>https://refrain-blog.pages.dev/posts/python-to-fastapi-backend-learning-path/</guid><description>&lt;p&gt;第一次接触 FastAPI，几行代码就能做出一个接口。但真正的后端服务还要回答更多问题：请求数据不对怎么办？数据怎样存进数据库？用户如何登录？接口怎样测试？应用怎样安全地部署？&lt;/p&gt;
&lt;p&gt;把这些内容按顺序学，会比一上来复制一个完整项目更容易理解。本文从 Python 基础开始，沿着“接收请求、验证数据、处理业务、保存数据、测试、部署”的路线，介绍搭建后端 API 时需要掌握的知识。&lt;/p&gt;
&lt;h2 id="先补够-python-基础"&gt;先补够 Python 基础&lt;/h2&gt;
&lt;p&gt;如果这是你第一次写 Python，先熟悉列表、字典、循环、条件判断、函数和异常处理。FastAPI 的接口经常接收和返回列表、字典，也会用函数参数表达请求数据。&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-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;books &lt;span style="color:#fe8019"&gt;=&lt;/span&gt; [
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&lt;span style="color:#b8bb26"&gt;&amp;#34;title&amp;#34;&lt;/span&gt;: &lt;span style="color:#b8bb26"&gt;&amp;#34;海边的卡夫卡&amp;#34;&lt;/span&gt;, &lt;span style="color:#b8bb26"&gt;&amp;#34;author&amp;#34;&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 style="color:#b8bb26"&gt;&amp;#34;title&amp;#34;&lt;/span&gt;: &lt;span style="color:#b8bb26"&gt;&amp;#34;挪威的森林&amp;#34;&lt;/span&gt;, &lt;span style="color:#b8bb26"&gt;&amp;#34;author&amp;#34;&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 style="color:#b8bb26"&gt;&amp;#34;title&amp;#34;&lt;/span&gt;: &lt;span style="color:#b8bb26"&gt;&amp;#34;活着&amp;#34;&lt;/span&gt;, &lt;span style="color:#b8bb26"&gt;&amp;#34;author&amp;#34;&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;author &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;matches &lt;span style="color:#fe8019"&gt;=&lt;/span&gt; [book &lt;span style="color:#fe8019"&gt;for&lt;/span&gt; book &lt;span style="color:#fe8019"&gt;in&lt;/span&gt; books &lt;span style="color:#fe8019"&gt;if&lt;/span&gt; book[&lt;span style="color:#b8bb26"&gt;&amp;#34;author&amp;#34;&lt;/span&gt;] &lt;span style="color:#fe8019"&gt;==&lt;/span&gt; author]
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;如果这段代码还看不懂，就先练习“遍历每一项、判断条件、把符合条件的项收集起来”。理解这些基础后，阅读 API 代码会轻松很多。&lt;/p&gt;
&lt;h2 id="按功能递进学习"&gt;按功能递进学习&lt;/h2&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;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1. Python 基础&lt;/td&gt;
&lt;td&gt;数据类型、循环、函数、异常、对象&lt;/td&gt;
&lt;td&gt;能读懂接口里的数据处理代码&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2. FastAPI 入门&lt;/td&gt;
&lt;td&gt;应用启动、路由、路径参数、查询参数&lt;/td&gt;
&lt;td&gt;能接收请求并返回 JSON&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3. 请求和验证&lt;/td&gt;
&lt;td&gt;HTTP 方法、状态码、Pydantic 模型、错误响应&lt;/td&gt;
&lt;td&gt;能检查输入并给调用者清楚的结果&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4. 数据库&lt;/td&gt;
&lt;td&gt;SQL、SQLAlchemy 或 SQLModel、会话、关系、迁移&lt;/td&gt;
&lt;td&gt;重启服务后数据仍然存在&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5. 安全和可靠性&lt;/td&gt;
&lt;td&gt;密码哈希、登录、JWT、权限、日志、中间件&lt;/td&gt;
&lt;td&gt;保护接口并定位运行问题&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6. 实际功能&lt;/td&gt;
&lt;td&gt;文件、邮件、后台任务、WebSocket&lt;/td&gt;
&lt;td&gt;为应用增加常见的后端能力&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7. 测试和部署&lt;/td&gt;
&lt;td&gt;API 测试、Docker、环境变量、反向代理、HTTPS&lt;/td&gt;
&lt;td&gt;能验证并运行一个完整服务&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;每学完一项，就用一个小功能把它串起来。比如书籍 API 可以先返回固定数据，再增加请求验证，然后接数据库和登录权限，最后补测试和部署。&lt;/p&gt;
&lt;h2 id="从最小接口理解-fastapi"&gt;从最小接口理解 FastAPI&lt;/h2&gt;
&lt;p&gt;FastAPI 会根据函数签名识别路径参数、查询参数和请求体，也会利用类型信息进行验证并生成 API 文档：&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-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#fe8019"&gt;from&lt;/span&gt; fastapi &lt;span style="color:#fe8019"&gt;import&lt;/span&gt; FastAPI
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#fe8019"&gt;from&lt;/span&gt; pydantic &lt;span style="color:#fe8019"&gt;import&lt;/span&gt; BaseModel, Field
&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;app &lt;span style="color:#fe8019"&gt;=&lt;/span&gt; FastAPI()
&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;class&lt;/span&gt; BookCreate(BaseModel):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; title: &lt;span style="color:#fabd2f"&gt;str&lt;/span&gt; &lt;span style="color:#fe8019"&gt;=&lt;/span&gt; Field(min_length&lt;span style="color:#fe8019"&gt;=&lt;/span&gt;&lt;span style="color:#d3869b"&gt;1&lt;/span&gt;, max_length&lt;span style="color:#fe8019"&gt;=&lt;/span&gt;&lt;span style="color:#d3869b"&gt;120&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; author: &lt;span style="color:#fabd2f"&gt;str&lt;/span&gt; &lt;span style="color:#fe8019"&gt;=&lt;/span&gt; Field(min_length&lt;span style="color:#fe8019"&gt;=&lt;/span&gt;&lt;span style="color:#d3869b"&gt;1&lt;/span&gt;, max_length&lt;span style="color:#fe8019"&gt;=&lt;/span&gt;&lt;span style="color:#d3869b"&gt;80&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;@app.post(&lt;span style="color:#b8bb26"&gt;&amp;#34;/books&amp;#34;&lt;/span&gt;, status_code&lt;span style="color:#fe8019"&gt;=&lt;/span&gt;&lt;span style="color:#d3869b"&gt;201&lt;/span&gt;)
&lt;/span&gt;&lt;/span&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;def&lt;/span&gt; &lt;span style="color:#fabd2f"&gt;create_book&lt;/span&gt;(book: BookCreate):
&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; book&lt;span style="color:#fe8019"&gt;.&lt;/span&gt;model_dump()
&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;@app.get(&lt;span style="color:#b8bb26"&gt;&amp;#34;/books/&lt;/span&gt;&lt;span style="color:#b8bb26"&gt;{book_id}&lt;/span&gt;&lt;span style="color:#b8bb26"&gt;&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;async&lt;/span&gt; &lt;span style="color:#fe8019"&gt;def&lt;/span&gt; &lt;span style="color:#fabd2f"&gt;get_book&lt;/span&gt;(book_id: &lt;span style="color:#fabd2f"&gt;int&lt;/span&gt;, include_summary: &lt;span style="color:#fabd2f"&gt;bool&lt;/span&gt; &lt;span style="color:#fe8019"&gt;=&lt;/span&gt; &lt;span style="color:#fe8019"&gt;False&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;&amp;#34;id&amp;#34;&lt;/span&gt;: book_id, &lt;span style="color:#b8bb26"&gt;&amp;#34;include_summary&amp;#34;&lt;/span&gt;: include_summary}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;第一个路由接收 JSON 请求体，&lt;code&gt;BookCreate&lt;/code&gt; 规定书名和作者的格式；第二个路由从 URL 读取整数路径参数，也可以读取可选的查询参数。这里为了演示没有保存数据，生产项目需要把业务数据交给数据库。&lt;/p&gt;
&lt;p&gt;理解一个接口时，可以按这条链路往下看：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;路由根据 URL 和 HTTP 方法匹配处理函数。&lt;/li&gt;
&lt;li&gt;FastAPI 解析路径、查询参数和请求体。&lt;/li&gt;
&lt;li&gt;Pydantic 模型检查数据格式，不符合时返回验证错误。&lt;/li&gt;
&lt;li&gt;处理函数调用业务逻辑和数据库操作。&lt;/li&gt;
&lt;li&gt;FastAPI 将结果序列化为响应，并返回状态码和响应头。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;路径参数适合标识资源，例如 &lt;code&gt;/books/42&lt;/code&gt;；查询参数适合筛选或控制结果，例如 &lt;code&gt;/books?author=余华&lt;/code&gt;。创建资源通常用 &lt;code&gt;POST&lt;/code&gt;，读取用 &lt;code&gt;GET&lt;/code&gt;，更新用 &lt;code&gt;PUT&lt;/code&gt; 或 &lt;code&gt;PATCH&lt;/code&gt;，删除用 &lt;code&gt;DELETE&lt;/code&gt;。状态码也要表达清楚：成功创建常用 &lt;code&gt;201&lt;/code&gt;，找不到资源用 &lt;code&gt;404&lt;/code&gt;，输入无效则返回相应的客户端错误。&lt;/p&gt;
&lt;h2 id="从固定数据走到数据库"&gt;从固定数据走到数据库&lt;/h2&gt;
&lt;p&gt;内存中的列表或字典适合教学，但服务重启后数据会消失。接下来需要学习 SQL、数据库表、CRUD 操作和事务，再把数据保存到数据库。&lt;/p&gt;
&lt;p&gt;SQLAlchemy 提供成熟的 ORM 和数据库会话管理；SQLModel 将类型模型、数据验证与 SQLAlchemy 的数据库映射结合起来，能减少部分重复定义。无论选哪种，都要理解数据库会话的生命周期、提交和回滚，以及如何用 Alembic 管理结构迁移。&lt;/p&gt;
&lt;p&gt;随后可以学习表关系和关联查询。以书店为例，一本书可能有多个分类，一个用户可能创建多笔订单。先把实体关系画清楚，再设计表结构和 API，可以避免后续数据模型难以维护。&lt;/p&gt;
&lt;p&gt;FastAPI 的依赖注入适合统一提供数据库会话、当前用户和配置。路由负责接收和返回请求，业务逻辑放在合适的函数或服务层，数据访问也可以独立组织。小练习可以从单文件开始，项目变大后再拆分模块。&lt;/p&gt;
&lt;h2 id="异步错误和用户认证"&gt;异步、错误和用户认证&lt;/h2&gt;
&lt;p&gt;Web 服务常常要等待数据库、文件或其他接口。&lt;code&gt;async def&lt;/code&gt; 和 &lt;code&gt;await&lt;/code&gt; 能帮助程序在等待 I/O 时处理其他请求；它们不会自动加速 CPU 密集型计算。异步函数里如果调用会长时间阻塞的同步操作，仍可能拖慢其他请求。&lt;/p&gt;
&lt;p&gt;异常处理要让调用方得到合适的 HTTP 状态码和错误信息，也要避免把调试堆栈或内部机密直接返回给用户。日志可以记录请求、异常和关键业务过程，但不要把密码、令牌等敏感值写进日志。&lt;/p&gt;
&lt;p&gt;认证和授权是两个概念：认证回答“你是谁”，授权回答“你能做什么”。学习密码哈希、登录流程、JWT 和权限依赖时，应避免明文存储密码，并在每个受保护的接口确认当前用户是否有权操作资源。Pydantic 验证输入格式，不等于身份认证或业务授权。&lt;/p&gt;
&lt;p&gt;跨域资源共享（CORS）也不是登录保护。CORS 控制浏览器页面能否读取其他来源的响应；它不能替代认证、授权、输入校验或服务端访问控制。&lt;/p&gt;
&lt;h2 id="再添加真实应用功能"&gt;再添加真实应用功能&lt;/h2&gt;
&lt;p&gt;基础 API 和数据库稳定后，可以按项目需要逐项加入文件上传、邮件发送、后台任务和 WebSocket：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;文件上传&lt;/strong&gt;：检查文件大小和允许的类型，并决定文件放在对象存储还是服务器磁盘。不要仅凭用户提供的文件名判断安全性。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;邮件发送&lt;/strong&gt;：把 SMTP 地址、账号和密码放在环境变量中，用邮件验证码或重置密码流程练习。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;后台任务&lt;/strong&gt;：适合发送通知、处理小型耗时工作。可靠的长任务和定时任务通常还需要队列、重试和监控。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;WebSocket&lt;/strong&gt;：适合聊天和实时通知。要考虑连接鉴权、断线重连和多实例之间的消息同步。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;中间件&lt;/strong&gt;：可用于请求日志、耗时统计和跨域策略；每加一层，都要确认它会怎样影响请求和响应。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;先做一个最小版本，再处理失败、重复请求和并发等边界情况。功能不是越多越好，能说明每个模块解决什么问题更重要。&lt;/p&gt;
&lt;h2 id="测试和部署是学习的一部分"&gt;测试和部署是学习的一部分&lt;/h2&gt;
&lt;p&gt;API 测试要覆盖成功请求、验证失败、未登录访问、没有权限和资源不存在等情况。涉及数据库时，应准备可重复的测试数据，并确保测试不会误操作线上数据库。&lt;/p&gt;
&lt;p&gt;部署前，先区分开发和生产配置。开发时可以用自动重载；生产环境要妥善设置密钥、数据库连接、CORS 和日志。Docker 能把运行环境打包，反向代理可以处理 HTTPS 和请求转发，GitHub Actions 等 CI/CD 工具可以自动执行检查和发布。&lt;/p&gt;
&lt;p&gt;秘密值应通过环境变量或受控的密钥管理传入，避免提交到 Git 仓库。即使仓库是私有的，也不要把真实密码、令牌或生产密钥放进版本历史。&lt;/p&gt;
&lt;h2 id="一个适合练习的项目路线"&gt;一个适合练习的项目路线&lt;/h2&gt;
&lt;p&gt;可以从简单的书籍 API 开始，按下面的顺序迭代：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;使用内存数据实现查询和新增接口。&lt;/li&gt;
&lt;li&gt;定义 Pydantic 请求模型，加入字段校验和错误处理。&lt;/li&gt;
&lt;li&gt;接入数据库，完成创建、查询、修改和删除。&lt;/li&gt;
&lt;li&gt;增加用户注册、登录和权限检查。&lt;/li&gt;
&lt;li&gt;为关键路由和数据库逻辑编写测试。&lt;/li&gt;
&lt;li&gt;使用 Docker 部署，配置环境变量、迁移、日志和健康检查。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;每一步都可以运行、观察、修改，再继续下一步。遇到问题时，先看请求数据、响应状态和日志；不要只复制一段“能跑”的配置，却不知道它为什么有效。&lt;/p&gt;
&lt;p&gt;当你能解释请求从进入路由到写入数据库、再返回响应的过程，能用测试重现错误，也能把服务部署到一个干净环境里，这条学习路线就已经完成了最重要的部分。&lt;/p&gt;
&lt;h2 id="官方资料"&gt;官方资料&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.python.org/3/tutorial/"&gt;Python 教程&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://fastapi.tiangolo.com/tutorial/"&gt;FastAPI 官方教程&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.pydantic.dev/latest/"&gt;Pydantic 文档&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://sqlmodel.tiangolo.com/tutorial/"&gt;SQLModel 教程&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><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>