feat(docs): enhance JavaScript runtime and browser-as-os content
refactor(demos): improve variable box, scope, and type annotation demos style(demos): update visual styles and animations for better UX docs(browser-as-os): restructure content with tables and practical examples feat(demos): add new TypeScript and runtime environment demos
This commit is contained in:
@@ -3,6 +3,22 @@
|
||||
**为什么有些网页流畅如丝,有些却卡成PPT?** 浏览器是怎么把一堆HTML、CSS、JavaScript代码变成你眼前看到的网页的?本章将带你深入浏览器的"车间",理解它的工作流程,从而写出性能更好的网页。
|
||||
:::
|
||||
|
||||
**这篇文章会带你学什么?**
|
||||
|
||||
| 章节 | 内容 | 学完能干嘛 |
|
||||
|-----|------|-----------|
|
||||
| **第 1 章** | 为什么要理解渲染管线 | 理解性能优化的必要性 |
|
||||
| **第 2 章** | 渲染管线的五个阶段 | 掌握浏览器渲染的基本流程 |
|
||||
| **第 3 章** | 构建DOM树和CSSOM树 | 理解HTML和CSS如何被解析 |
|
||||
| **第 4 章** | 构建渲染树 | 知道哪些元素会被渲染 |
|
||||
| **第 5 章** | 布局与重排 | 避免触发昂贵的布局计算 |
|
||||
| **第 6 章** | 绘制与重绘 | 减少不必要的绘制操作 |
|
||||
| **第 7 章** | 合成与GPU加速 | 利用GPU提升动画性能 |
|
||||
| **第 8 章** | 事件循环 | 理解JavaScript的执行机制 |
|
||||
| **第 9 章** | 性能优化实战 | 掌握常用的性能优化技巧 |
|
||||
|
||||
每一章都从"理解原理"开始,不需要你会手写优化代码。遇到性能问题时,随时回来查就行。
|
||||
|
||||
---
|
||||
|
||||
## 1. 为什么要理解"渲染管线"?
|
||||
@@ -933,7 +949,41 @@ lazyImages.forEach(img => imageObserver.observe(img))
|
||||
|
||||
---
|
||||
|
||||
## 10. 总结:渲染管线优化的本质
|
||||
## 10. 你现在应该能识别的性能问题
|
||||
|
||||
理解了浏览器的渲染管线后,你应该能识别以下常见的性能问题:
|
||||
|
||||
| 问题代码 | 问题所在 | 如何描述给AI |
|
||||
|---------|---------|-------------|
|
||||
| `element.style.width = ...` | 在循环中频繁修改宽度 | "这里会触发多次重排,请改用transform或者批量处理" |
|
||||
| `height = element.offsetHeight` | 在写入后立即读取布局属性 | "这是强制同步布局,请分离读写操作" |
|
||||
| `element.className = ...` | 频繁修改class触发样式重新计算 | "用classList.add/remove代替,减少样式计算" |
|
||||
| 动画用`width`/`left` | 触发重排和重绘,性能差 | "改用transform和opacity做动画" |
|
||||
| 给所有元素加`translateZ(0)` | 滥用GPU加速导致内存爆炸 | "只给需要动画的元素开启GPU加速" |
|
||||
| 列表项10000个全渲染 | DOM节点过多导致卡顿 | "实现虚拟滚动,只渲染可见区域" |
|
||||
| scroll事件里直接操作DOM | 触发频率太高导致卡顿 | "用requestAnimationFrame或节流优化" |
|
||||
| `box-shadow`做hover动画 | 复杂的阴影计算很慢 | "改用transform或伪元素,避免动画阴影" |
|
||||
|
||||
**如果你认真读了每一章的"踩坑实录",你还掌握了这些核心概念:**
|
||||
|
||||
- **渲染管线五阶段**:DOM/CSSOM → 渲染树 → 布局 → 绘制 → 合成
|
||||
- **重排 vs 重绘**:重排最昂贵(几何变化),重绘次之(外观变化)
|
||||
- **强制同步布局**:读写交替会导致布局抖动,必须分离
|
||||
- **GPU加速**:transform和opacity由GPU处理,性能最佳
|
||||
- **事件循环**:JavaScript是单线程的,通过任务队列实现异步
|
||||
|
||||
这些概念会帮你快速定位性能瓶颈。
|
||||
|
||||
::: info 💡 遇到性能问题时这样跟AI说
|
||||
- "动画卡顿,检查是否触发了重排或重绘"
|
||||
- "滚动性能差,可能需要节流或requestAnimationFrame"
|
||||
- "列表数据量大时卡顿,需要虚拟滚动"
|
||||
- "频繁修改样式导致性能问题,请用transform优化"
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## 11. 总结:渲染管线优化的本质
|
||||
|
||||
通过本文的学习,我们可以得出以下核心结论:
|
||||
|
||||
@@ -948,7 +998,7 @@ lazyImages.forEach(img => imageObserver.observe(img))
|
||||
|
||||
---
|
||||
|
||||
## 11. 名词对照表
|
||||
## 12. 名词对照表
|
||||
|
||||
| 英文术语 | 中文对照 | 解释 |
|
||||
| :--- | :--- | :--- |
|
||||
|
||||
@@ -1,18 +1,77 @@
|
||||
# 浏览器是一个操作系统
|
||||
|
||||
> **学习指南**:本章节无需编程基础。我们将用**"网购"**的生活化比喻,配合**真实的技术过程**,带你一步步理解浏览器如何将一行网址变成丰富多彩的页面。
|
||||
::: tip 前言
|
||||
你每天都在用浏览器——看视频、刷新闻、在线办公。但你有没有想过:**当你在地址栏输入一个网址并按下回车,背后发生了什么?**
|
||||
|
||||
这篇文章会用**"网购"**的生活化比喻,配合**真实的技术过程**,带你一步步理解浏览器如何将一行网址变成丰富多彩的页面。
|
||||
|
||||
读完这篇,你就能:
|
||||
- 理解从输入网址到显示页面的完整流程
|
||||
- 掌握 URL、DNS、TCP、HTTP 等核心概念
|
||||
- 了解浏览器如何渲染页面
|
||||
- 知道静态网站和动态网站的区别
|
||||
|
||||
**无需编程基础**,只需要你平时网购的经验即可。
|
||||
:::
|
||||
|
||||
**这篇文章会带你学什么?**
|
||||
|
||||
| 章节 | 内容 | 核心概念 |
|
||||
|-----|------|---------|
|
||||
| **第 1 章** | URL 解析 | 网址的结构和作用 |
|
||||
| **第 2 章** | DNS 查询 | 域名如何转换成 IP 地址 |
|
||||
| **第 3 章** | TCP 握手 | 如何建立可靠的连接 |
|
||||
| **第 4 章** | HTTP 通信 | 浏览器和服务器如何对话 |
|
||||
| **第 5 章** | 浏览器渲染 | 代码如何变成画面 |
|
||||
| **第 6 章** | 静态 vs 动态 | 网页内容的生成方式 |
|
||||
|
||||
---
|
||||
|
||||
## 0. 引言:当你按下回车键的那一刻
|
||||
|
||||
想象你正在进行一次**网购**。你需要:
|
||||
::: tip 🤔 核心问题
|
||||
**当你在浏览器输入网址并按下回车,后台发生了什么?** 为什么有的网页打开很快,有的很慢?为什么有时候会出现"找不到服务器"的错误?
|
||||
:::
|
||||
|
||||
1. **填写订单**(选好商品,确认收货地址)
|
||||
2. **系统查找仓库**(根据店铺名找到具体的发货仓库)
|
||||
3. **建立物流通道**(确保仓库正常营业且能发货)
|
||||
4. **仓库发货**(快递员把包裹送上门)
|
||||
5. **拆箱体验**(打开包裹,看到心仪的商品)
|
||||
### 生活比喻:一次网购之旅
|
||||
|
||||
想象你正在进行一次**网购**。整个过程可以分为 5 个步骤:
|
||||
|
||||
<div style="display: flex; gap: 20px; margin: 20px 0;">
|
||||
<div style="flex: 1; padding: 16px; background: var(--vp-c-bg-alt); border-radius: 12px;">
|
||||
|
||||
**🛒 第 1 步:填写订单**
|
||||
选好商品,确认收货地址
|
||||
|
||||
</div>
|
||||
<div style="flex: 1; padding: 16px; background: var(--vp-c-bg-alt); border-radius: 12px;">
|
||||
|
||||
**🗺️ 第 2 步:查找仓库**
|
||||
系统找到具体的发货仓库
|
||||
|
||||
</div>
|
||||
<div style="flex: 1; padding: 16px; background: var(--vp-c-bg-alt); border-radius: 12px;">
|
||||
|
||||
**📞 第 3 步:建立通道**
|
||||
确认仓库营业且能发货
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="display: flex; gap: 20px; margin: 20px 0;">
|
||||
<div style="flex: 1; padding: 16px; background: var(--vp-c-bg-alt); border-radius: 12px;">
|
||||
|
||||
**🚚 第 4 步:仓库发货**
|
||||
快递员把包裹送上门
|
||||
|
||||
</div>
|
||||
<div style="flex: 1; padding: 16px; background: var(--vp-c-bg-alt); border-radius: 12px;">
|
||||
|
||||
**🎁 第 5 步:拆箱体验**
|
||||
打开包裹,看到心仪的商品
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
**访问网页的过程和网购惊人地相似!**
|
||||
|
||||
@@ -20,10 +79,18 @@
|
||||
|
||||
<UrlToBrowserQuickStart />
|
||||
|
||||
::: info 💡 核心启示
|
||||
理解浏览器工作原理的关键是:**把复杂的技术过程映射到熟悉的生活场景**。网购的 5 个步骤完美对应了浏览器访问网页的 5 个技术阶段。
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## 1. 第一步:填写"订单" —— URL 解析
|
||||
|
||||
::: tip 🤔 核心问题
|
||||
**为什么网址要写成这样?** `https://www.example.com:8080/path/page.html?id=123#section` — 这串字符到底有什么含义?
|
||||
:::
|
||||
|
||||
### 生活比喻:填写购物单
|
||||
|
||||
假设你只在订单上写"买鞋子",仓库肯定不知道发哪双。你需要写清楚:
|
||||
@@ -49,12 +116,18 @@
|
||||
|
||||
<UrlParserDemo />
|
||||
|
||||
> **关键理解**:URL 的存在是为了让**人类**能记住和输入。计算机最终需要的是 **IP 地址**(就像快递员最终需要的是具体的仓库地址,而不是"Nike 官方店"这个名字)。
|
||||
::: info 💡 关键理解
|
||||
URL 的存在是为了让**人类**能记住和输入。计算机最终需要的是 **IP 地址**(就像快递员最终需要的是具体的仓库地址,而不是"Nike 官方店"这个名字)。
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## 2. 第二步:查"地址簿" —— DNS 查询
|
||||
|
||||
::: tip 🤔 核心问题
|
||||
**为什么浏览器能找到网站?** 你输入的是人类可读的域名(如 `baidu.com`),但计算机真正需要的是数字地址(IP)。这中间发生了什么?
|
||||
:::
|
||||
|
||||
### 生活比喻:查仓库地址
|
||||
|
||||
你下单写的是"Nike 官方店",但物流系统不知道仓库在哪。它需要查地址簿:
|
||||
@@ -90,12 +163,20 @@
|
||||
|
||||
<DnsLookupDemo />
|
||||
|
||||
> **为什么需要这么多层?** 想象一下如果全世界只有一个地址簿,几十亿人同时查,早就崩溃了。分层设计让每个层级只管理自己的"辖区",既高效又可靠。
|
||||
::: info 💡 为什么需要这么多层?
|
||||
想象一下如果全世界只有一个地址簿,几十亿人同时查,早就崩溃了。分层设计让每个层级只管理自己的"辖区",既高效又可靠。
|
||||
|
||||
这就是互联网设计的核心思想:**分布式系统**。
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## 3. 第三步:打电话确认 —— TCP 三次握手
|
||||
|
||||
::: tip 🤔 核心问题
|
||||
**为什么需要"三次握手"?** 找到服务器地址后,为什么不能直接发送数据?为什么要先进行三次通信?
|
||||
:::
|
||||
|
||||
### 生活比喻:建立物流通道
|
||||
|
||||
假设物流车直接开到仓库,结果:
|
||||
@@ -145,6 +226,10 @@
|
||||
|
||||
## 4. 第四步:"买家"和"商家"的对话 —— HTTP 请求与响应
|
||||
|
||||
::: tip 🤔 核心问题
|
||||
**浏览器和服务器在说什么?** 建立连接后,浏览器如何"告诉"服务器它想要什么?服务器又如何"回应"?
|
||||
:::
|
||||
|
||||
### 生活比喻:仓库发货
|
||||
|
||||
物流车到达仓库:"这是订单(HTTP请求),**我要取回商品(网页 HTML 源代码)!**"
|
||||
@@ -225,6 +310,10 @@ Set-Cookie: user_id=xyz789 ← 设置 Cookie
|
||||
## 5. 第五步:拆开"包裹" —— 浏览器渲染
|
||||
|
||||
::: tip 🤔 核心问题
|
||||
**代码怎么变成画面?** 服务器发来的是枯燥的 HTML/CSS/JavaScript 代码,浏览器如何把它们变成丰富多彩的网页?
|
||||
:::
|
||||
|
||||
### 生活比喻:拆箱与组装
|
||||
|
||||
你终于收到了快递包裹(HTTP 响应),但打开一看,里面不是现成的家具,而是一堆**零件**(HTML)和一本**组装说明书**(CSS)。作为"买家"(浏览器),你需要亲自动手组装:
|
||||
|
||||
@@ -298,14 +387,18 @@ DOM 树 + CSSOM 树 = **渲染树 (Render Tree)**。
|
||||
<BrowserRenderingDemo />
|
||||
|
||||
::: info 💡 你知道吗?
|
||||
>
|
||||
> **布局和绘制**是浏览器最忙碌的时候。网页里的元素越多、结构越复杂,浏览器就需要花更多时间来计算位置和上色。这就是为什么有的复杂网页打开会卡顿的原因。
|
||||
|
||||
**布局和绘制**是浏览器最忙碌的时候。网页里的元素越多、结构越复杂,浏览器就需要花更多时间来计算位置和上色。这就是为什么有的复杂网页打开会卡顿的原因。
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## 5.5 网页是怎么"生成"的?静态网站 vs 动态网站
|
||||
|
||||
::: tip 🤔 核心问题
|
||||
**网页内容从哪里来?** 前面我们讲了浏览器如何渲染页面,但服务器上的 HTML 文件是怎么来的?是提前做好还是现做?
|
||||
:::
|
||||
|
||||
前面我们讲的都是浏览器如何"拆开包裹"——把服务器发来的 HTML/CSS/JS 渲染成页面。但你有没有想过一个问题:**服务器上那个 HTML 文件是怎么来的?**
|
||||
|
||||
答案是:**有两种方式**,这就是静态网站和动态网站的区别。
|
||||
|
||||
@@ -381,6 +474,14 @@ DOM 树 + CSSOM 树 = **渲染树 (Render Tree)**。
|
||||
## 6. 总结:一次完整的"网购"之旅
|
||||
|
||||
::: tip 🎉 学完本章,你应该能
|
||||
- 解释从输入网址到显示页面的完整流程
|
||||
- 理解 URL、DNS、TCP、HTTP 的作用和关系
|
||||
- 知道浏览器如何渲染页面
|
||||
- 区分静态网站和动态网站
|
||||
- 用生活化比喻向他人解释浏览器工作原理
|
||||
:::
|
||||
|
||||
让我们回顾整个旅程:
|
||||
|
||||
| 阶段 | 技术术语 | 网购类比 | 核心任务 | 关键技术 |
|
||||
| ----------- | ---------- | -------- | ------------------ | ------------------------------ |
|
||||
@@ -403,6 +504,13 @@ DOM 树 + CSSOM 树 = **渲染树 (Render Tree)**。
|
||||
这就是互联网的魅力:**复杂的技术,简单的体验**。
|
||||
|
||||
::: info 💡 进阶学习
|
||||
如果你想深入了解某个环节,可以参考:
|
||||
- **API 开发**:[API 简介](./api-intro.md) - 学习如何设计和使用 API
|
||||
- **前端性能**:[前端性能优化](./frontend-performance.md) - 学习如何优化网页加载速度
|
||||
- **浏览器渲染**:[浏览器渲染管道](./browser-rendering-pipeline.md) - 深入了解渲染细节
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## 7. 名词速查表 (Glossary)
|
||||
|
||||
@@ -424,4 +532,13 @@ DOM 树 + CSSOM 树 = **渲染树 (Render Tree)**。
|
||||
---
|
||||
|
||||
::: tip 🎓 恭喜
|
||||
> **恭喜!** 现在当你再次在地址栏输入网址时,你已经能看到屏幕背后的那个忙碌而精彩的数字世界了。
|
||||
现在当你再次在地址栏输入网址并按下回车时,你已经能看到屏幕背后的那个忙碌而精彩的数字世界了。
|
||||
|
||||
你理解了:
|
||||
- 为什么有时候网页打不开(DNS 解析失败、服务器宕机)
|
||||
- 为什么有的网页快、有的慢(网络延迟、服务器性能、页面复杂度)
|
||||
- 浏览器是如何把代码变成画面的(渲染管道)
|
||||
|
||||
**这就是理解技术原理的价值** — 遇到问题时,你能知道从哪里找原因,而不是束手无策。
|
||||
:::
|
||||
:::
|
||||
|
||||
@@ -1,69 +1,101 @@
|
||||
# 前端框架对比(React / Vue / Svelte / Angular)
|
||||
::: tip 🎯 核心问题
|
||||
**为什么网页越来越复杂?前端技术为什么要不断演进?** 这个问题会带你理解从简单网页到现代 Web 应用的技术演变之路。
|
||||
# 前端框架深度指南
|
||||
|
||||
::: tip 前言
|
||||
你已经学会了 HTML、CSS 和 JavaScript 基础,能做出简单的网页了。但随着网页功能越来越复杂,你可能会发现:用原生 JavaScript 写代码变得很难维护,改一处要动很多地方,多人协作时经常冲突。
|
||||
|
||||
这就是我们需要前端框架的原因——它让代码更有条理、更易维护、更高效开发。在 vibecoding 里,AI 会帮你写大部分代码。但你至少得能看懂不同框架的代码风格,知道它们的优缺点,这样 AI 才能帮你选择最合适的技术栈。
|
||||
|
||||
读完这篇,你就能:
|
||||
- 理解前端技术为什么要不断演进
|
||||
- 知道 Vue、React、Svelte、Angular 各有什么特点
|
||||
- 懂得"数据驱动"、"组件化"这些核心概念
|
||||
- 能根据项目选择合适的框架
|
||||
:::
|
||||
|
||||
**这篇文章会带你学什么?**
|
||||
|
||||
| 章节 | 内容 | 学完能干嘛 |
|
||||
|-----|------|-----------|
|
||||
| **第 1 章** | 为什么要关注前端演进 | 明白技术演进是为了解决什么问题 |
|
||||
| **第 2 章** | 静态网页时代 | 了解最早期的网页开发方式 |
|
||||
| **第 3 章** | jQuery 时代 | 理解"命令式"编程的痛点 |
|
||||
| **第 4 章** | Vue/React 时代 | 掌握"声明式"和"数据驱动"思想 |
|
||||
| **第 5 章** | 渲染策略 | 知道 CSR、SSR、SSG 的区别和适用场景 |
|
||||
| **第 6 章** | 工程化工具 | 理解 Webpack、Vite 等构建工具的作用 |
|
||||
|
||||
每一章都从"为什么需要这个技术"开始,让你理解技术演进背后的逻辑。
|
||||
|
||||
---
|
||||
|
||||
## 1. 为什么要关注前端演进史?
|
||||
|
||||
::: tip 🤔 核心问题
|
||||
**为什么网页越来越复杂?前端技术为什么要不断演进?** 这个问题会带你理解从简单网页到现代 Web 应用的技术演变之路。
|
||||
:::
|
||||
|
||||
### 1.1 从"电子海报"到"桌面应用"
|
||||
|
||||
想象一下你在街上看到的**海报**:
|
||||
想象一下你在街上看到的**海报**:
|
||||
|
||||
- ✅ 有内容(文字、图片)
|
||||
- ✅ 有设计(颜色、排版)
|
||||
- ❌ 但你跟它说话,它不会回应
|
||||
- ❌ 你点击某个地方,不会发生什么
|
||||
- ✅ 有内容(文字、图片)
|
||||
- ✅ 有设计(颜色、排版)
|
||||
- ❌ 但你跟它说话,它不会回应
|
||||
- ❌ 你点击某个地方,不会发生什么
|
||||
|
||||
**最早的网页**就是这样的"电子海报":只能看、不能改、内容固定。
|
||||
**最早的网页**就是这样的"电子海报":只能看、不能改、内容固定。
|
||||
|
||||
**现代网页**完全不同了。它们像**桌面应用**(VS Code、Figma):
|
||||
**现代网页**完全不同了。它们像**桌面应用**(VS Code、Figma):
|
||||
|
||||
- ✅ 可以编辑文档、画图、玩游戏
|
||||
- ✅ 实时响应你的每个操作
|
||||
- ✅ 甚至可以离线工作
|
||||
|
||||
**这种转变的核心原因: 网页的功能越来越复杂,需要更高效的技术和开发方式。**
|
||||
**这种转变的核心原因:网页的功能越来越复杂,需要更高效的技术和开发方式。**
|
||||
|
||||
### 1.2 一个生活的比喻:盖房子
|
||||
### 1.2 一个生活的比喻:盖房子
|
||||
|
||||
前端技术的演进,就像盖房子方式的进化:
|
||||
前端技术的演进,就像盖房子方式的进化:
|
||||
|
||||
| 时代 | 🏠 盖房比喻 | 实际特点 | 优缺点 |
|
||||
| --------- | ------------------ | ---------------------------- | --------------------------- |
|
||||
| **2000s** | **贴海报** | 静态网页,写好 HTML 就行 | ✅ 简单 ❌ 不能互动 |
|
||||
| **2010s** | **请工人手动装修** | jQuery 时代,手动操作每个元素 | ✅ 能互动 ❌ 代码乱、难维护 |
|
||||
| **2020s** | **用乐高搭房子** | Vue/React 时代,组件化开发 | ✅ 高效、可维护 ❌ 学习曲线 |
|
||||
| 时代 | 🏠 盖房比喻 | 实际特点 | 优缺点 |
|
||||
|------|-----------|---------|--------|
|
||||
| **2000s** | **贴海报** | 静态网页,写好 HTML 就行 | ✅ 简单 ❌ 不能互动 |
|
||||
| **2010s** | **请工人手动装修** | jQuery 时代,手动操作每个元素 | ✅ 能互动 ❌ 代码乱、难维护 |
|
||||
| **2020s** | **用乐高搭房子** | Vue/React 时代,组件化开发 | ✅ 高效、可维护 ❌ 学习曲线 |
|
||||
|
||||
::: tip 💡 从表格中你能看到什么?
|
||||
::: tip 💡 从表格中你能看到什么?
|
||||
|
||||
**阶段一 → 阶段二**: 从"不能动"到"能动"。这是质的飞跃——网页开始有交互,但代价是代码变得混乱。
|
||||
**阶段一 → 阶段二**:从"不能动"到"能动"。这是质的飞跃——网页开始有交互,但代价是代码变得混乱。
|
||||
|
||||
**阶段二 → 阶段三**: 从"能用"到"好用"。组件化让代码像积木一样可复用,大幅提升开发效率。
|
||||
**阶段二 → 阶段三**:从"能用"到"好用"。组件化让代码像积木一样可复用,大幅提升开发效率。
|
||||
|
||||
**核心思想**: 技术演进不是"为了新而新",而是为了解决上一个阶段的痛点。
|
||||
**核心思想**:技术演进不是"为了新而新",而是为了解决上一个阶段的痛点。
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## 2. 第一阶段:静态网页与"切图"(2000s)
|
||||
---
|
||||
|
||||
## 2. 第一阶段:静态网页与"切图"(2000s)
|
||||
|
||||
::: tip 🤔 核心问题
|
||||
**最早的网页是什么样的?为什么那时候不需要框架?** 理解这个阶段的局限性,才能明白后来技术演进的必要性。
|
||||
:::
|
||||
|
||||
<FrontendEvolutionDemo />
|
||||
|
||||
### 2.1 这个时代是什么样的?
|
||||
### 2.1 这个时代是什么样的?
|
||||
|
||||
**开发方式**:
|
||||
**开发方式**:
|
||||
|
||||
- 写几个 HTML 文件
|
||||
- 内嵌一些 CSS 和 JavaScript
|
||||
- 直接把文件拖到浏览器就能看效果
|
||||
- 上传文件夹到服务器就完成部署
|
||||
|
||||
**特点**:
|
||||
**特点**:
|
||||
|
||||
- ✅ **优点**: 简单直接,没有学习成本,写完就能跑
|
||||
- ❌ **缺点**: 无法实现复杂交互,代码一多就乱
|
||||
- ✅ **优点**:简单直接,没有学习成本,写完就能跑
|
||||
- ❌ **缺点**:无法实现复杂交互,代码一多就乱
|
||||
|
||||
::: details 查看当时的项目结构
|
||||
|
||||
@@ -80,63 +112,69 @@ project/
|
||||
└── images/
|
||||
```
|
||||
|
||||
**遇到的问题**:
|
||||
**遇到的问题**:
|
||||
|
||||
1. **全局变量污染**: 所有变量都在全局命名空间,容易互相覆盖
|
||||
2. **依赖管理混乱**: 必须按正确顺序加载 JS 文件,否则会报错
|
||||
3. **代码难以复用**: 想复用某个功能,只能复制粘贴
|
||||
:::
|
||||
1. **全局变量污染**:所有变量都在全局命名空间,容易互相覆盖
|
||||
2. **依赖管理混乱**:必须按正确顺序加载 JS 文件,否则会报错
|
||||
3. **代码难以复用**:想复用某个功能,只能复制粘贴
|
||||
:::
|
||||
|
||||
### 2.2 "切图"是什么?
|
||||
### 2.2 "切图"是什么?
|
||||
|
||||
<SliceRequestDemo />
|
||||
你可能听说过"切图"这个词。它是早期前端的主要工作:
|
||||
|
||||
你可能听说过"切图"这个词。它是早期前端的主要工作:
|
||||
|
||||
**什么是切图?**
|
||||
**什么是切图?**
|
||||
|
||||
设计师用 Photoshop 设计好页面 → 前端把设计切成小图片 → 用 HTML 把图片拼成页面
|
||||
|
||||
**为什么这么慢?**
|
||||
**为什么这么慢?**
|
||||
|
||||
网页上的每张小图片,浏览器都要发一次**网络请求**。请求越多,加载越慢。
|
||||
网页上的每张小图片,浏览器都要发一次**网络请求**。请求越多,加载越慢。
|
||||
|
||||
::: tip 💡 雪碧图(Sprite)
|
||||
👇 **动手试试看**:观察图片请求对加载性能的影响
|
||||
|
||||
为了减少请求数,出现了"雪碧图"技术:把很多小图合成一张大图。
|
||||
<SliceRequestDemo />
|
||||
|
||||
优点是请求数变少,缺点是制作和维护都很麻烦。
|
||||
::: tip 💡 雪碧图(Sprite)
|
||||
|
||||
这个阶段的教训:**请求太多是性能大敌**。
|
||||
为了减少请求数,出现了"雪碧图"技术:把很多小图合成一张大图。
|
||||
|
||||
优点是请求数变少,缺点是制作和维护都很麻烦。
|
||||
|
||||
这个阶段的教训:**请求太多是性能大敌**。
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## 3. 第二阶段:jQuery 时代 - "手动搬砖"(2010s)
|
||||
---
|
||||
|
||||
### 3.1 为什么需要 jQuery?
|
||||
## 3. 第二阶段:jQuery 时代 - "手动搬砖"(2010s)
|
||||
|
||||
随着网页变复杂,原生 JavaScript 的问题暴露出来:
|
||||
::: tip 🤔 核心问题
|
||||
**为什么需要 jQuery?它解决了什么问题,又带来了什么新问题?** 理解 jQuery 的局限性,才能明白 Vue/React 的价值。
|
||||
:::
|
||||
|
||||
- ❌ **API 繁琐**: 简单的操作也要写很多代码
|
||||
- ❌ **浏览器兼容**: 不同浏览器的 API 不一样,要写很多兼容代码
|
||||
- ❌ **选择器弱**: 找元素很麻烦
|
||||
### 3.1 为什么需要 jQuery?
|
||||
|
||||
**jQuery** 诞生了。它让 JavaScript 变得简单:
|
||||
随着网页变复杂,原生 JavaScript 的问题暴露出来:
|
||||
|
||||
- ❌ **API 繁琐**:简单的操作也要写很多代码
|
||||
- ❌ **浏览器兼容**:不同浏览器的 API 不一样,要写很多兼容代码
|
||||
- ❌ **选择器弱**:找元素很麻烦
|
||||
|
||||
**jQuery** 诞生了。它让 JavaScript 变得简单:
|
||||
|
||||
```javascript
|
||||
// 原生 JavaScript (繁琐)
|
||||
// 原生 JavaScript(繁琐)
|
||||
const element = document.getElementById('title')
|
||||
|
||||
// jQuery (简洁)
|
||||
// jQuery(简洁)
|
||||
const element = $('#title')
|
||||
```
|
||||
|
||||
### 3.2 jQuery 的思路:亲手改页面
|
||||
### 3.2 jQuery 的思路:亲手改页面
|
||||
|
||||
<JQueryVsStateDemo />
|
||||
|
||||
jQuery 的核心思路是**命令式**: 你告诉浏览器"怎么做"。
|
||||
jQuery 的核心思路是**命令式**:你告诉浏览器"怎么做"。
|
||||
|
||||
```javascript
|
||||
// 找到标题元素
|
||||
@@ -149,11 +187,15 @@ $('#submit-btn').attr('disabled', true)
|
||||
$('ul').append('<li>新项目</li>')
|
||||
```
|
||||
|
||||
**问题**: 你需要记住页面上有哪些元素,每次数据变化都要手动更新所有相关元素。
|
||||
**问题**:你需要记住页面上有哪些元素,每次数据变化都要手动更新所有相关元素。
|
||||
|
||||
👇 **动手试试看**:对比 jQuery 和数据驱动的方式
|
||||
|
||||
<JQueryVsStateDemo />
|
||||
|
||||
::: warning ⚠️ jQuery 的痛点
|
||||
|
||||
想象你在做一个购物车:
|
||||
想象你在做一个购物车:
|
||||
|
||||
```javascript
|
||||
// 用户点击"添加到购物车"
|
||||
@@ -165,32 +207,30 @@ function addToCart() {
|
||||
$('#cart-page-count').text(cartCount) // 购物车页面
|
||||
$('#checkout-price').text(calculatePrice()) // 结算按钮
|
||||
|
||||
// 如果漏了一个地方,页面就不一致了!
|
||||
// 如果漏了一个地方,页面就不一致了!
|
||||
}
|
||||
```
|
||||
|
||||
**这就是"手动搬砖"的代价**: 容易出错,难以维护。
|
||||
**这就是"手动搬砖"的代价**:容易出错,难以维护。
|
||||
:::
|
||||
|
||||
### 3.3 移动端普及:响应式设计的出现
|
||||
### 3.3 移动端普及:响应式设计的出现
|
||||
|
||||
这个阶段还有一个重要变化:**手机和平板开始流行**。
|
||||
这个阶段还有一个重要变化:**手机和平板开始流行**。
|
||||
|
||||
<ResponsiveGridDemo />
|
||||
网页必须适配不同屏幕。这需要**响应式布局**:同一套 HTML/CSS,自动根据屏幕宽度变换布局。
|
||||
|
||||
网页必须适配不同屏幕。这需要**响应式布局**: 同一套 HTML/CSS,自动根据屏幕宽度变换布局。
|
||||
|
||||
**响应式布局的核心: 媒体查询 (Media Query)**
|
||||
**响应式布局的核心:媒体查询(Media Query)**
|
||||
|
||||
```css
|
||||
/* 电脑屏幕(大于 640px) */
|
||||
/* 电脑屏幕(大于 640px) */
|
||||
@media (min-width: 640px) {
|
||||
.container {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
/* 手机屏幕(小于 640px) */
|
||||
/* 手机屏幕(小于 640px) */
|
||||
@media (max-width: 640px) {
|
||||
.container {
|
||||
display: block;
|
||||
@@ -198,35 +238,43 @@ function addToCart() {
|
||||
}
|
||||
```
|
||||
|
||||
👇 **动手试试看**:调整浏览器宽度,观察响应式布局的效果
|
||||
|
||||
<ResponsiveGridDemo />
|
||||
|
||||
::: tip 💡 响应式就像"智能相框"
|
||||
|
||||
想象你在不同房间看同一张照片:
|
||||
想象你在不同房间看同一张照片:
|
||||
|
||||
- 在**大客厅**(电脑屏幕),照片可以摆大一些,旁边还能放其他装饰品
|
||||
- 在**小卧室**(手机屏幕),照片需要缩小,其他装饰品要收起来
|
||||
- 在**大客厅**(电脑屏幕),照片可以摆大一些,旁边还能放其他装饰品
|
||||
- 在**小卧室**(手机屏幕),照片需要缩小,其他装饰品要收起来
|
||||
|
||||
**响应式布局**就是"智能相框",它会自动根据房间大小调整展示方式。
|
||||
**响应式布局**就是"智能相框",它会自动根据房间大小调整展示方式。
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## 4. 第三阶段:从"手动搬砖"到"数据驱动"(Vue/React)
|
||||
---
|
||||
|
||||
### 4.1 为什么需要新框架?
|
||||
## 4. 第三阶段:从"手动搬砖"到"数据驱动"(Vue/React)
|
||||
|
||||
jQuery 时代的问题积累到一定程度:
|
||||
::: tip 🤔 核心问题
|
||||
**为什么需要 Vue/React?它们和 jQuery 的本质区别是什么?** 理解"声明式"和"数据驱动",是掌握现代前端框架的关键。
|
||||
:::
|
||||
|
||||
- **代码一多就乱**: 到处都是 DOM 操作,难以维护
|
||||
- **容易出 bug**: 漏更新一个地方,页面就不一致
|
||||
- **协作困难**: 多人修改同一个文件,容易冲突
|
||||
### 4.1 为什么需要新框架?
|
||||
|
||||
**Vue / React** 的核心思路:**只改数据,页面自动更新**。
|
||||
jQuery 时代的问题积累到一定程度:
|
||||
|
||||
### 4.2 Vue/React 的思路:声明式 UI
|
||||
- **代码一多就乱**:到处都是 DOM 操作,难以维护
|
||||
- **容易出 bug**:漏更新一个地方,页面就不一致
|
||||
- **协作困难**:多人修改同一个文件,容易冲突
|
||||
|
||||
<ImperativeVsDeclarativeDemo />
|
||||
**Vue / React** 的核心思路:**只改数据,页面自动更新**。
|
||||
|
||||
**jQuery (命令式)**:
|
||||
### 4.2 Vue/React 的思路:声明式 UI
|
||||
|
||||
**jQuery(命令式)**:
|
||||
|
||||
```javascript
|
||||
// 你要告诉浏览器每一步怎么做
|
||||
@@ -235,7 +283,7 @@ $('#title').css('color', 'red')
|
||||
$('#title').show()
|
||||
```
|
||||
|
||||
**Vue (声明式)**:
|
||||
**Vue(声明式)**:
|
||||
|
||||
```javascript
|
||||
// 你只需告诉浏览器"要显示什么"
|
||||
@@ -248,213 +296,409 @@ data() {
|
||||
}
|
||||
```
|
||||
|
||||
👇 **动手试试看**:对比命令式和声明式的区别
|
||||
|
||||
<ImperativeVsDeclarativeDemo />
|
||||
|
||||
::: tip 💡 命令式 vs 声明式
|
||||
|
||||
就像画一幅画:
|
||||
就像画一幅画:
|
||||
|
||||
- **命令式**: 你告诉画家"拿起笔,蘸红颜料,在坐标(10,10)画一个圈"
|
||||
- **声明式**: 你直接给画家一张照片,"给我画成这样"
|
||||
- **命令式**:你告诉画家"拿起笔,蘸红颜料,在坐标(10,10)画一个圈"
|
||||
- **声明式**:你直接给画家一张照片,"给我画成这样"
|
||||
|
||||
Vue/React 就是"声明式": 你描述"页面长什么样",框架负责"怎么把它画出来"。
|
||||
Vue/React 就是"声明式":你描述"页面长什么样",框架负责"怎么把它画出来"。
|
||||
:::
|
||||
|
||||
### 4.3 组件化:像搭乐高一样写页面
|
||||
### 4.3 组件化:像搭乐高一样写页面
|
||||
|
||||
**Vue / React** 最强大的特性是**组件化**: 把页面拆成一个个独立的"积木"。
|
||||
**Vue / React** 最强大的特性是**组件化**:把页面拆成一个个独立的"积木"。
|
||||
|
||||
想象一下你在搭乐高:
|
||||
想象一下你在搭乐高:
|
||||
|
||||
- 你不需要"从头开始雕刻每一块积木"(从头写 HTML/CSS)
|
||||
- 你只需要"按说明书把积木拼在一起"(把组件组合起来)
|
||||
- 每个积木都是**独立的**,你可以在不同的套装里**重复使用**
|
||||
- 你不需要"从头开始雕刻每一块积木"(从头写 HTML/CSS)
|
||||
- 你只需要"按说明书把积木拼在一起"(把组件组合起来)
|
||||
- 每个积木都是**独立的**,你可以在不同的套装里**重复使用**
|
||||
|
||||
**组件的好处**:
|
||||
**组件的好处**:
|
||||
|
||||
- **复用**: 写一个"商品卡片"组件,可以用 100 次
|
||||
- **封装**: 组件内部的状态不影响别人
|
||||
- **维护**: 修改一个组件,所有用到它的地方都会更新
|
||||
- **复用**:写一个"商品卡片"组件,可以用 100 次
|
||||
- **封装**:组件内部的状态不影响别人
|
||||
- **维护**:修改一个组件,所有用到它的地方都会更新
|
||||
|
||||
### 4.4 SPA:单页应用的诞生
|
||||
::: info 💡 识别技巧
|
||||
- 看到 `<ComponentName />` → 这是一个组件
|
||||
- 看到 `import xxx from './xxx.vue'` → 在导入一个组件
|
||||
- 看到 `props: {...}` → 组件接收的参数
|
||||
- 看到 `emit('xxx')` → 组件向父组件发送事件
|
||||
:::
|
||||
|
||||
### 4.4 SPA:单页应用的诞生
|
||||
|
||||
**Vue / React** 时代还有一个重要变化:**从 MPA 到 SPA**。
|
||||
|
||||
**MPA(Multi-Page Application)**:
|
||||
|
||||
- 点一个链接 → 整页刷新 → 显示新页面
|
||||
- 就像**翻书**:每翻一页都要把旧书合上、去书架拿新书
|
||||
|
||||
**SPA(Single-Page Application)**:
|
||||
|
||||
- 点一个链接 → 只刷新内容区域 → 页面不刷新
|
||||
- 就像**同一本书里换章节**:只擦掉旧内容、写上新内容
|
||||
|
||||
👇 **动手试试看**:体验 MPA 和 SPA 的区别
|
||||
|
||||
<RoutingModeDemo />
|
||||
|
||||
**Vue / React** 时代还有一个重要变化:**从 MPA 到 SPA**。
|
||||
**SPA 的优点**:
|
||||
|
||||
**MPA (Multi-Page Application)**:
|
||||
|
||||
- 点一个链接 → 整页刷新 → 显示新页面
|
||||
- 就像**翻书**: 每翻一页都要把旧书合上、去书架拿新书
|
||||
|
||||
**SPA (Single-Page Application)**:
|
||||
|
||||
- 点一个链接 → 只刷新内容区域 → 页面不刷新
|
||||
- 就像**同一本书里换章节**: 只擦掉旧内容、写上新内容
|
||||
|
||||
**SPA 的优点**:
|
||||
|
||||
- ✅ **体验丝滑**: 页面切换快
|
||||
- ✅ **状态好管理**: 输入的内容、滚动位置都在
|
||||
- ❌ **首屏可能慢**: 需要先下载 JavaScript
|
||||
- ❌ **SEO 要额外处理**: 搜索引擎可能抓不到内容(需要 SSR/SSG)
|
||||
- ✅ **体验丝滑**:页面切换快
|
||||
- ✅ **状态好管理**:输入的内容、滚动位置都在
|
||||
- ❌ **首屏可能慢**:需要先下载 JavaScript
|
||||
- ❌ **SEO 要额外处理**:搜索引擎可能抓不到内容(需要 SSR/SSG)
|
||||
|
||||
---
|
||||
|
||||
## 5. 渲染策略:从 CSR 到 SSR/SSG
|
||||
---
|
||||
|
||||
## 5. 渲染策略:从 CSR 到 SSR/SSG
|
||||
|
||||
::: tip 🤔 核心问题
|
||||
**页面是在服务器生成,还是在浏览器生成?** 不同渲染策略各有优劣,选择合适的策略对性能和 SEO 至关重要。
|
||||
:::
|
||||
|
||||
**CSR(Client-Side Rendering)客户端渲染**:
|
||||
|
||||
- 浏览器下载 JavaScript → 执行代码 → 生成页面
|
||||
- 优点:交互流畅,服务器压力小
|
||||
- 缺点:首屏慢,不利于 SEO
|
||||
|
||||
**SSR(Server-Side Rendering)服务端渲染**:
|
||||
|
||||
- 服务器生成 HTML → 发给浏览器 → 浏览器直接显示
|
||||
- 优点:首屏快,利于 SEO
|
||||
- 缺点:服务器压力大,实现复杂
|
||||
|
||||
**SSG(Static Site Generation)静态站点生成**:
|
||||
|
||||
- 构建时生成所有页面的 HTML
|
||||
- 优点:极快,完全静态,CDN 友好
|
||||
- 缺点:不适合动态内容
|
||||
|
||||
👇 **动手试试看**:对比不同渲染策略的特点
|
||||
|
||||
<RenderingStrategyDemo />
|
||||
|
||||
## 6. 第四阶段:工程化与构建工具(2015s-2020s)
|
||||
|
||||
### 6.1 为什么需要"工程化"?
|
||||
|
||||
前端项目越来越大,不能再靠"手动引入脚本"。
|
||||
|
||||
**工程化**就是用工具和规范,让开发更高效、代码更可靠、协作更顺畅。
|
||||
|
||||
::: tip 💡 工程化 = 从"手工作坊"到"现代化工厂"
|
||||
|
||||
想象一下你在家做饭 vs 开餐厅:
|
||||
|
||||
- **在家做饭**: 想吃什么就做什么,很自由
|
||||
- **开餐厅**: 需要标准化的菜谱、规范的操作流程、统一的原材料采购
|
||||
|
||||
前端开发也一样:
|
||||
|
||||
- **小项目**: 怎么写都行
|
||||
- **大项目**: 需要统一的代码规范、自动化工具、标准化流程
|
||||
:::
|
||||
|
||||
### 6.2 构建工具:Webpack → Vite
|
||||
|
||||
**Webpack** (传统):
|
||||
|
||||
- 工作方式:**先打包,后服务**
|
||||
- 启动时: 打包所有代码 → 启动服务器
|
||||
- 问题:**慢**。项目越大,启动越慢(可能要等 30 秒)
|
||||
|
||||
**Vite** (现代):
|
||||
|
||||
- 工作方式:**按需编译**
|
||||
- 启动时: 不打包,直接启动服务器
|
||||
- 浏览器请求哪个文件,就实时编译哪个
|
||||
- 优势:**快**。通常 1 秒内启动
|
||||
|
||||
| 对比项 | Webpack | Vite | 提升 |
|
||||
| -------- | ------- | ------ | ------------ |
|
||||
| 冷启动 | 30s+ | <1s | **快 30 倍** |
|
||||
| 热更新 | 3-5s | <100ms | **快 30 倍** |
|
||||
| 配置文件 | 几百行 | 几十行 | **大幅简化** |
|
||||
|
||||
::: tip 💡 为什么 Vite 这么快?
|
||||
|
||||
**Webpack** 就像**整备家当搬家**:先把所有东西打包,再出门。
|
||||
|
||||
**Vite** 就像**轻装旅行**:只带必需品,用到什么再买什么。
|
||||
|
||||
在开发环境,大多数时候你只需要修改几个文件,Vite 只编译这几个文件,当然快。
|
||||
::: info 💡 如何选择?
|
||||
- **内容网站**(博客、文档):优先 SSG
|
||||
- **需要 SEO 的动态网站**(电商、新闻):使用 SSR
|
||||
- **后台管理系统**:使用 CSR
|
||||
- **混合需求**:考虑 Nuxt/Next.js 的混合渲染
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## 7. 总结:演进的本质
|
||||
## 6. 第四阶段:工程化与构建工具(2015s-2020s)
|
||||
|
||||
前端技术的演进,本质上是在解决两个问题:
|
||||
::: tip 🤔 核心问题
|
||||
**为什么前端需要"工程化"?构建工具到底在做什么?** 理解工程化,才能看懂现代前端项目的工作流程。
|
||||
:::
|
||||
|
||||
### 7.1 效率:从手动到自动
|
||||
### 6.1 为什么需要"工程化"?
|
||||
|
||||
| 时代 | 开发方式 | 效率 |
|
||||
| --------- | ------------------------ | ---------- |
|
||||
| **2000s** | 手写 HTML/CSS/JS | ⭐ |
|
||||
| **2010s** | jQuery + 手动 DOM 操作 | ⭐⭐ |
|
||||
| **2020s** | Vue/React + 数据驱动 | ⭐⭐⭐ |
|
||||
| **现在** | 组件化 + 工程化 + 自动化 | ⭐⭐⭐⭐⭐ |
|
||||
前端项目越来越大,不能再靠"手动引入脚本"。
|
||||
|
||||
### 7.2 规模:从个人到团队
|
||||
**工程化**就是用工具和规范,让开发更高效、代码更可靠、协作更顺畅。
|
||||
|
||||
| 时代 | 项目规模 | 协作方式 |
|
||||
| --------- | ---------- | ----------------------- |
|
||||
| **2000s** | 几个文件 | 单人就能维护 |
|
||||
| **2010s** | 几十个文件 | 小团队,容易冲突 |
|
||||
| **2020s** | 几百个文件 | 中团队,需要规范 |
|
||||
| **现在** | 几千个文件 | 大团队,需要完整工程体系 |
|
||||
::: tip 💡 工程化 = 从"手工作坊"到"现代化工厂"
|
||||
|
||||
想象一下你在家做饭 vs 开餐厅:
|
||||
|
||||
- **在家做饭**:想吃什么就做什么,很自由
|
||||
- **开餐厅**:需要标准化的菜谱、规范的操作流程、统一的原材料采购
|
||||
|
||||
前端开发也一样:
|
||||
|
||||
- **小项目**:怎么写都行
|
||||
- **大项目**:需要统一的代码规范、自动化工具、标准化流程
|
||||
:::
|
||||
|
||||
### 6.2 构建工具:Webpack → Vite
|
||||
|
||||
**Webpack**(传统):
|
||||
|
||||
- 工作方式:**先打包,后服务**
|
||||
- 启动时:打包所有代码 → 启动服务器
|
||||
- 问题:**慢**。项目越大,启动越慢(可能要等 30 秒)
|
||||
|
||||
**Vite**(现代):
|
||||
|
||||
- 工作方式:**按需编译**
|
||||
- 启动时:不打包,直接启动服务器
|
||||
- 浏览器请求哪个文件,就实时编译哪个
|
||||
- 优势:**快**。通常 1 秒内启动
|
||||
|
||||
| 对比项 | Webpack | Vite | 提升 |
|
||||
|--------|---------|------|------|
|
||||
| 冷启动 | 30s+ | <1s | **快 30 倍** |
|
||||
| 热更新 | 3-5s | <100ms | **快 30 倍** |
|
||||
| 配置文件 | 几百行 | 几十行 | **大幅简化** |
|
||||
|
||||
::: tip 💡 为什么 Vite 这么快?
|
||||
|
||||
**Webpack** 就像**整备家当搬家**:先把所有东西打包,再出门。
|
||||
|
||||
**Vite** 就像**轻装旅行**:只带必需品,用到什么再买什么。
|
||||
|
||||
在开发环境,大多数时候你只需要修改几个文件,Vite 只编译这几个文件,当然快。
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## 8. 学习路线图
|
||||
---
|
||||
|
||||
### 8.1 如果你是零基础
|
||||
## 7. 主流框架对比
|
||||
|
||||
**第 1 步: HTML/CSS/JavaScript 基础**
|
||||
::: tip 🤔 核心问题
|
||||
**Vue、React、Svelte、Angular 各有什么特点?如何选择适合自己的框架?** 了解它们的设计理念和使用场景,才能做出明智的选择。
|
||||
:::
|
||||
|
||||
### 7.1 四大框架对比
|
||||
|
||||
| 特性 | Vue | React | Svelte | Angular |
|
||||
|------|-----|-------|--------|---------|
|
||||
| **设计理念** | 渐进式框架 | UI 库 | 编译时框架 | 完整平台 |
|
||||
| **学习曲线** | ⭐⭐ 简单 | ⭐⭐⭐ 中等 | ⭐⭐ 简单 | ⭐⭐⭐⭐ 陡峭 |
|
||||
| **性能** | 快 | 快 | **极快** | 快 |
|
||||
| **生态系统** | 完善 | **最完善** | 成长中 | 完善 |
|
||||
| **包大小** | 小 | 中等 | **最小** | 大 |
|
||||
| **适合场景** | 中小型项目 | 大型项目 | 性能要求高 | 企业级应用 |
|
||||
| **公司支持** | 尤雨溪(独立) | Meta | 社区 | Google |
|
||||
|
||||
### 7.2 Vue:渐进式框架
|
||||
|
||||
**核心理念**:渐进式采用,可以只用一部分,也可以用全家桶
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<div>{{ message }}</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
message: 'Hello Vue'
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
**优点**:
|
||||
- ✅ 学习曲线平缓,中文文档完善
|
||||
- ✅ 模板语法直观,易于理解
|
||||
- ✅ 单文件组件(.vue)结构清晰
|
||||
- ✅ 适合快速开发
|
||||
|
||||
**缺点**:
|
||||
- ❌ 大型项目的状态管理需要额外学习 Vuex/Pinia
|
||||
- ❌ 灵活性略逊于 React
|
||||
|
||||
**适用场景**:
|
||||
- 中小型 Web 应用
|
||||
- 快速原型开发
|
||||
- 中文团队(文档友好)
|
||||
|
||||
### 7.3 React:UI 库
|
||||
|
||||
**核心理念**:只负责视图层,其他问题交给社区
|
||||
|
||||
```jsx
|
||||
function App() {
|
||||
const [message, setMessage] = useState('Hello React')
|
||||
return <div>{message}</div>
|
||||
}
|
||||
```
|
||||
|
||||
**优点**:
|
||||
- ✅ 生态系统最完善,组件库丰富
|
||||
- ✅ JSX 语法灵活,表达能力强大
|
||||
- ✅ 虚拟 DOM 性能优秀
|
||||
- ✅ 适合大型项目
|
||||
|
||||
**缺点**:
|
||||
- ❌ 学习曲线较陡,需要掌握额外概念
|
||||
- ❌ 需要自己选择和搭配各种库
|
||||
- ❌ JSX 需要编译,不能直接在浏览器运行
|
||||
|
||||
**适用场景**:
|
||||
- 大型复杂应用
|
||||
- 需要丰富生态的项目
|
||||
- 跨平台开发(React Native)
|
||||
|
||||
### 7.4 Svelte:编译时框架
|
||||
|
||||
**核心理念**:没有虚拟 DOM,编译时将组件转换为高效的原生代码
|
||||
|
||||
```svelte
|
||||
<script>
|
||||
let message = 'Hello Svelte'
|
||||
</script>
|
||||
|
||||
<div>{message}</div>
|
||||
```
|
||||
|
||||
**优点**:
|
||||
- ✅ **性能最优**(无虚拟 DOM 运行时开销)
|
||||
- ✅ 包体积最小
|
||||
- ✅ 语法简单直观
|
||||
- ✅ 响应式系统天然支持
|
||||
|
||||
**缺点**:
|
||||
- ❌ 生态相对较小
|
||||
- ❌ 社区规模不如 Vue/React
|
||||
- ❌ 第三方库较少
|
||||
|
||||
**适用场景**:
|
||||
- 性能要求极高的应用
|
||||
- 包体积敏感的项目
|
||||
- 愿意尝试新技术的团队
|
||||
|
||||
### 7.5 Angular:完整平台
|
||||
|
||||
**核心理念**:提供完整的解决方案,开箱即用
|
||||
|
||||
```typescript
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
template: '<div>{{ message }}</div>'
|
||||
})
|
||||
export class AppComponent {
|
||||
message = 'Hello Angular'
|
||||
}
|
||||
```
|
||||
|
||||
**优点**:
|
||||
- ✅ 功能完整,路由、HTTP、表单全都有
|
||||
- ✅ TypeScript 原生支持
|
||||
- ✅ 适合大型团队和项目
|
||||
- ✅ 代码规范统一
|
||||
|
||||
**缺点**:
|
||||
- ❌ 学习曲线陡峭
|
||||
- ❌ 概念多,复杂度高
|
||||
- ❌ 包体积大
|
||||
- ❌ 不适合小型项目
|
||||
|
||||
**适用场景**:
|
||||
- 大型企业级应用
|
||||
- 需要严格规范的团队
|
||||
- 已有 TypeScript 技术栈的项目
|
||||
|
||||
---
|
||||
|
||||
## 8. 总结:演进的本质
|
||||
|
||||
前端技术的演进,本质上是在解决两个问题:
|
||||
|
||||
### 8.1 效率:从手动到自动
|
||||
|
||||
| 时代 | 开发方式 | 效率 |
|
||||
|------|---------|------|
|
||||
| **2000s** | 手写 HTML/CSS/JS | ⭐ |
|
||||
| **2010s** | jQuery + 手动 DOM 操作 | ⭐⭐ |
|
||||
| **2020s** | Vue/React + 数据驱动 | ⭐⭐⭐ |
|
||||
| **现在** | 组件化 + 工程化 + 自动化 | ⭐⭐⭐⭐⭐ |
|
||||
|
||||
### 8.2 规模:从个人到团队
|
||||
|
||||
| 时代 | 项目规模 | 协作方式 |
|
||||
|------|---------|---------|
|
||||
| **2000s** | 几个文件 | 单人就能维护 |
|
||||
| **2010s** | 几十个文件 | 小团队,容易冲突 |
|
||||
| **2020s** | 几百个文件 | 中团队,需要规范 |
|
||||
| **现在** | 几千个文件 | 大团队,需要完整工程体系 |
|
||||
|
||||
---
|
||||
|
||||
---
|
||||
|
||||
## 9. 学习路线图
|
||||
|
||||
### 9.1 如果你是零基础
|
||||
|
||||
**第 1 步:HTML/CSS/JavaScript 基础**
|
||||
|
||||
- 理解网页的三大基石
|
||||
- 能写出简单的静态页面
|
||||
|
||||
**第 2 步: 学习一个框架(Vue 推荐)**
|
||||
**第 2 步:学习一个框架(Vue 推荐)**
|
||||
|
||||
- 理解"数据驱动"的思想
|
||||
- 掌握组件化开发
|
||||
|
||||
**第 3 步: 实战项目**
|
||||
**第 3 步:实战项目**
|
||||
|
||||
- 做一个完整的单页应用
|
||||
- 熟悉路由、状态管理、API 调用
|
||||
|
||||
### 8.2 如果你有基础
|
||||
### 9.2 如果你有基础
|
||||
|
||||
**进阶方向**:
|
||||
**进阶方向**:
|
||||
|
||||
- **工程化**: 学习 Vite/Webpack,理解构建流程
|
||||
- **性能优化**: 学习懒加载、代码分割、缓存策略
|
||||
- **TypeScript**: 为代码加上类型,提升可靠性
|
||||
- **服务端渲染**: 学习 Nuxt/Next.js,解决 SEO 和首屏问题
|
||||
- **工程化**:学习 Vite/Webpack,理解构建流程
|
||||
- **性能优化**:学习懒加载、代码分割、缓存策略
|
||||
- **TypeScript**:为代码加上类型,提升可靠性
|
||||
- **服务端渲染**:学习 Nuxt/Next.js,解决 SEO 和首屏问题
|
||||
|
||||
---
|
||||
|
||||
## 9. 名词速查表 (Glossary)
|
||||
## 10. 你现在应该能识别的代码
|
||||
|
||||
| 名词 | 英文 | 用人话解释 |
|
||||
| ---------------- | ----------------------- | --------------------------------------------- |
|
||||
| **DOM** | Document Object Model | 文档对象模型。用对象树表示页面,可被 JS 读写。 |
|
||||
| **jQuery** | - | 早期流行的 JS 库,简化了 DOM 操作。 |
|
||||
| **Vue/React** | - | 现代前端框架,采用数据驱动和组件化开发。 |
|
||||
| **组件** | Component | 可复用的 UI 单元,如按钮、卡片、导航栏。 |
|
||||
| **MPA** | Multi-Page Application | 多页应用。每次跳转都重新加载整个页面。 |
|
||||
| **SPA** | Single-Page Application | 单页应用。只加载一次,后续切换不刷新页面。 |
|
||||
| **路由** | Routing | 管理页面之间切换的规则和过程。 |
|
||||
| **SSR** | Server-Side Rendering | 服务端渲染。服务器生成 HTML 后发给浏览器。 |
|
||||
| **SSG** | Static Site Generation | 静态站点生成。构建时预渲染页面为静态 HTML。 |
|
||||
| **Webpack** | - | 传统打包工具,先打包后服务。 |
|
||||
| **Vite** | - | 现代构建工具,按需编译,速度极快。 |
|
||||
| **响应式** | Responsive Design | 页面自动适配不同屏幕尺寸的设计。 |
|
||||
| **媒体查询** | Media Query | CSS 的条件判断,根据屏幕宽度应用不同样式。 |
|
||||
| **命令式** | Imperative | 告诉程序"怎么做"。 |
|
||||
| **声明式** | Declarative | 告诉程序"要什么"。 |
|
||||
| **数据驱动** | Data-Driven | 只修改数据,界面自动更新。 |
|
||||
| **Tree Shaking** | - | 摇树优化。自动移除未使用的代码,减小包体积。 |
|
||||
| **代码分割** | Code Splitting | 把代码分成多个小块,按需加载。 |
|
||||
通过阅读本章,你应该能够:
|
||||
|
||||
- ✅ 理解前端技术演进的脉络和原因
|
||||
- ✅ 区分 Vue、React、Svelte、Angular 的特点
|
||||
- ✅ 理解"命令式"和"声明式"的区别
|
||||
- ✅ 掌握"数据驱动"的核心思想
|
||||
- ✅ 知道组件化开发的价值
|
||||
- ✅ 了解 CSR、SSR、SSG 的适用场景
|
||||
- ✅ 理解构建工具(Webpack、Vite)的作用
|
||||
- ✅ 能根据项目选择合适的框架和技术栈
|
||||
|
||||
::: info 💡 实际应用
|
||||
当你用 AI 做项目时,你可以这样告诉它:
|
||||
|
||||
- "这是一个需要 SEO 的博客网站,用 Nuxt(Vue 的 SSR 框架)"
|
||||
- "这是一个后台管理系统,用 Vue + Element Plus,不需要 SSR"
|
||||
- "这是一个性能要求高的 Web 应用,考虑使用 Svelte"
|
||||
- "项目已经用 React 了,继续用 React 生态的库"
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## 总结
|
||||
## 名词速查表
|
||||
|
||||
前端技术的演进,本质上是**从"手工"到"工业化"的进化**:
|
||||
|
||||
- **2000s**: 手工时代,简单直接
|
||||
- **2010s**: 工具化时代,开始有框架
|
||||
- **2020s**: 工业化时代,组件化 + 工程化
|
||||
- **现在**: 智能化时代,AI 辅助开发
|
||||
|
||||
理解这个演进,你就能:
|
||||
|
||||
- 知道为什么要有 Vue/React
|
||||
- 理解"数据驱动"的价值
|
||||
- 明白工程化的必要性
|
||||
- 快速上手新技术
|
||||
|
||||
**下一步建议**:
|
||||
|
||||
- 如果你想快速上手,学习 **Vue 3** (推荐) 或 **React**
|
||||
- 如果你想深入理解,学习 **Vite** 构建流程
|
||||
- 如果你想提升代码质量,学习 **TypeScript**
|
||||
|
||||
祝你学习愉快!
|
||||
| 名词 | 英文 | 用人话解释 |
|
||||
|------|------|-----------|
|
||||
| **DOM** | Document Object Model | 文档对象模型。用对象树表示页面,可被 JS 读写。 |
|
||||
| **jQuery** | - | 早期流行的 JS 库,简化了 DOM 操作。 |
|
||||
| **Vue/React** | - | 现代前端框架,采用数据驱动和组件化开发。 |
|
||||
| **组件** | Component | 可复用的 UI 单元,如按钮、卡片、导航栏。 |
|
||||
| **MPA** | Multi-Page Application | 多页应用。每次跳转都重新加载整个页面。 |
|
||||
| **SPA** | Single-Page Application | 单页应用。只加载一次,后续切换不刷新页面。 |
|
||||
| **路由** | Routing | 管理页面之间切换的规则和过程。 |
|
||||
| **SSR** | Server-Side Rendering | 服务端渲染。服务器生成 HTML 后发给浏览器。 |
|
||||
| **SSG** | Static Site Generation | 静态站点生成。构建时预渲染页面为静态 HTML。 |
|
||||
| **CSR** | Client-Side Rendering | 客户端渲染。浏览器通过 JS 生成页面。 |
|
||||
| **Webpack** | - | 传统打包工具,先打包后服务。 |
|
||||
| **Vite** | - | 现代构建工具,按需编译,速度极快。 |
|
||||
| **响应式** | Responsive Design | 页面自动适配不同屏幕尺寸的设计。 |
|
||||
| **媒体查询** | Media Query | CSS 的条件判断,根据屏幕宽度应用不同样式。 |
|
||||
| **命令式** | Imperative | 告诉程序"怎么做"。 |
|
||||
| **声明式** | Declarative | 告诉程序"要什么"。 |
|
||||
| **数据驱动** | Data-Driven | 只修改数据,界面自动更新。 |
|
||||
| **Tree Shaking** | - | 摇树优化。自动移除未使用的代码,减小包体积。 |
|
||||
| **代码分割** | Code Splitting | 把代码分成多个小块,按需加载。 |
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,3 +1,599 @@
|
||||
# JavaScript 运行时
|
||||
# JavaScript 运行时深度指南
|
||||
|
||||
> 待实现
|
||||
::: tip 前言
|
||||
你已经学会了 JavaScript 的基本语法,但你是否想过:
|
||||
- 代码到底在哪里运行?
|
||||
- 为什么同样的代码在浏览器和 Node.js 中行为不一样?
|
||||
- 为什么有时代码会"卡住",有时却能"并行"执行?
|
||||
|
||||
这篇文章会带你深入了解 JavaScript 的运行时环境,包括事件循环、调用栈、内存管理等。读完这篇,你就能理解代码为什么按某个顺序执行,快速定位异步相关的 bug,优化代码性能并避免内存泄漏。
|
||||
:::
|
||||
|
||||
**这篇文章会带你学什么?**
|
||||
|
||||
| 章节 | 内容 | 学完能干嘛 |
|
||||
|-----|------|-----------|
|
||||
| **第 1 章** | 运行时概述 | 理解 JavaScript 代码在哪里运行 |
|
||||
| **第 2 章** | 浏览器运行时 | 知道浏览器提供了哪些 Web API |
|
||||
| **第 3 章** | Node.js 运行时 | 了解服务器端的 JavaScript 环境 |
|
||||
| **第 4 章** | 事件循环深入 | 掌握宏任务和微任务的执行顺序 |
|
||||
| **第 5 章** | 调用栈与内存 | 理解代码执行过程和内存管理 |
|
||||
| **第 6 章** | 实战技巧 | 优化性能、调试内存泄漏 |
|
||||
|
||||
---
|
||||
|
||||
## 1. 运行时概述
|
||||
|
||||
::: tip 🤔 核心问题
|
||||
**什么是"运行时"?** JavaScript 只是一门语言,为什么同样的代码在不同环境中会有不同的行为?
|
||||
:::
|
||||
|
||||
### 1.1 运行时是什么
|
||||
|
||||
**运行时 = JavaScript 引擎 + 环境提供的 API**
|
||||
|
||||
如果把 JavaScript 比作"编程语言",那么运行时就是"操作系统"——它决定了你的代码能做什么、不能做什么。
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────┐
|
||||
│ JavaScript 代码 │
|
||||
├─────────────────────────────────────┤
|
||||
│ JavaScript 引擎 (V8) │ ← 负责解析和执行代码
|
||||
├─────────────────────────────────────┤
|
||||
│ 运行时环境 (浏览器/Node.js) │ ← 提供额外能力
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**一个比喻:JavaScript 是"普通话",运行时是"城市"**
|
||||
|
||||
- JavaScript 语法(普通话)哪里都一样
|
||||
- 但不同城市提供的设施不一样:
|
||||
- 浏览器 = 有 DOM、window、fetch(就像城市有商场、图书馆)
|
||||
- Node.js = 有 fs、http、path(就像城市有工厂、高速公路)
|
||||
|
||||
### 1.2 两大主流运行时
|
||||
|
||||
| 特性 | 浏览器 | Node.js |
|
||||
|------|--------|---------|
|
||||
| **主要用途** | 网页交互、用户界面 | 服务器端应用、命令行工具 |
|
||||
| **全局对象** | `window` | `global` |
|
||||
| **DOM API** | ✅ 支持 | ❌ 不支持 |
|
||||
| **文件系统** | ❌ 受限 | ✅ 完整支持 |
|
||||
| **模块系统** | ES Modules | CommonJS + ES Modules |
|
||||
| **定时器** | `setTimeout`, `setInterval` | `setTimeout`, `setInterval` |
|
||||
| **网络请求** | `fetch`, `XMLHttpRequest` | `http`, `https` 模块 |
|
||||
|
||||
👇 **动手试试看**:对比浏览器和 Node.js 的环境差异
|
||||
|
||||
<RuntimeEnvironmentDemo />
|
||||
|
||||
::: info 💡 核心启示
|
||||
运行时决定了你能用什么 API。在浏览器能用的 DOM API,在 Node.js 里用不了;在 Node.js 能用的文件 API,在浏览器里也用不了。这就是为什么有些代码需要"环境判断"。
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## 2. 浏览器运行时
|
||||
|
||||
::: tip 🤔 核心问题
|
||||
**浏览器提供了哪些能力让 JavaScript 操作网页?**
|
||||
:::
|
||||
|
||||
### 2.1 浏览器运行时的组成
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────┐
|
||||
│ JavaScript 引擎 │
|
||||
│ (V8 / SpiderMonkey) │
|
||||
└─────────────────────────────────────────────┘
|
||||
↓
|
||||
┌─────────────────────────────────────────────┐
|
||||
│ Web APIs │
|
||||
│ ┌─────────┐ ┌──────────┐ ┌──────────┐ │
|
||||
│ │ DOM │ │ BOM │ │ Network │ │
|
||||
│ │ 操作网页 │ │ 操作浏览器 │ │ 网络请求 │ │
|
||||
│ └─────────┘ └──────────┘ └──────────┘ │
|
||||
└─────────────────────────────────────────────┘
|
||||
↓
|
||||
┌─────────────────────────────────────────────┐
|
||||
│ 事件循环 (Event Loop) │
|
||||
│ 负责协调代码执行、事件处理、任务调度 │
|
||||
└─────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 2.2 Web APIs 的三大类
|
||||
|
||||
**1. DOM API - 操作网页内容**
|
||||
|
||||
```javascript
|
||||
// 查找元素
|
||||
const title = document.querySelector('h1')
|
||||
|
||||
// 修改内容
|
||||
title.textContent = '新标题'
|
||||
|
||||
// 添加样式
|
||||
title.style.color = 'red'
|
||||
```
|
||||
|
||||
**2. BOM API - 操作浏览器**
|
||||
|
||||
```javascript
|
||||
// 页面跳转
|
||||
window.location.href = 'https://example.com'
|
||||
|
||||
// 浏览器存储
|
||||
localStorage.setItem('key', 'value')
|
||||
|
||||
// 浏览器历史
|
||||
history.back()
|
||||
```
|
||||
|
||||
**3. Network API - 网络请求**
|
||||
|
||||
```javascript
|
||||
// 发送 HTTP 请求
|
||||
fetch('/api/data')
|
||||
.then(response => response.json())
|
||||
.then(data => console.log(data))
|
||||
```
|
||||
|
||||
### 2.3 浏览器特有的事件机制
|
||||
|
||||
浏览器运行时最强大的功能之一是"事件驱动"——代码不需要一直运行,而是等用户操作时才执行。
|
||||
|
||||
```javascript
|
||||
button.addEventListener('click', () => {
|
||||
console.log('按钮被点击了')
|
||||
})
|
||||
```
|
||||
|
||||
**常见事件类型:**
|
||||
|
||||
| 事件类型 | 触发时机 | 实际场景 |
|
||||
|---------|---------|---------|
|
||||
| `click` | 鼠标点击 | 按钮交互 |
|
||||
| `input` | 输入框内容变化 | 实时搜索 |
|
||||
| `scroll` | 页面滚动 | 懒加载 |
|
||||
| `load` | 资源加载完成 | 初始化数据 |
|
||||
| `error` | 发生错误 | 错误处理 |
|
||||
|
||||
---
|
||||
|
||||
## 3. Node.js 运行时
|
||||
|
||||
::: tip 🤔 核心问题
|
||||
**JavaScript 能在服务器端运行,靠的是什么?**
|
||||
:::
|
||||
|
||||
### 3.1 Node.js 的组成
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────┐
|
||||
│ JavaScript 引擎 │
|
||||
│ (V8) │
|
||||
└─────────────────────────────────────────────┘
|
||||
↓
|
||||
┌─────────────────────────────────────────────┐
|
||||
│ Node.js 内置模块 │
|
||||
│ ┌─────────┐ ┌──────────┐ ┌──────────┐ │
|
||||
│ │ fs │ │ http │ │ path │ │
|
||||
│ │ 文件操作 │ │ 网络服务器 │ │ 路径处理 │ │
|
||||
│ └─────────┘ └──────────┘ └──────────┘ │
|
||||
└─────────────────────────────────────────────┘
|
||||
↓
|
||||
┌─────────────────────────────────────────────┐
|
||||
│ libuv 事件循环库 │
|
||||
│ 跨平台的异步 I/O 支持 │
|
||||
└─────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 3.2 Node.js 特有能力
|
||||
|
||||
**1. 文件系统操作**
|
||||
|
||||
```javascript
|
||||
const fs = require('fs')
|
||||
|
||||
// 读取文件
|
||||
fs.readFile('./data.txt', 'utf8', (err, data) => {
|
||||
if (err) throw err
|
||||
console.log(data)
|
||||
})
|
||||
|
||||
// 写入文件
|
||||
fs.writeFile('./output.txt', 'Hello', (err) => {
|
||||
if (err) throw err
|
||||
console.log('写入成功')
|
||||
})
|
||||
```
|
||||
|
||||
**2. HTTP 服务器**
|
||||
|
||||
```javascript
|
||||
const http = require('http')
|
||||
|
||||
const server = http.createServer((req, res) => {
|
||||
res.writeHead(200, { 'Content-Type': 'text/html' })
|
||||
res.end('<h1>Hello World</h1>')
|
||||
})
|
||||
|
||||
server.listen(3000)
|
||||
```
|
||||
|
||||
**3. 模块系统**
|
||||
|
||||
```javascript
|
||||
// CommonJS (Node.js 默认)
|
||||
const fs = require('fs')
|
||||
module.exports = { myFunction }
|
||||
|
||||
// ES Modules (现代方式)
|
||||
import fs from 'fs'
|
||||
export { myFunction }
|
||||
```
|
||||
|
||||
### 3.3 浏览器 vs Node.js 对比
|
||||
|
||||
| 特性 | 浏览器 | Node.js |
|
||||
|------|--------|---------|
|
||||
| **入口文件** | HTML 文件 | JavaScript 文件 |
|
||||
| **全局对象** | `window`, `document` | `global`, `process` |
|
||||
| **模块加载** | `<script>` 标签 | `require()` / `import` |
|
||||
| **安全性** | 沙箱环境,受限 | 可以访问系统资源 |
|
||||
| **用途** | 用户界面 | 后端服务、工具 |
|
||||
|
||||
---
|
||||
|
||||
## 4. 事件循环深入
|
||||
|
||||
::: tip 🤔 核心问题
|
||||
**JavaScript 是单线程的,为什么能做到"不阻塞"?**
|
||||
:::
|
||||
|
||||
### 4.1 事件循环是什么
|
||||
|
||||
**事件循环 = JavaScript 的"任务调度中心"**
|
||||
|
||||
JavaScript 是单线程的,一次只能做一件事。但事件循环让它看起来能"同时"做很多事。
|
||||
|
||||
**核心机制:**
|
||||
|
||||
1. **执行同步代码** (调用栈)
|
||||
2. **处理异步任务** (任务队列)
|
||||
3. **等待新任务** (循环往复)
|
||||
|
||||
```
|
||||
调用栈 任务队列
|
||||
┌─────────┐ ┌──────────┐
|
||||
│ 任务 1 │ │ 宏任务 1 │
|
||||
│ 任务 2 │ ←──────────── │ 宏任务 2 │
|
||||
│ 任务 3 │ 执行完一个 │ 宏任务 3 │
|
||||
└─────────┘ 就取下一个 └──────────┘
|
||||
↓ ↑
|
||||
└────────────────────────┘
|
||||
事件循环不断检查
|
||||
```
|
||||
|
||||
### 4.2 宏任务 vs 微任务
|
||||
|
||||
这是面试和实际开发中最容易搞混的概念!
|
||||
|
||||
**宏任务 (Macrotask):**
|
||||
- `setTimeout`, `setInterval`
|
||||
- I/O 操作
|
||||
- UI 渲染
|
||||
|
||||
**微任务 (Microtask):**
|
||||
- `Promise.then`
|
||||
- `MutationObserver`
|
||||
- `queueMicrotask`
|
||||
|
||||
**执行顺序:同步代码 → 微任务 → 宏任务**
|
||||
|
||||
👇 **动手试试看**:观察宏任务和微任务的执行顺序
|
||||
|
||||
<TaskQueueDemo />
|
||||
|
||||
### 4.3 经典面试题
|
||||
|
||||
```javascript
|
||||
console.log('1')
|
||||
|
||||
setTimeout(() => console.log('2'), 0)
|
||||
|
||||
Promise.resolve().then(() => console.log('3'))
|
||||
|
||||
console.log('4')
|
||||
|
||||
// 输出: 1, 4, 3, 2
|
||||
```
|
||||
|
||||
**为什么是这个顺序?**
|
||||
|
||||
1. 执行同步代码:`console.log('1')`,`console.log('4')` → 输出 1, 4
|
||||
2. 检查微任务队列:`Promise.then` → 输出 3
|
||||
3. 检查宏任务队列:`setTimeout` → 输出 2
|
||||
|
||||
::: info 💡 实战技巧
|
||||
- 如果想让代码尽快执行,用微任务 (`Promise.then`)
|
||||
- 如果想延迟执行,用宏任务 (`setTimeout`)
|
||||
- 永远不要混用太多异步操作,否则会陷入"回调地狱"
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## 5. 调用栈与内存
|
||||
|
||||
::: tip 🤔 核心问题
|
||||
**代码是怎么被执行的?变量存在哪里?什么时候被回收?**
|
||||
:::
|
||||
|
||||
### 5.1 调用栈:函数执行的"足迹"
|
||||
|
||||
**调用栈 = 记录函数调用的"笔记本"**
|
||||
|
||||
每次调用一个函数,就会在栈上新增一条记录;函数执行完,记录就被移除。
|
||||
|
||||
```javascript
|
||||
function a() {
|
||||
b()
|
||||
}
|
||||
|
||||
function b() {
|
||||
c()
|
||||
}
|
||||
|
||||
function c() {
|
||||
console.log('执行完毕')
|
||||
}
|
||||
|
||||
a()
|
||||
```
|
||||
|
||||
**调用栈的变化:**
|
||||
|
||||
```
|
||||
步骤 1: 调用 a()
|
||||
┌─────────┐
|
||||
│ a │
|
||||
└─────────┘
|
||||
|
||||
步骤 2: a() 调用 b()
|
||||
┌─────────┐
|
||||
│ b │
|
||||
│ a │
|
||||
└─────────┘
|
||||
|
||||
步骤 3: b() 调用 c()
|
||||
┌─────────┐
|
||||
│ c │
|
||||
│ b │
|
||||
│ a │
|
||||
└─────────┘
|
||||
|
||||
步骤 4: c() 执行完,依次弹出
|
||||
┌─────────┐
|
||||
│ b │
|
||||
│ a │
|
||||
└─────────┘
|
||||
```
|
||||
|
||||
👇 **动手试试看**:观察调用栈的变化
|
||||
|
||||
<CallStackDemo />
|
||||
|
||||
### 5.2 内存管理:垃圾去哪儿了
|
||||
|
||||
JavaScript 有"自动垃圾回收"机制——你不需要手动释放内存,引擎会帮你做。
|
||||
|
||||
**垃圾回收的原理:标记-清除算法**
|
||||
|
||||
1. **标记阶段**:从"根"开始,找到所有能访问的变量
|
||||
2. **清除阶段**:没被标记的变量就是"垃圾",会被回收
|
||||
|
||||
```javascript
|
||||
// 垃圾回收示例
|
||||
let obj1 = { name: '对象1' }
|
||||
let obj2 = { name: '对象2' }
|
||||
|
||||
// obj1 被重新赋值,原来的对象失去了引用
|
||||
obj1 = null // 原来的 { name: '对象1' } 会被回收
|
||||
|
||||
// obj2 还在使用中,不会被回收
|
||||
console.log(obj2.name)
|
||||
```
|
||||
|
||||
👇 **动手试试看**:观察垃圾回收的过程
|
||||
|
||||
<GarbageCollectionDemo />
|
||||
|
||||
### 5.3 内存泄漏:忘记清理的后果
|
||||
|
||||
**内存泄漏 = 该释放的内存没释放,越积越多**
|
||||
|
||||
常见原因:
|
||||
|
||||
**1. 全局变量太多**
|
||||
|
||||
```javascript
|
||||
// ❌ 错误:全局变量不会被回收
|
||||
globalCache = []
|
||||
|
||||
function addItem(item) {
|
||||
globalCache.push(item)
|
||||
}
|
||||
```
|
||||
|
||||
**2. 事件监听没移除**
|
||||
|
||||
```javascript
|
||||
// ❌ 错误:监听器没移除
|
||||
button.addEventListener('click', handleClick)
|
||||
|
||||
// ✅ 正确:不需要时移除监听
|
||||
button.removeEventListener('click', handleClick)
|
||||
```
|
||||
|
||||
**3. 闭包引用大对象**
|
||||
|
||||
```javascript
|
||||
// ❌ 错误:闭包一直引用大对象,不会被回收
|
||||
function createHandler() {
|
||||
const bigData = new Array(1000000).fill('data')
|
||||
return function() {
|
||||
console.log('处理中')
|
||||
}
|
||||
}
|
||||
|
||||
const handler = createHandler() // bigData 一直存在于内存中
|
||||
```
|
||||
|
||||
👇 **动手试试看**:观察内存泄漏是如何发生的
|
||||
|
||||
<MemoryLeakDemo />
|
||||
|
||||
::: info 💡 实战技巧
|
||||
- **定期检查**:打开浏览器 DevTools → Memory → Take Heap Snapshot,查看内存占用
|
||||
- **避免全局变量**:尽量用 `const` 和 `let`,不用 `var`
|
||||
- **及时清理**:事件监听、定时器用完要移除
|
||||
- **弱引用**:用 `WeakMap` 和 `WeakSet` 存储对象引用
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## 6. 实战技巧
|
||||
|
||||
::: tip 🤔 核心问题
|
||||
**怎么写出高性能的 JavaScript 代码?遇到问题怎么调试?**
|
||||
:::
|
||||
|
||||
### 6.1 性能优化技巧
|
||||
|
||||
**1. 减少重排重绘**
|
||||
|
||||
```javascript
|
||||
// ❌ 错误:每次循环都触发重排
|
||||
for (let i = 0; i < 1000; i++) {
|
||||
element.style.top = i + 'px'
|
||||
}
|
||||
|
||||
// ✅ 正确:批量修改
|
||||
element.style.transform = `translateY(${position}px)`
|
||||
```
|
||||
|
||||
**2. 使用事件委托**
|
||||
|
||||
```javascript
|
||||
// ❌ 错误:给每个按钮都添加监听
|
||||
buttons.forEach(btn => {
|
||||
btn.addEventListener('click', handleClick)
|
||||
})
|
||||
|
||||
// ✅ 正确:只给父元素添加一个监听
|
||||
container.addEventListener('click', (e) => {
|
||||
if (e.target.matches('.button')) {
|
||||
handleClick(e)
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
**3. 防抖和节流**
|
||||
|
||||
```javascript
|
||||
// 防抖:用户停止输入后再执行
|
||||
function debounce(fn, delay) {
|
||||
let timer
|
||||
return function(...args) {
|
||||
clearTimeout(timer)
|
||||
timer = setTimeout(() => fn.apply(this, args), delay)
|
||||
}
|
||||
}
|
||||
|
||||
// 节流:限制执行频率
|
||||
function throttle(fn, delay) {
|
||||
let lastTime = 0
|
||||
return function(...args) {
|
||||
const now = Date.now()
|
||||
if (now - lastTime >= delay) {
|
||||
fn.apply(this, args)
|
||||
lastTime = now
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 6.2 调试技巧
|
||||
|
||||
**1. 用 DevTools 查看调用栈**
|
||||
|
||||
```javascript
|
||||
function a() {
|
||||
b()
|
||||
}
|
||||
|
||||
function b() {
|
||||
c()
|
||||
}
|
||||
|
||||
function c() {
|
||||
debugger // 在这里暂停,查看调用栈
|
||||
}
|
||||
|
||||
a()
|
||||
```
|
||||
|
||||
**2. 用 `console.trace()` 追踪执行路径**
|
||||
|
||||
```javascript
|
||||
function trackExecution() {
|
||||
console.trace('执行路径')
|
||||
// 会输出完整的调用栈
|
||||
}
|
||||
```
|
||||
|
||||
**3. 用 Performance 分析性能**
|
||||
|
||||
```javascript
|
||||
performance.mark('start')
|
||||
|
||||
// 执行一些代码
|
||||
for (let i = 0; i < 10000; i++) {
|
||||
// ...
|
||||
}
|
||||
|
||||
performance.mark('end')
|
||||
performance.measure('循环性能', 'start', 'end')
|
||||
|
||||
const measure = performance.getEntriesByName('循环性能')[0]
|
||||
console.log(`执行时间: ${measure.duration}ms`)
|
||||
```
|
||||
|
||||
### 6.3 常见问题速查
|
||||
|
||||
| 问题 | 可能原因 | 解决方案 |
|
||||
|------|---------|---------|
|
||||
| **内存占用高** | 内存泄漏、缓存太多 | 检查全局变量、移除监听器 |
|
||||
| **页面卡顿** | 长任务阻塞主线程 | 拆分任务、用 Web Workers |
|
||||
| **事件不触发** | 监听器没绑定、元素不存在 | 检查 DOM 加载时机 |
|
||||
| **异步顺序错乱** | 混用宏任务和微任务 | 统一用 Promise 或 async/await |
|
||||
| **定时器不准** | 主线程阻塞 | 用 Web Workers 或 requestAnimationFrame |
|
||||
|
||||
---
|
||||
|
||||
## 总结
|
||||
|
||||
你现在应该能理解:
|
||||
|
||||
- **运行时 = 引擎 + 环境 API**,不同运行时提供不同能力
|
||||
- **事件循环**负责协调同步代码、微任务、宏任务的执行顺序
|
||||
- **调用栈**记录函数执行过程,**栈溢出**是因为递归太深
|
||||
- **垃圾回收**自动清理不用的变量,但要注意**内存泄漏**
|
||||
- **性能优化**的关键是减少重排重绘、合理使用异步
|
||||
|
||||
::: info 💡 遇到问题时这样跟 AI 说
|
||||
- "这个函数执行太慢,帮我看看怎么优化性能"
|
||||
- "内存占用一直在涨,可能是内存泄漏,帮我检查一下"
|
||||
- "异步操作顺序不对,应该是先 A 再 B,现在是 A 和 B 几乎同时开始"
|
||||
- "事件监听器没有触发,检查一下元素是否已经加载到 DOM"
|
||||
:::
|
||||
|
||||
@@ -1,3 +1,823 @@
|
||||
# TypeScript:给 JS 加上类型系统
|
||||
# TypeScript 深度指南
|
||||
|
||||
> 待实现
|
||||
::: tip 前言
|
||||
你已经会写 JavaScript 了,但可能遇到过这些问题:
|
||||
- 变量赋值了错误类型,运行时才发现
|
||||
- 对象属性写错了名字,调试半天
|
||||
- 函数参数类型不对,改来改去
|
||||
|
||||
TypeScript 就是在代码运行前帮你发现这些问题的工具。读完这篇,你就能理解 TypeScript 为什么能提升代码质量,看懂类型注解、接口、泛型等核心概念,在 vibecoding 中更好地利用 AI 生成的代码。
|
||||
:::
|
||||
|
||||
**这篇文章会带你学什么?**
|
||||
|
||||
| 章节 | 内容 | 学完能干嘛 |
|
||||
|-----|------|-----------|
|
||||
| **第 1 章** | TypeScript 是什么 | 明白它和 JavaScript 的关系 |
|
||||
| **第 2 章** | 基础类型注解 | 知道怎么给变量标注类型 |
|
||||
| **第 3 章** | 对象类型与接口 | 定义数据结构的类型 |
|
||||
| **第 4 章** | 函数类型 | 给函数参数和返回值标注类型 |
|
||||
| **第 5 章** | 泛型 | 编写可复用的类型安全代码 |
|
||||
| **第 6 章** | 类型推断与实用技巧 | 知道何时需要显式注解 |
|
||||
|
||||
---
|
||||
|
||||
## 1. TypeScript 是什么
|
||||
|
||||
::: tip 🤔 核心问题
|
||||
**JavaScript 已经够用了,为什么还需要 TypeScript?** 多学一门语法值得吗?
|
||||
:::
|
||||
|
||||
### 1.1 从"运行时出错"到"编译时发现"
|
||||
|
||||
<div style="display: flex; gap: 20px; margin: 20px 0;">
|
||||
<div style="flex: 1; padding: 16px; border: 1px solid #e4e7ed; border-radius: 12px;">
|
||||
|
||||
**🔴 JavaScript 的痛点**
|
||||
- 运行时才发现类型错误
|
||||
- 拼写错误难以察觉
|
||||
- 重构时容易遗漏
|
||||
- IDE 提示不够准确
|
||||
|
||||
*就像没有拼写检查的文档编辑器*
|
||||
|
||||
</div>
|
||||
<div style="flex: 1; padding: 16px; border: 1px solid #e4e7ed; border-radius: 12px;">
|
||||
|
||||
**✅ TypeScript 的优势**
|
||||
- 写代码时就发现错误
|
||||
- 智能提示更准确
|
||||
- 重构更安全
|
||||
- 代码更易维护
|
||||
|
||||
*就像有拼写检查和语法高亮的编辑器*
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
**用一句话理解两者的关系:**
|
||||
|
||||
| 技术 | 比喻 | 作用 |
|
||||
|------|------|------|
|
||||
| **JavaScript** | 原始材料 | 可以直接运行的代码 |
|
||||
| **TypeScript** | 蓝图 + 质检 | 给 JavaScript 加类型检查,最后编译成 JavaScript |
|
||||
|
||||
### 1.2 为什么 vibecoding 也需要 TypeScript?
|
||||
|
||||
::: warning AI 写代码也会出错
|
||||
一位开发者用 AI 生成了一个用户管理功能。AI 写的 JavaScript 代码能运行,但有个问题:用户年龄应该是数字,但有时候会被错误地赋值为字符串。
|
||||
|
||||
结果在计算"是否成年"时,字符串 "25" 被当成字符串处理,导致判断失败。这个 bug 隐藏了很久,直到某个用户输入了非数字字符才暴露出来。
|
||||
|
||||
如果用 TypeScript,这段代码在写的时候就会报错:`不能将类型 "string" 分配给类型 "number"`。
|
||||
|
||||
**这就是 TypeScript 的价值——在 AI 写错类型时,你能第一时间发现。**
|
||||
:::
|
||||
|
||||
### 1.3 TypeScript 实际上是这样的
|
||||
|
||||
TypeScript 不是一门全新的语言,它只是 JavaScript 的"超集":
|
||||
|
||||
```typescript
|
||||
// 这是有效的 JavaScript,也是有效的 TypeScript
|
||||
const name = "张三"
|
||||
const age = 25
|
||||
function greet(user) {
|
||||
return `Hello ${user}`
|
||||
}
|
||||
|
||||
// 这是 TypeScript 特有的类型注解
|
||||
const name2: string = "李四"
|
||||
const age2: number = 30
|
||||
function greet2(user: string): string {
|
||||
return `Hello ${user}`
|
||||
}
|
||||
```
|
||||
|
||||
**关键理解:**
|
||||
- 所有 JavaScript 代码都是有效的 TypeScript 代码
|
||||
- TypeScript 添加了可选的**类型注解**
|
||||
- TypeScript 最终会编译成 JavaScript 运行
|
||||
|
||||
::: info 💡 核心启示
|
||||
TypeScript 不会改变代码的运行方式,它只是在编译时帮你检查类型是否正确。**你可以渐进地采用 TypeScript**——从给关键变量添加类型开始。
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## 2. 基础类型注解
|
||||
|
||||
::: tip 🤔 核心问题
|
||||
**怎么告诉 TypeScript 一个变量应该是什么类型?** 类型注解的语法是怎样的?
|
||||
:::
|
||||
|
||||
### 2.1 类型注解语法
|
||||
|
||||
类型注解就是在变量名后面加上`: 类型`:
|
||||
|
||||
```typescript
|
||||
// 语法:变量名: 类型 = 值
|
||||
const name: string = "张三"
|
||||
let age: number = 25
|
||||
let isStudent: boolean = true
|
||||
```
|
||||
|
||||
👇 **动手试试看**:给变量添加类型注解
|
||||
|
||||
<TypeAnnotationDemo />
|
||||
|
||||
::: details 🔍 为什么有些地方不需要类型注解?
|
||||
TypeScript 可以根据赋值自动推断类型:
|
||||
|
||||
```typescript
|
||||
// 这些不需要类型注解,TypeScript 能自动推断
|
||||
const name = "张三" // 推断为 string
|
||||
const age = 25 // 推断为 number
|
||||
const isActive = true // 推断为 boolean
|
||||
|
||||
// 这些情况需要显式注解
|
||||
let data // ❌ 错误:不能推断类型
|
||||
let data: any // ✅ 可以,但失去了类型检查的好处
|
||||
|
||||
function add(a, b) { // ❌ 参数类型不明确
|
||||
return a + b
|
||||
}
|
||||
|
||||
function add2(a: number, b: number): number { // ✅ 类型明确
|
||||
return a + b
|
||||
}
|
||||
```
|
||||
:::
|
||||
|
||||
### 2.2 基本类型
|
||||
|
||||
TypeScript 支持所有 JavaScript 的基本类型:
|
||||
|
||||
| 类型 | 说明 | 示例 |
|
||||
|------|------|------|
|
||||
| `string` | 字符串 | `"hello"`, `'你好'` |
|
||||
| `number` | 数字(整数和小数) | `42`, `3.14` |
|
||||
| `boolean` | 布尔值 | `true`, `false` |
|
||||
| `null` / `undefined` | 空值 | `null`, `undefined` |
|
||||
| `array` | 数组 | `number[]`, `string[]` |
|
||||
| `object` | 对象 | `{ name: string; age: number }` |
|
||||
|
||||
**数组类型的两种写法:**
|
||||
|
||||
```typescript
|
||||
// 写法 1:类型[](更常用)
|
||||
const numbers: number[] = [1, 2, 3, 4, 5]
|
||||
const names: string[] = ["张三", "李四", "王五"]
|
||||
|
||||
// 写法 2:Array<类型>
|
||||
const numbers2: Array<number> = [1, 2, 3, 4, 5]
|
||||
const names2: Array<string> = ["张三", "李四", "王五"]
|
||||
```
|
||||
|
||||
**特殊类型:**
|
||||
|
||||
```typescript
|
||||
// any:任意类型(慎用,相当于关闭类型检查)
|
||||
let data: any = 42
|
||||
data = "现在可以是字符串"
|
||||
data = { name: "张三" } // 也可以是对象
|
||||
|
||||
// unknown:类型安全的 any
|
||||
let value: unknown = 42
|
||||
// if (typeof value === "number") {
|
||||
// console.log(value + 10) // 需要先检查类型才能用
|
||||
// }
|
||||
|
||||
// void:没有返回值
|
||||
function log(message: string): void {
|
||||
console.log(message)
|
||||
}
|
||||
|
||||
// never:永远不会返回
|
||||
function error(message: string): never {
|
||||
throw new Error(message)
|
||||
}
|
||||
```
|
||||
|
||||
::: info 💡 识别技巧
|
||||
- 看到 `: string` → 这是 string 类型的注解
|
||||
- 看到 `: number[]` → 这是数字数组的注解
|
||||
- 看到 `: void` → 这个函数没有返回值
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## 3. 对象类型与接口
|
||||
|
||||
::: tip 🤔 核心问题
|
||||
**怎么定义一个对象的类型?** 对象的属性应该是什么类型?
|
||||
:::
|
||||
|
||||
### 3.1 接口(Interface):定义对象的"形状"
|
||||
|
||||
接口是 TypeScript 中定义对象类型的主要方式:
|
||||
|
||||
```typescript
|
||||
// 定义一个 User 接口
|
||||
interface User {
|
||||
id: number
|
||||
name: string
|
||||
email: string
|
||||
age?: number // 可选属性
|
||||
}
|
||||
|
||||
// 使用接口
|
||||
const user: User = {
|
||||
id: 1,
|
||||
name: "张三",
|
||||
email: "zhangsan@example.com",
|
||||
age: 25
|
||||
}
|
||||
|
||||
// age 是可选的,可以不提供
|
||||
const user2: User = {
|
||||
id: 2,
|
||||
name: "李四",
|
||||
email: "lisi@example.com"
|
||||
}
|
||||
```
|
||||
|
||||
👇 **动手试试看**:创建符合接口定义的对象
|
||||
|
||||
<InterfaceDemo />
|
||||
|
||||
::: details 🔍 接口的其他特性
|
||||
```typescript
|
||||
// 只读属性
|
||||
interface User {
|
||||
readonly id: number // id 创建后不能修改
|
||||
name: string
|
||||
}
|
||||
|
||||
const user: User = {
|
||||
id: 1,
|
||||
name: "张三"
|
||||
}
|
||||
|
||||
user.id = 2 // ❌ 错误:不能修改只读属性
|
||||
user.name = "李四" // ✅ 可以修改
|
||||
|
||||
// 函数类型
|
||||
interface User {
|
||||
name: string
|
||||
greet: () => string // greet 是一个函数,返回 string
|
||||
}
|
||||
|
||||
const user: User = {
|
||||
name: "张三",
|
||||
greet: () => "Hello"
|
||||
}
|
||||
|
||||
// 继承接口
|
||||
interface Admin extends User {
|
||||
permissions: string[]
|
||||
}
|
||||
|
||||
const admin: Admin = {
|
||||
name: "管理员",
|
||||
greet: () => "Hello Admin",
|
||||
permissions: ["read", "write", "delete"]
|
||||
}
|
||||
```
|
||||
:::
|
||||
|
||||
### 3.2 类型别名(Type Alias)
|
||||
|
||||
除了接口,还可以用 `type` 定义类型别名:
|
||||
|
||||
```typescript
|
||||
// 类型别名
|
||||
type User = {
|
||||
id: number
|
||||
name: string
|
||||
email: string
|
||||
}
|
||||
|
||||
// 联合类型
|
||||
type Status = "pending" | "success" | "error"
|
||||
|
||||
const status: Status = "success" // ✅
|
||||
// const status2: Status = "failed" // ❌ 错误:不在联合类型中
|
||||
|
||||
// 交叉类型(合并多个类型)
|
||||
type User = {
|
||||
id: number
|
||||
name: string
|
||||
}
|
||||
|
||||
type Timestamp = {
|
||||
createdAt: Date
|
||||
updatedAt: Date
|
||||
}
|
||||
|
||||
type UserWithTimestamp = User & Timestamp
|
||||
|
||||
const user: UserWithTimestamp = {
|
||||
id: 1,
|
||||
name: "张三",
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date()
|
||||
}
|
||||
```
|
||||
|
||||
**接口 vs 类型别名:**
|
||||
|
||||
| 特性 | interface | type |
|
||||
|------|-----------|------|
|
||||
| 扩展 | `extends` | `&` 交叉类型 |
|
||||
| 重复声明 | 会自动合并 | 会报错 |
|
||||
| 适用场景 | 对象形状、类 | 联合类型、交叉类型、基本类型别名 |
|
||||
|
||||
::: info 💡 识别技巧
|
||||
- 看到 `interface` → 这是定义对象类型
|
||||
- 看到 `type` → 这是创建类型别名
|
||||
- 看到 `?` → 这是可选属性
|
||||
- 看到 `readonly` → 这是只读属性
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## 4. 函数类型
|
||||
|
||||
::: tip 🤔 核心问题
|
||||
**怎么给函数的参数和返回值标注类型?**
|
||||
:::
|
||||
|
||||
### 4.1 参数类型与返回值类型
|
||||
|
||||
```typescript
|
||||
// 完整的函数类型注解
|
||||
function add(a: number, b: number): number {
|
||||
return a + b
|
||||
}
|
||||
|
||||
// 箭头函数
|
||||
const multiply = (a: number, b: number): number => {
|
||||
return a * b
|
||||
}
|
||||
|
||||
// 没有返回值
|
||||
function log(message: string): void {
|
||||
console.log(message)
|
||||
}
|
||||
|
||||
// 返回多种类型(联合类型)
|
||||
function parseInput(input: string): number | string {
|
||||
const num = parseFloat(input)
|
||||
return isNaN(num) ? input : num
|
||||
}
|
||||
```
|
||||
|
||||
### 4.2 可选参数与默认参数
|
||||
|
||||
```typescript
|
||||
// 可选参数(用 ? 标记)
|
||||
function greet(name: string, title?: string): string {
|
||||
return title ? `${title} ${name}` : name
|
||||
}
|
||||
|
||||
greet("张三") // "张三"
|
||||
greet("张三", "先生") // "先生 张三"
|
||||
|
||||
// 默认参数
|
||||
function greet2(name: string, title: string = "朋友"): string {
|
||||
return `${title} ${name}`
|
||||
}
|
||||
|
||||
greet2("李四") // "朋友 李四"
|
||||
greet2("李四", "博士") // "博士 李四"
|
||||
```
|
||||
|
||||
### 4.3 函数类型作为参数
|
||||
|
||||
```typescript
|
||||
// 接受函数作为参数
|
||||
function calculate(
|
||||
a: number,
|
||||
b: number,
|
||||
operation: (x: number, y: number) => number
|
||||
): number {
|
||||
return operation(a, b)
|
||||
}
|
||||
|
||||
calculate(10, 5, (x, y) => x + y) // 15
|
||||
calculate(10, 5, (x, y) => x * y) // 50
|
||||
|
||||
// 更清晰的写法:先定义函数类型
|
||||
type Operation = (x: number, y: number) => number
|
||||
|
||||
function calculate2(
|
||||
a: number,
|
||||
b: number,
|
||||
operation: Operation
|
||||
): number {
|
||||
return operation(a, b)
|
||||
}
|
||||
```
|
||||
|
||||
::: info 💡 识别技巧
|
||||
- 看到 `(a: number, b: number) => number` → 这是函数类型,描述参数和返回值
|
||||
- 看到 `: void` → 函数没有返回值
|
||||
- 看到 `?` → 参数是可选的
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## 5. 泛型
|
||||
|
||||
::: tip 🤔 核心问题
|
||||
**怎么编写能处理多种类型、但保持类型安全的代码?**
|
||||
:::
|
||||
|
||||
### 5.1 泛型的基本概念
|
||||
|
||||
泛型让你在定义函数、接口或类时,不预先指定具体的类型,而是在使用时再指定:
|
||||
|
||||
```typescript
|
||||
// 泛型函数:T 是类型变量
|
||||
function identity<T>(arg: T): T {
|
||||
return arg
|
||||
}
|
||||
|
||||
// 使用时明确指定类型
|
||||
const num1 = identity<number>(42) // 类型是 number
|
||||
const str1 = identity<string>("hello") // 类型是 string
|
||||
|
||||
// 类型推断:TypeScript 能自动推断
|
||||
const num2 = identity(42) // 推断为 number
|
||||
const str2 = identity("hello") // 推断为 string
|
||||
```
|
||||
|
||||
👇 **动手试试看**:使用泛型处理不同类型的数据
|
||||
|
||||
<GenericDemo />
|
||||
|
||||
### 5.2 泛型约束
|
||||
|
||||
限制泛型必须满足某些条件:
|
||||
|
||||
```typescript
|
||||
// 约束 T 必须有 length 属性
|
||||
interface HasLength {
|
||||
length: number
|
||||
}
|
||||
|
||||
function logLength<T extends HasLength>(arg: T): void {
|
||||
console.log(arg.length)
|
||||
}
|
||||
|
||||
logLength("hello") // ✅ 字符串有 length
|
||||
logLength([1, 2, 3]) // ✅ 数组有 length
|
||||
// logLength(42) // ❌ 数字没有 length 属性
|
||||
```
|
||||
|
||||
### 5.3 泛型接口和类
|
||||
|
||||
```typescript
|
||||
// 泛型接口
|
||||
interface Box<T> {
|
||||
value: T
|
||||
getValue(): T
|
||||
}
|
||||
|
||||
const numberBox: Box<number> = {
|
||||
value: 42,
|
||||
getValue: () => 42
|
||||
}
|
||||
|
||||
const stringBox: Box<string> = {
|
||||
value: "hello",
|
||||
getValue: () => "hello"
|
||||
}
|
||||
|
||||
// 泛型类
|
||||
class Storage<T> {
|
||||
private items: T[] = []
|
||||
|
||||
add(item: T): void {
|
||||
this.items.push(item)
|
||||
}
|
||||
|
||||
get(index: number): T {
|
||||
return this.items[index]
|
||||
}
|
||||
}
|
||||
|
||||
const numberStorage = new Storage<number>()
|
||||
numberStorage.add(1)
|
||||
numberStorage.add(2)
|
||||
// numberStorage.add("string") // ❌ 错误
|
||||
|
||||
const stringStorage = new Storage<string>()
|
||||
stringStorage.add("hello")
|
||||
// stringStorage.add(1) // ❌ 错误
|
||||
```
|
||||
|
||||
::: info 💡 识别技巧
|
||||
- 看到 `<T>` → 这是泛型类型变量
|
||||
- 看到 `<T extends SomeType>` → 泛型约束
|
||||
- 看到 `Array<T>` 或 `Promise<T>` → 内置泛型类型
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## 6. 类型推断与实用技巧
|
||||
|
||||
::: tip 🤔 核心问题
|
||||
**什么时候需要显式类型注解?什么时候可以依赖推断?**
|
||||
:::
|
||||
|
||||
### 6.1 类型推断
|
||||
|
||||
TypeScript 能根据上下文自动推断类型:
|
||||
|
||||
```typescript
|
||||
// 变量初始化时的推断
|
||||
const name = "张三" // 推断为 string
|
||||
const age = 25 // 推断为 number
|
||||
const isActive = true // 推断为 boolean
|
||||
|
||||
// 数组推断
|
||||
const numbers = [1, 2, 3] // 推断为 number[]
|
||||
const mixed = [1, "hello", true] // 推断为 (number | string | boolean)[]
|
||||
|
||||
// 函数返回值推断
|
||||
function add(a: number, b: number) {
|
||||
return a + b // 推断返回值为 number
|
||||
}
|
||||
```
|
||||
|
||||
👇 **动手试试看**:观察 TypeScript 如何推断类型
|
||||
|
||||
<TypeInferenceDemo />
|
||||
|
||||
### 6.2 何时使用显式类型注解
|
||||
|
||||
::: details 推荐使用类型推断的场景
|
||||
```typescript
|
||||
// ✅ 推荐:简单的字面量赋值
|
||||
const count = 0
|
||||
const name = "张三"
|
||||
const isActive = true
|
||||
|
||||
// ✅ 推荐:函数返回值可以推断
|
||||
function getUserId(user: User) {
|
||||
return user.id // 推断为 number
|
||||
}
|
||||
```
|
||||
:::
|
||||
|
||||
::: details 推荐使用显式注解的场景
|
||||
```typescript
|
||||
// ✅ 推荐:函数参数(必须)
|
||||
function add(a: number, b: number) {
|
||||
return a + b
|
||||
}
|
||||
|
||||
// ✅ 推荐:对象属性类型不明确
|
||||
const user: {
|
||||
id: number
|
||||
name: string
|
||||
metadata: Record<string, any>
|
||||
} = {
|
||||
id: 1,
|
||||
name: "张三",
|
||||
metadata: {} // 可能推断为 {},需要明确指定
|
||||
}
|
||||
|
||||
// ✅ 推荐:函数返回类型复杂
|
||||
function getUser(): User | null {
|
||||
// ...
|
||||
return null
|
||||
}
|
||||
|
||||
// ✅ 推荐:公共 API
|
||||
export function calculateTotal(prices: number[]): number {
|
||||
return prices.reduce((sum, price) => sum + price, 0)
|
||||
}
|
||||
```
|
||||
:::
|
||||
|
||||
### 6.3 类型守卫
|
||||
|
||||
在运行时检查类型:
|
||||
|
||||
```typescript
|
||||
// typeof 类型守卫
|
||||
function processValue(value: string | number) {
|
||||
if (typeof value === "string") {
|
||||
// 这里 TypeScript 知道 value 是 string
|
||||
console.log(value.toUpperCase())
|
||||
} else {
|
||||
// 这里 TypeScript 知道 value 是 number
|
||||
console.log(value * 2)
|
||||
}
|
||||
}
|
||||
|
||||
// instanceof 类型守卫
|
||||
class Dog {
|
||||
bark() {
|
||||
console.log("汪汪")
|
||||
}
|
||||
}
|
||||
|
||||
class Cat {
|
||||
meow() {
|
||||
console.log("喵喵")
|
||||
}
|
||||
}
|
||||
|
||||
function makeSound(animal: Dog | Cat) {
|
||||
if (animal instanceof Dog) {
|
||||
animal.bark() // TypeScript 知道这是 Dog
|
||||
} else {
|
||||
animal.meow() // TypeScript 知道这是 Cat
|
||||
}
|
||||
}
|
||||
|
||||
// 自定义类型守卫
|
||||
interface User {
|
||||
name: string
|
||||
email: string
|
||||
}
|
||||
|
||||
function isUser(value: any): value is User {
|
||||
return (
|
||||
typeof value === "object" &&
|
||||
value !== null &&
|
||||
typeof value.name === "string" &&
|
||||
typeof value.email === "string"
|
||||
)
|
||||
}
|
||||
|
||||
function processValue(value: unknown) {
|
||||
if (isUser(value)) {
|
||||
// 这里 value 是 User
|
||||
console.log(value.name)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 6.4 实用工具类型
|
||||
|
||||
TypeScript 提供了一些内置的工具类型:
|
||||
|
||||
```typescript
|
||||
// Partial:将所有属性变为可选
|
||||
interface User {
|
||||
id: number
|
||||
name: string
|
||||
email: string
|
||||
}
|
||||
|
||||
type PartialUser = Partial<User>
|
||||
// 等价于:{ id?: number; name?: string; email?: string }
|
||||
|
||||
// Required:将所有属性变为必需
|
||||
type RequiredUser = Required<PartialUser>
|
||||
// 等价于:{ id: number; name: number; email: string }
|
||||
|
||||
// Pick:只保留指定的属性
|
||||
type UserBasicInfo = Pick<User, "id" | "name">
|
||||
// 等价于:{ id: number; name: string }
|
||||
|
||||
// Omit:排除指定的属性
|
||||
type UserWithoutEmail = Omit<User, "email">
|
||||
// 等价于:{ id: number; name: string }
|
||||
|
||||
// Record:创建对象类型
|
||||
type UserRoles = Record<string, boolean>
|
||||
// 等价于:{ [key: string]: boolean }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 实战技巧:在 vibecoding 中使用 TypeScript
|
||||
|
||||
::: tip 🤔 核心问题
|
||||
**怎么在 AI 辅助开发中更好地利用 TypeScript?**
|
||||
:::
|
||||
|
||||
### 7.1 让 AI 生成类型安全代码
|
||||
|
||||
**❌ 不好的提示词:**
|
||||
```
|
||||
帮我写一个用户管理功能
|
||||
```
|
||||
|
||||
**✅ 好的提示词:**
|
||||
```
|
||||
帮我写一个用户管理功能,使用 TypeScript。
|
||||
|
||||
数据结构定义如下:
|
||||
interface User {
|
||||
id: number
|
||||
name: string
|
||||
email: string
|
||||
age: number
|
||||
}
|
||||
|
||||
需要实现:
|
||||
1. 获取用户列表:返回 User[]
|
||||
2. 创建用户:接受 Partial<User>,返回 User
|
||||
3. 更新用户:接受 id 和 Partial<User>,返回 User
|
||||
4. 删除用户:接受 id,返回 void
|
||||
|
||||
请确保所有函数都有完整的类型注解。
|
||||
```
|
||||
|
||||
### 7.2 看懂 TypeScript 错误信息
|
||||
|
||||
**常见错误及含义:**
|
||||
|
||||
| 错误信息 | 含义 | 解决方法 |
|
||||
|---------|------|---------|
|
||||
| `Type 'X' is not assignable to type 'Y'` | 类型 X 不能赋值给类型 Y | 检查类型是否匹配,或进行类型转换 |
|
||||
| `Property 'X' does not exist on type 'Y'` | 类型 Y 上不存在属性 X | 检查属性名拼写,或定义该属性 |
|
||||
| `Argument of type 'X' is not assignable to parameter of type 'Y'` | 参数类型不匹配 | 检查函数调用时的参数类型 |
|
||||
| `Type 'X' is missing the following properties from type 'Y'` | 类型 X 缺少类型 Y 的某些属性 | 补全缺失的属性 |
|
||||
|
||||
### 7.3 渐进式采用 TypeScript
|
||||
|
||||
如果你有一个 JavaScript 项目,可以渐进地迁移到 TypeScript:
|
||||
|
||||
1. **第一步:将文件重命名为 `.ts`**
|
||||
```bash
|
||||
# 从 utils.js 改为 utils.ts
|
||||
mv utils.js utils.ts
|
||||
```
|
||||
|
||||
2. **第二步:修复明显的类型错误**
|
||||
```typescript
|
||||
// 如果报错:Parameter 'a' implicitly has an 'any' type
|
||||
// 添加类型注解
|
||||
function add(a: number, b: number) {
|
||||
return a + b
|
||||
}
|
||||
```
|
||||
|
||||
3. **第三步:逐步添加类型定义**
|
||||
```typescript
|
||||
// 先用 any 快速修复
|
||||
function processUser(user: any) {
|
||||
// ...
|
||||
}
|
||||
|
||||
// 后续再完善类型
|
||||
interface User {
|
||||
id: number
|
||||
name: string
|
||||
}
|
||||
|
||||
function processUser(user: User) {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
4. **第四步:启用更严格的类型检查**
|
||||
```json
|
||||
// tsconfig.json
|
||||
{
|
||||
"compilerOptions": {
|
||||
"strict": true, // 启用严格模式
|
||||
"noImplicitAny": true, // 禁止隐式 any
|
||||
"strictNullChecks": true // 严格空值检查
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. 你现在应该能识别的代码
|
||||
|
||||
- 看到 `: string` → 这是 string 类型的注解
|
||||
- 看到 `: number[]` → 这是数字数组的注解
|
||||
- 看到 `interface User` → 这是定义对象类型
|
||||
- 看到 `type User =` → 这是类型别名
|
||||
- 看到 `<T>` → 这是泛型
|
||||
- 看到 `extends` → 接口继承或泛型约束
|
||||
- 看到 `?` → 可选属性
|
||||
- 看到 `readonly` → 只读属性
|
||||
- 看到 `|` → 联合类型
|
||||
- 看到 `&` → 交叉类型
|
||||
|
||||
**如果你认真读了每章的"深入"部分,你还掌握了这些核心概念:**
|
||||
|
||||
- **类型注解**:明确告诉 TypeScript 变量的类型
|
||||
- **接口**:定义对象的结构和类型
|
||||
- **泛型**:编写可复用的类型安全代码
|
||||
- **类型推断**:TypeScript 自动推断类型
|
||||
- **类型守卫**:运行时检查类型
|
||||
- **工具类型**:Partial、Required、Pick、Omit 等
|
||||
|
||||
::: info 💡 遇到问题时这样跟 AI 说
|
||||
- "这个函数的类型注解应该怎么写?参数是 X,返回值是 Y"
|
||||
- "帮我定义一个接口,描述这个数据结构:..."
|
||||
- "这个 TypeScript 错误是什么意思?怎么修复?"
|
||||
- "如何给这个泛型函数添加约束,确保 T 必须有某个属性?"
|
||||
:::
|
||||
|
||||
Reference in New Issue
Block a user