diff --git a/CLAUDE.md b/CLAUDE.md index 16a1afb..68ec47d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -205,13 +205,94 @@ Prettier configuration (`.prettierrc`): Run `npm run format` before committing code changes. +## Interactive Vue Components + +### Component Registration + +All interactive Vue components for the documentation are registered in `docs/.vitepress/theme/index.js`. To add a new component: + +1. Create the `.vue` file in the appropriate subdirectory of `docs/.vitepress/theme/components/` +2. Import the component in `docs/.vitepress/theme/index.js` +3. Register the component using `app.component('ComponentName', ComponentName)` in the `enhanceApp` function + +### Component Categories + +Components are organized by topic: + +- `appendix/llm-intro/` - Large Language Model interactive demos +- `appendix/vlm-intro/` - Vision Language Model interactive demos +- `appendix/git-intro/` - Git workflow visualizations +- `appendix/terminal-intro/` - Terminal/CLI interactive demos +- `appendix/web-basics/` - HTML/CSS/JavaScript fundamentals +- `appendix/auth-design/` - Authentication/authorization demos +- `appendix/cache-design/` - Caching strategy visualizations +- `appendix/database-intro/` - Database fundamentals +- `appendix/queue-design/` - Message queue demos +- `appendix/operations/` - DevOps/monitoring demos +- `appendix/deployment/` - Deployment architecture demos +- `appendix/frontend-performance/` - Frontend performance demos +- `appendix/frontend-evolution/` - Frontend history/evolution demos +- `appendix/backend-evolution/` - Backend architecture evolution +- `appendix/backend-languages/` - Backend language comparisons + +### Using Components in Markdown + +Components can be used directly in markdown files: + +```markdown +## LLM Basics + + + +### Tokenization + + +``` + +### Component Development Best Practices + +1. **Props**: Use props for configurable demo parameters +2. **Styling**: Use scoped CSS or Tailwind-like utility classes +3. **Responsiveness**: Ensure components work on mobile and desktop +4. **Accessibility**: Include aria labels where appropriate +5. **i18n**: Keep text content minimal or use props for text + +## Multi-language Support + +### Supported Locales + +The project supports 13 languages: + +- `zh-cn` - Simplified Chinese (primary) +- `zh-tw` - Traditional Chinese +- `en-us` - English (US) +- `ja-jp` - Japanese +- `ko-kr` - Korean +- `es-es` - Spanish +- `fr-fr` - French +- `de-de` - German +- `ar-sa` - Arabic +- `vi-vn` - Vietnamese + +### Adding Multi-language Content + +1. Create content in `docs/{locale}/` following the same structure as `docs/zh-cn/` +2. Add locale configuration in `docs/.vitepress/config.mjs` under `locales` +3. Copy the sidebar structure from `zh-cn` and translate the text values + +### Content Translation Priority + +1. **Primary**: `zh-cn` (Simplified Chinese) - always complete this first +2. **Secondary**: `en-us` (English) - for international reach +3. **Tertiary**: Other languages based on contributor availability + ## Permissions The project has configured bash permissions in `.claude/settings.local.json`: - File operations: `which`, `find`, `mv`, `tree`, `cat`, `curl`, `lsof`, `mkdir`, `cp`, `ls` - Process management: `xargs ps`, `kill` -- Development: `npm run dev` +- Development: `npm run dev`, `npm run build`, `npm run preview`, `npm run format` ## Key Context for Development diff --git a/docs/.vitepress/config.mjs b/docs/.vitepress/config.mjs index 35f8459..11a3c41 100644 --- a/docs/.vitepress/config.mjs +++ b/docs/.vitepress/config.mjs @@ -314,7 +314,7 @@ export default defineConfig({ { text: '首页', link: '/zh-cn/' }, { text: '新手与产品原型', - link: '/zh-cn/stage-0/intro' + link: '/zh-cn/stage-0/' }, { text: '初中级开发', @@ -672,7 +672,7 @@ export default defineConfig({ { text: 'Home', link: '/en-us/' }, { text: 'Novice & PM', - link: '/en-us/stage-0/intro' + link: '/en-us/stage-0/' }, { text: 'Full-Stack Development', @@ -712,7 +712,7 @@ export default defineConfig({ { text: 'ホーム', link: '/ja-jp/' }, { text: '初心者とPM', - link: '/ja-jp/stage-0/intro' + link: '/ja-jp/stage-0/' }, { text: 'フルスタック開発', @@ -750,7 +750,7 @@ export default defineConfig({ { text: '首頁', link: '/zh-tw/' }, { text: '新手與產品原型', - link: '/zh-tw/stage-0/intro' + link: '/zh-tw/stage-0/' }, { text: '初中級開發', @@ -785,7 +785,7 @@ export default defineConfig({ }, nav: [ { text: '홈', link: '/ko-kr/' }, - { text: '초보자 & PM', link: '/ko-kr/stage-0/intro' }, + { text: '초보자 & PM', link: '/ko-kr/stage-0/' }, { text: '풀스택 개발', link: '/ko-kr/stage-2/intro' @@ -821,7 +821,7 @@ export default defineConfig({ { text: 'Inicio', link: '/es-es/' }, { text: 'Principiante y PM', - link: '/es-es/stage-0/intro' + link: '/es-es/stage-0/' }, { text: 'Desarrollo Full Stack', @@ -856,7 +856,7 @@ export default defineConfig({ }, nav: [ { text: 'Accueil', link: '/fr-fr/' }, - { text: 'Débutant & PM', link: '/fr-fr/stage-0/intro' }, + { text: 'Débutant & PM', link: '/fr-fr/stage-0/' }, { text: 'Développement Full Stack', link: '/fr-fr/stage-2/intro' @@ -890,7 +890,7 @@ export default defineConfig({ }, nav: [ { text: 'Start', link: '/de-de/' }, - { text: 'Anfänger & PM', link: '/de-de/stage-0/intro' }, + { text: 'Anfänger & PM', link: '/de-de/stage-0/' }, { text: 'Full Stack Entwicklung', link: '/de-de/stage-2/intro' @@ -926,7 +926,7 @@ export default defineConfig({ { text: 'الرئيسية', link: '/ar-sa/' }, { text: 'مبتدأ & PM', - link: '/ar-sa/stage-0/intro' + link: '/ar-sa/stage-0/' }, { text: 'تطوير Full Stack', @@ -963,7 +963,7 @@ export default defineConfig({ { text: 'Trang chủ', link: '/vi-vn/' }, { text: 'Người mới & PM', - link: '/vi-vn/stage-0/intro' + link: '/vi-vn/stage-0/' }, { text: 'Phát triển Full Stack', diff --git a/docs/.vitepress/theme/components/appendix/backend-evolution/BackendQuickStartDemo.vue b/docs/.vitepress/theme/components/appendix/backend-evolution/BackendQuickStartDemo.vue new file mode 100644 index 0000000..e83ce5e --- /dev/null +++ b/docs/.vitepress/theme/components/appendix/backend-evolution/BackendQuickStartDemo.vue @@ -0,0 +1,454 @@ + + + + + diff --git a/docs/.vitepress/theme/components/appendix/frontend-evolution/ImperativeVsDeclarativeDemo.vue b/docs/.vitepress/theme/components/appendix/frontend-evolution/ImperativeVsDeclarativeDemo.vue new file mode 100644 index 0000000..797b5bf --- /dev/null +++ b/docs/.vitepress/theme/components/appendix/frontend-evolution/ImperativeVsDeclarativeDemo.vue @@ -0,0 +1,490 @@ + + + + + + diff --git a/docs/.vitepress/theme/components/appendix/frontend-evolution/JQueryVsStateDemo.vue b/docs/.vitepress/theme/components/appendix/frontend-evolution/JQueryVsStateDemo.vue new file mode 100644 index 0000000..a701dc3 --- /dev/null +++ b/docs/.vitepress/theme/components/appendix/frontend-evolution/JQueryVsStateDemo.vue @@ -0,0 +1,804 @@ + + + + + + diff --git a/docs/.vitepress/theme/components/appendix/frontend-evolution/ResponsiveGridDemo.vue b/docs/.vitepress/theme/components/appendix/frontend-evolution/ResponsiveGridDemo.vue new file mode 100644 index 0000000..463ea7e --- /dev/null +++ b/docs/.vitepress/theme/components/appendix/frontend-evolution/ResponsiveGridDemo.vue @@ -0,0 +1,532 @@ + + + + + + diff --git a/docs/.vitepress/theme/components/appendix/frontend-evolution/SliceRequestDemo.vue b/docs/.vitepress/theme/components/appendix/frontend-evolution/SliceRequestDemo.vue new file mode 100644 index 0000000..8f2e90e --- /dev/null +++ b/docs/.vitepress/theme/components/appendix/frontend-evolution/SliceRequestDemo.vue @@ -0,0 +1,717 @@ + + + + + + diff --git a/docs/.vitepress/theme/components/appendix/frontend-performance/PerformanceOverviewDemo.vue b/docs/.vitepress/theme/components/appendix/frontend-performance/PerformanceOverviewDemo.vue new file mode 100644 index 0000000..f5cece5 --- /dev/null +++ b/docs/.vitepress/theme/components/appendix/frontend-performance/PerformanceOverviewDemo.vue @@ -0,0 +1,320 @@ + + + + + + diff --git a/docs/.vitepress/theme/components/appendix/llm-intro/MoEDemo.vue b/docs/.vitepress/theme/components/appendix/llm-intro/MoEDemo.vue index f8390cd..cef27c8 100644 --- a/docs/.vitepress/theme/components/appendix/llm-intro/MoEDemo.vue +++ b/docs/.vitepress/theme/components/appendix/llm-intro/MoEDemo.vue @@ -15,8 +15,8 @@
{{ architecture === 'dense' - ? '全能天才:每个问题都动用整个大脑 (100% 激活)' - : '专家团队:根据问题指派专人处理 (稀疏激活)' + ? '全能天才:每个 Token 都激活所有神经元 (100% 激活)' + : '专家团队:每个 Token 路由给特定专家 (Token-Level Routing)' }}
@@ -25,7 +25,7 @@
- +
-
-
{{ selectedTask.icon }}
-
- -
⬇️
- - -
- - - -
-
-
前馈神经网络 (FFN)
-
-
-
-
- 🔥 激活率: 100% (全员过载) -
+ +
+ +
+
+ Current Token: + + {{ currentToken?.text || '...' }} +
- -
- -
-
门控路由 (Router)
-
- 🔍 识别意图: "{{ selectedTask.type }}" -
+ +
+ - -
+ +
-
- - -
-
-
{{ expert.icon }}
-
{{ expert.name }}
-
{{ expert.role }}
+
Dense FFN Layers
+
+
+
+
+ 🔥 激活率: 100% (All Parameters) +
+
+
+ + +
+ +
+
Router (Token 分发)
+
+ Routing "{{ currentToken.text.trim() }}" → {{ experts[currentToken.expert].name }} +
+
+ + +
+
+ + +
+
- ✅ 激活 +
{{ expert.icon }}
+
{{ expert.name }}
+
+ ⚡ Active +
- -
⬇️
-
- -
-
- {{ selectedTask.icon }} - {{ selectedTask.output }} -
-
等待处理...
+ +
+ + {{ token.text }} + | + +
点击运行查看生成过程...
@@ -145,7 +156,7 @@
@@ -156,43 +167,54 @@ import { ref, computed } from 'vue' const architecture = ref('moe') const processing = ref(false) -const currentStep = ref(0) // 0: idle, 1: router, 2: experts, 3: output +const currentStep = ref('idle') // idle, router, expert +const currentToken = ref(null) +const generatedTokens = ref([]) const experts = [ - { icon: '💻', name: '代码专家', role: 'Python/JS/Rust' }, - { icon: '🎨', name: '创意专家', role: '诗歌/小说/绘画' }, - { icon: '📐', name: '逻辑专家', role: '数学/推理/证明' }, - { icon: '🌍', name: '语言专家', role: '翻译/润色/摘要' } + { icon: '💻', name: 'Code', color: '#059669' }, // Green + { icon: '📐', name: 'Math', color: '#2563eb' }, // Blue + { icon: '🎨', name: 'Creative', color: '#d97706' }, // Amber + { icon: '📝', name: 'Grammar', color: '#7c3aed' } // Purple ] const tasks = [ { - label: '写 Python 脚本', - type: '编程', + label: 'Python 代码示例', icon: '🐍', - expertIdx: 0, - output: 'def fib(n): return n if n < 2 else...' + tokens: [ + { text: 'def', expert: 0 }, + { text: ' calc', expert: 3 }, + { text: '_area', expert: 0 }, + { text: '(', expert: 3 }, + { text: 'r', expert: 0 }, + { text: '):', expert: 0 }, + { text: '\n ', expert: 3 }, + { text: 'return', expert: 0 }, + { text: ' 3.14', expert: 1 }, // Math + { text: ' *', expert: 1 }, + { text: ' r', expert: 0 }, + { text: ' **', expert: 1 }, + { text: ' 2', expert: 1 } + ] }, { - label: '写七言绝句', - type: '文学', - icon: '🌸', - expertIdx: 1, - output: '窗含西岭千秋雪,门泊东吴万里船...' - }, - { - label: '解二元方程', - type: '数学', - icon: '✖️', - expertIdx: 2, - output: 'x = 5, y = -2 (过程略)' - }, - { - label: '翻译成英文', - type: '翻译', - icon: '🔤', - expertIdx: 3, - output: 'To be, or not to be, that is the question.' + label: '科幻小说片段', + icon: '🚀', + tokens: [ + { text: 'The', expert: 3 }, + { text: ' spaceship', expert: 2 }, + { text: ' warped', expert: 2 }, + { text: ' into', expert: 3 }, + { text: ' dimension', expert: 1 }, // Logic/Math concept + { text: ' X', expert: 2 }, + { text: '.', expert: 3 }, + { text: ' Coordinates', expert: 1 }, + { text: ':', expert: 3 }, + { text: ' 42', expert: 1 }, + { text: '.', expert: 3 }, + { text: '00', expert: 1 } + ] } ] @@ -211,33 +233,39 @@ const selectTask = (task) => { } const resetDemo = () => { - currentStep.value = 0 + currentStep.value = 'idle' + generatedTokens.value = [] + currentToken.value = null } -const isExpertSelected = (idx) => { - if (architecture.value === 'dense') return true // All active in dense - return idx === selectedTask.value.expertIdx +const getExpertColor = (expertIdx) => { + if (expertIdx === undefined || architecture.value === 'dense') return 'var(--vp-c-text-1)' + return experts[expertIdx].color } const runDemo = async () => { if (processing.value) return processing.value = true - currentStep.value = 0 + resetDemo() - // Step 1: Input -> Router - await wait(300) - currentStep.value = 1 + for (const token of selectedTask.value.tokens) { + currentToken.value = token + + // Step 1: Router (MoE only) or Prep (Dense) + currentStep.value = 'router' + await wait(architecture.value === 'moe' ? 400 : 200) - // Step 2: Router -> Expert / Dense Processing - await wait(800) - currentStep.value = 2 + // Step 2: Expert Processing + currentStep.value = 'expert' + await wait(architecture.value === 'moe' ? 600 : 400) // Dense might be slower in reality, but for demo keep it brisk - // Step 3: Expert -> Output - await wait(1200) - currentStep.value = 3 + // Step 3: Output + generatedTokens.value.push(token) + await wait(200) + } - // Finish - await wait(500) + currentStep.value = 'idle' + currentToken.value = null processing.value = false } @@ -246,8 +274,7 @@ const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms)) + \ No newline at end of file diff --git a/docs/.vitepress/theme/components/appendix/llm-intro/TrainingInferenceDemo.vue b/docs/.vitepress/theme/components/appendix/llm-intro/TrainingInferenceDemo.vue index 6eb4c29..c1ffbe8 100644 --- a/docs/.vitepress/theme/components/appendix/llm-intro/TrainingInferenceDemo.vue +++ b/docs/.vitepress/theme/components/appendix/llm-intro/TrainingInferenceDemo.vue @@ -238,8 +238,8 @@ > {{ isPredictionCorrect - ? '✅ Parameters Good' - : '❌ Update Weights' + ? '✅ Good Prediction' + : '🔧 Adjusting Weights' }}
diff --git a/docs/.vitepress/theme/index.js b/docs/.vitepress/theme/index.js index 6a48e2e..3a997b7 100644 --- a/docs/.vitepress/theme/index.js +++ b/docs/.vitepress/theme/index.js @@ -144,6 +144,11 @@ import GPTEvolutionDemo from './components/appendix/ai-history/GPTEvolutionDemo. import ImperativeVsDeclarativeDemo from './components/appendix/web-basics/ImperativeVsDeclarativeDemo.vue' import ComponentReusabilityDemo from './components/appendix/web-basics/ComponentReusabilityDemo.vue' +// Frontend Evolution Components +import EvolutionSliceRequestDemo from './components/appendix/frontend-evolution/SliceRequestDemo.vue' +import EvolutionResponsiveGridDemo from './components/appendix/frontend-evolution/ResponsiveGridDemo.vue' +import EvolutionJQueryVsStateDemo from './components/appendix/frontend-evolution/JQueryVsStateDemo.vue' + import BackendEvolutionDemo from './components/appendix/backend-evolution/BackendEvolutionDemo.vue' import MonolithVsMicroserviceDemo from './components/appendix/backend-evolution/MonolithVsMicroserviceDemo.vue' import CgiQueueDemo from './components/appendix/backend-evolution/CgiQueueDemo.vue' @@ -154,6 +159,7 @@ import ServerlessCostAutoScaleDemo from './components/appendix/backend-evolution // Frontend Performance Components import PerformanceMetricsDemo from './components/appendix/frontend-performance/PerformanceMetricsDemo.vue' +import PerformanceOverviewDemo from './components/appendix/frontend-performance/PerformanceOverviewDemo.vue' import ReflowRepaintDemo from './components/appendix/frontend-performance/ReflowRepaintDemo.vue' import ImageOptimizationDemo from './components/appendix/frontend-performance/ImageOptimizationDemo.vue' import LazyLoadingDemo from './components/appendix/frontend-performance/LazyLoadingDemo.vue' @@ -433,6 +439,11 @@ export default { app.component('ImperativeVsDeclarativeDemo', ImperativeVsDeclarativeDemo) app.component('ComponentReusabilityDemo', ComponentReusabilityDemo) + // Frontend Evolution Components Registration + app.component('EvolutionSliceRequestDemo', EvolutionSliceRequestDemo) + app.component('EvolutionResponsiveGridDemo', EvolutionResponsiveGridDemo) + app.component('EvolutionJQueryVsStateDemo', EvolutionJQueryVsStateDemo) + app.component('BackendEvolutionDemo', BackendEvolutionDemo) app.component('MonolithVsMicroserviceDemo', MonolithVsMicroserviceDemo) app.component('CgiQueueDemo', CgiQueueDemo) @@ -441,8 +452,9 @@ export default { app.component('CacheHitRatioDemo', CacheHitRatioDemo) app.component('ServerlessCostAutoScaleDemo', ServerlessCostAutoScaleDemo) - // Frontend Performance Components Registration + // Frontend Performance Components app.component('PerformanceMetricsDemo', PerformanceMetricsDemo) + app.component('PerformanceOverviewDemo', PerformanceOverviewDemo) app.component('ReflowRepaintDemo', ReflowRepaintDemo) app.component('ImageOptimizationDemo', ImageOptimizationDemo) app.component('LazyLoadingDemo', LazyLoadingDemo) diff --git a/docs/ar-sa/stage-0/intro.md b/docs/ar-sa/stage-0/index.md similarity index 100% rename from docs/ar-sa/stage-0/intro.md rename to docs/ar-sa/stage-0/index.md diff --git a/docs/de-de/stage-0/intro.md b/docs/de-de/stage-0/index.md similarity index 100% rename from docs/de-de/stage-0/intro.md rename to docs/de-de/stage-0/index.md diff --git a/docs/en-us/stage-0/intro.md b/docs/en-us/stage-0/index.md similarity index 100% rename from docs/en-us/stage-0/intro.md rename to docs/en-us/stage-0/index.md diff --git a/docs/es-es/stage-0/intro.md b/docs/es-es/stage-0/index.md similarity index 100% rename from docs/es-es/stage-0/intro.md rename to docs/es-es/stage-0/index.md diff --git a/docs/fr-fr/stage-0/intro.md b/docs/fr-fr/stage-0/index.md similarity index 100% rename from docs/fr-fr/stage-0/intro.md rename to docs/fr-fr/stage-0/index.md diff --git a/docs/ja-jp/stage-0/intro.md b/docs/ja-jp/stage-0/index.md similarity index 100% rename from docs/ja-jp/stage-0/intro.md rename to docs/ja-jp/stage-0/index.md diff --git a/docs/ko-kr/stage-0/intro.md b/docs/ko-kr/stage-0/index.md similarity index 100% rename from docs/ko-kr/stage-0/intro.md rename to docs/ko-kr/stage-0/index.md diff --git a/docs/public/favicon.ico b/docs/public/favicon.ico new file mode 100644 index 0000000..31f9efc Binary files /dev/null and b/docs/public/favicon.ico differ diff --git a/docs/vi-vn/stage-0/intro.md b/docs/vi-vn/stage-0/index.md similarity index 100% rename from docs/vi-vn/stage-0/intro.md rename to docs/vi-vn/stage-0/index.md diff --git a/docs/zh-cn/appendix/ai-evolution.md b/docs/zh-cn/appendix/ai-evolution.md index f8cd6eb..49b9d41 100644 --- a/docs/zh-cn/appendix/ai-evolution.md +++ b/docs/zh-cn/appendix/ai-evolution.md @@ -49,6 +49,8 @@ function decideTrafficLight(color) { return 'caution' } else if (color === 'green') { return 'go' + } else { + return 'unknown' } } ``` @@ -60,10 +62,10 @@ function decideTrafficLight(color) { 它的工作原理是: ```lisp -;; MYCIN 系统的规则示例 (伪代码) +// MYCIN 系统的规则示例 (伪代码) (IF - (organism IS gram-positive) - (morphology IS coccus) + (organism IS gram-positive) AND + (morphology IS coccus) AND (growth-chains IS chains) THEN (identity IS 0.7 streptococcus)) @@ -242,7 +244,7 @@ _数据示例 (训练数据格式)_: - **黑盒问题**:虽然能识别猫,但我们说不清"它是怎么识别的" - **数据饥渴**:需要海量标注数据,获取成本高 -- **缺乏常识**:能认猫,但不知道"猫会怕狗" +- **缺乏常识**:能识别出这是“猫”,但理解不了“猫喜欢抓老鼠”或“猫通常怕狗”这种常识关系(因为它只是在做像素级的统计匹配,而非真正的概念理解) --- diff --git a/docs/zh-cn/appendix/database-intro.md b/docs/zh-cn/appendix/database-intro.md index bb56702..a76f91a 100644 --- a/docs/zh-cn/appendix/database-intro.md +++ b/docs/zh-cn/appendix/database-intro.md @@ -1,125 +1,430 @@ # 数据库原理入门:从 Excel 到 SQL -> 💡 **学习指南**:本章节无需计算机专业背景。我们将从你熟悉的 Excel 表格开始,一步步带你理解数据库(Database)的核心原理,并揭示它为什么能从数十亿条数据中瞬间找到你想要的那一条。 +> 💡 **学习指南**:本章节无需编程基础,我们将从你熟悉的 Excel 表格出发,一步步深入数据库的底层原理。你会明白为什么数据库能从数十亿条数据中瞬间找到你想要的那一条,以及它背后那个神奇的 B+ 树是如何工作的。 -## 1. 数据的进化:为什么要用数据库? + + +## 0. 引言:当数据像滚雪球一样增长 + +人类天生擅长处理小数据。一家小店里的账目、一个班级里的学生名单,我们可以轻松地记在脑子里,或者写在纸上。 + +但是,当数据开始**像滚雪球一样增长**时——从 100 条变成 100 万条,从 1 个用户变成 1 亿个用户——我们遇到了一个核心问题: + +**如何高效地存取海量数据?** + +这个问题的答案,就是**数据库 (Database)**。 + +本教程将带你从零开始,一步步拆解数据库这座大厦的构建过程: + +1. **存储**:数据是如何从 Excel 进化到数据库的? +2. **组织**:表、列、行、关系,这些概念到底是什么? +3. **语言**:如何用 SQL 与数据库对话? +4. **速度**:为什么数据库能毫秒级查询?(揭秘 B+ 树索引) + +--- + +## 1. 数据的进化:从记事本到数据库 想象一下,你经营着一家小书店。 -### 第一阶段:记事本 +### 1.1 第一阶段:记事本 刚开始,你每天卖出几本书,随手记在**记事本**上。 -- _优点_:简单,拿笔就能写。 -- _缺点_:想知道“上个月一共卖了多少钱?”或者“哪本书卖得最好?”,你需要一页页翻,按着计算器算半天。 +- **优点**:简单,拿笔就能写。 +- **缺点**: + - 想知道"上个月一共卖了多少钱?"——你得一页页翻,按着计算器算半天。 + - 想知道"哪本书卖得最好?"——你可能需要手动数每本书出现的次数。 -### 第二阶段:Excel 表格 +这就是**无结构化数据**的困境:数据是死的,想要获得洞察,需要人工大量加工。 + +### 1.2 第二阶段:Excel 表格 生意好了,你开始用 **Excel**。 + 你建了一张表,列出了:`书名`、`价格`、`购买者`、`日期`。 -- _优点_:可以自动求和,可以排序,可以筛选。 -- _缺点_: +| 书名 | 价格 | 购买者 | 日期 | +|------|------|--------|------| +| 百年孤独 | 59 | 张三 | 2024-01-15 | +| 活着 | 39 | 李四 | 2024-01-16 | + +- **优点**: + - 可以**自动求和**(SUM 函数)。 + - 可以**排序**(按价格从高到低)。 + - 可以**筛选**(只看张三的购买记录)。 +- **缺点**: - **容量有限**:当你有 100 万行数据时,Excel 打开都要几分钟,甚至直接卡死。 - - **难以协作**:你和店员不能同时修改同一个文件,否则会冲突。 - - **数据不安全**:不小心删了一行,可能很难找回来。 + - **难以协作**:你和店员不能同时修改同一个文件,否则会冲突(你得等同事保存关闭后才能编辑)。 + - **数据不安全**:不小心删了一行,Ctrl+Z 可能救不回来。如果硬盘坏了,数据可能永久丢失。 + - **数据冗余**:如果张三买了 100 本书,你得在每一行重复写张三的地址和电话。如果张三换了电话,你得修改 100 行。 -### 第三阶段:数据库 (Database) +这就是**单机文件型数据**的瓶颈:它只适合个人或小团队处理中等规模的数据。 -当你的书店变成了“亚马逊”,你需要处理亿级的订单,成千上万的用户同时访问。这时,你就需要**数据库**。 -数据库,本质上就是一个**“超级 Excel”**,但它专为**海量数据**、**高并发访问**和**数据安全**而设计。 +### 1.3 第三阶段:数据库 (Database) + +当你的书店变成了"亚马逊",你需要处理亿级的订单,成千上万的用户同时访问。这时,你就需要**数据库**。 + +**数据库,本质上就是一个"超级 Excel"**,但它专为**海量数据**、**高并发访问**和**数据安全**而设计。 + + + +**核心优势对比**: + +| 特性 | 记事本 | Excel | 数据库 | +|------|--------|-------|--------| +| 数据量 | 极少 | 中等 (百万级) | 海量 (亿级+) | +| 并发访问 | 单人 | 单人/顺序 | 千人/万人在线 | +| 数据安全 | 低 | 中 | 高 (备份、事务) | +| 数据关系 | 无 | 弱 | 强 (关系型) | +| 查询速度 | 极慢 | 中等 | 极快 (毫秒级) | --- ## 2. 数据库长什么样? -最流行的数据库类型是**关系型数据库 (Relational Database)**,比如 MySQL、PostgreSQL。它们的样子其实和 Excel 非常像。 +最流行的数据库类型是**关系型数据库 (Relational Database)**,比如 MySQL、PostgreSQL。它们的样子其实和 Excel 非常像,但概念更加严谨。 -### 核心概念 +### 2.1 核心概念:图书馆的比喻 -1. **表 (Table)**:就像 Excel 中的一个 Sheet。比如 `users`(用户表)、`orders`(订单表)。 -2. **列 (Column)**:数据的属性。比如 `name`(姓名)、`age`(年龄)。 -3. **行 (Row)**:一条具体的数据。比如“张三,25岁”就是一行。 -4. **主键 (Primary Key)**:每一行的唯一身份证号。通常是一个数字 ID(如 `user_id`),绝对不会重复。 +想象你是一个**图书馆管理员**。 -### 关系 (Relation) +| 数据库概念 | 图书馆类比 | 解释 | +|------------|------------|------| +| **数据库 (Database)** | 整座图书馆 | 存放所有数据的容器 | +| **表 (Table)** | 一个书架 | 存放同一类数据的集合,比如"用户书架"、"图书书架" | +| **列 (Column)** | 书脊上的标签 | 数据的属性,比如"书名"、"作者"、"出版日期" | +| **行 (Row)** | 书架上的每一本书 | 一条具体的数据记录,比如"《百年孤独》,马尔克斯,1967" | +| **主键 (Primary Key)** | 每本书的 ISBN 编号 | 唯一标识每一行的 ID,绝对不会重复 | + +**举个例子**: + +**用户表 (users)**: + +| user_id (主键) | name | age | email | +|----------------|------|-----|-------| +| 1 | 张三 | 25 | zhangsan@example.com | +| 2 | 李四 | 30 | lisi@example.com | +| 3 | 王五 | 28 | wangwu@example.com | + +- **表**:`users`(用户书架) +- **列**:`user_id`、`name`、`age`、`email`(书脊标签) +- **行**:每一行是一个用户(书架上的每本书) +- **主键**:`user_id`(ISBN 编号,1、2、3 永不重复) + +### 2.2 关系 (Relation):数据库的灵魂 这是数据库比 Excel 强大的关键。 -Excel 里,你可能在订单表里重复写“张三”的地址和电话。 -数据库里,我们把数据拆开: -- **用户表**:只存用户 ID、姓名、电话。 -- **订单表**:只存订单号、书名、**用户 ID**。 +**Excel 的问题:数据冗余** -当我们需要查看“某个订单是谁买的”时,数据库会通过 **用户 ID** 瞬间把两张表关联(Join)起来。这样做既节省空间,又保证了如果张三换了电话,只需要改用户表,所有订单显示的电话都会自动更新。 +在 Excel 中,如果你要记录订单,可能会这样写: + +| 订单号 | 书名 | 价格 | 购买者 | 购买者电话 | 购买者地址 | +|--------|------|------|--------|------------|------------| +| 001 | 百年孤独 | 59 | 张三 | 138xxxx | 北京 | +| 002 | 活着 | 39 | 张三 | 138xxxx | 北京 | +| 003 | 三体 | 99 | 张三 | 138xxxx | 北京 | + +**问题**: +- 张三买了 100 本书,你得重复写 100 次他的电话和地址。 +- 如果张三换了电话,你得修改 100 行,漏改一行就数据不一致了。 +- 数据量爆炸,浪费存储空间。 + +**数据库的解决方案:拆表 + 关联** + +数据库会把数据拆开,存到不同的表里,通过**关系**(外键)把它们连起来。 + +**用户表 (users)**: + +| user_id (主键) | name | phone | address | +|----------------|------|-------|---------| +| 101 | 张三 | 138xxxx | 北京 | +| 102 | 李四 | 139xxxx | 上海 | + +**订单表 (orders)**: + +| order_id (主键) | book_name | price | user_id (外键) | +|-----------------|-----------|-------|----------------| +| 001 | 百年孤独 | 59 | 101 | +| 002 | 活着 | 39 | 101 | +| 003 | 三体 | 99 | 101 | +| 004 | 百年孤独 | 59 | 102 | + +**关系解释**: +- `orders` 表里的 `user_id` 列,指向 `users` 表的 `user_id` 主键。 +- 当我们要查看"订单 001 是谁买的"时,数据库会去 `users` 表里查找 `user_id = 101` 的行,发现是"张三"。 +- 这种通过**外键 (Foreign Key)** 建立表与表之间联系的方式,就是**关系 (Relation)** 的含义。 + +**好处**: +- **节省空间**:张三的信息只存一次,不管他买多少本书。 +- **数据一致**:张三换电话,只需要改 `users` 表一行,所有订单关联的电话自动更新。 +- **灵活查询**:可以轻松回答复杂问题,比如"统计每个用户的总消费金额"。 + + --- -## 3. 如何和数据库说话?(SQL) +## 3. 如何和数据库说话?(SQL 入门) -你不能直接用鼠标去点数据库,你需要用一种特殊的语言:**SQL (Structured Query Language)**。 -它读起来很像英语: +你不能直接用鼠标去点数据库(虽然有图形化工具,但本质也是转换成命令),你需要用一种特殊的、标准化的语言来指挥数据库工作。 -- **查数据 (Read)**: +这种语言就是 **SQL (Structured Query Language,结构化查询语言)**。 - ```sql - SELECT name, price FROM books WHERE price < 50; - -- 翻译:从 books 表里,把价格小于 50 的书的名字和价格拿出来。 - ``` +好消息是,SQL 非常接近自然英语,读起来就像一句话。 -- **改数据 (Update)**: - ```sql - UPDATE users SET score = score + 10 WHERE id = 101; - -- 翻译:把 ID 是 101 的用户,积分加 10 分。 - ``` +### 3.1 SQL 的核心命令:CRUD - - - +大部分时候,你只需要掌握四种操作,江湖人称 **CRUD**: + +| 操作 | 英文 | SQL 关键字 | 类比 Excel | +|------|------|------------|------------| +| **C**reate | 创建/插入 | `INSERT` | 在末尾新增一行 | +| **R**ead | 读取/查询 | `SELECT` | 筛选、查找 | +| **U**pdate | 更新/修改 | `UPDATE` | 修改单元格内容 | +| **D**elete | 删除 | `DELETE` | 删除一行 | + +### 3.2 实战示例:书店管理系统 + +假设我们有以下两张表: + +**用户表 (users)**: + +| user_id | name | age | city | +|---------|------|-----|------| +| 1 | 张三 | 25 | 北京 | +| 2 | 李四 | 30 | 上海 | +| 3 | 王五 | 28 | 北京 | + +**图书表 (books)**: + +| book_id | title | price | stock | +|---------|-------|-------|-------| +| 101 | 百年孤独 | 59 | 100 | +| 102 | 活着 | 39 | 50 | +| 103 | 三体 | 99 | 200 | + +#### 查询数据 (Read) + +**示例 1**:查找所有年龄大于 25 岁的用户 + +```sql +SELECT name, age FROM users WHERE age > 25; +``` + +**逐词翻译**: +- `SELECT name, age`:选择 name 和 age 这两列 +- `FROM users`:从 users 这张表 +- `WHERE age > 25`:在 age 大于 25 的条件下 + +**返回结果**: + +| name | age | +|------|-----| +| 李四 | 30 | +| 王五 | 28 | + +**示例 2**:查找价格在 40 到 100 之间的图书 + +```sql +SELECT title, price FROM books WHERE price BETWEEN 40 AND 100; +``` + +#### 插入数据 (Create) + +**示例**:新增一个用户 + +```sql +INSERT INTO users (user_id, name, age, city) +VALUES (4, '赵六', 35, '广州'); +``` + +**逐词翻译**: +- `INSERT INTO users`:插入到 users 表 +- `(user_id, name, age, city)`:这几列 +- `VALUES (4, '赵六', 35, '广州')`:值分别是... + +#### 更新数据 (Update) + +**示例**:给所有北京的用户年龄加 1 岁 + +```sql +UPDATE users +SET age = age + 1 +WHERE city = '北京'; +``` + +**⚠️ 重要警告**:如果你忘记写 `WHERE city = '北京'`,这条命令会把**所有用户**的年龄都加 1!在生产环境中,这是一个极其危险的错误。 + +#### 删除数据 (Delete) + +**示例**:删除用户 ID 为 4 的用户 + +```sql +DELETE FROM users WHERE user_id = 4; +``` + +**⚠️ 重要警告**:和 UPDATE 一样,如果忘记写 `WHERE`,你会删除整张表的所有数据!这在生产环境中是灾难性的。 + +### 3.3 多表查询:JOIN 的力量 + +还记得我们讲过的"关系"吗?SQL 最强大的地方在于可以一次性查询多张关联的表。 + +**示例场景**:查询"张三购买过的所有图书" + +假设我们还有一张订单表 (orders): + +| order_id | user_id | book_id | quantity | +|----------|---------|---------|----------| +| 1001 | 1 | 101 | 1 | +| 1002 | 1 | 102 | 2 | +| 1003 | 2 | 101 | 1 | + +**SQL 查询**: + +```sql +SELECT u.name, b.title, o.quantity +FROM orders o +JOIN users u ON o.user_id = u.user_id +JOIN books b ON o.book_id = b.book_id +WHERE u.name = '张三'; +``` + +**返回结果**: + +| name | title | quantity | +|------|-------|----------| +| 张三 | 百年孤独 | 1 | +| 张三 | 活着 | 2 | + +通过 `JOIN`,我们把三张表的数据关联在了一起,得到了完整的答案。 + + --- -## 4. 为什么数据库这么快?(索引原理) +## 4. 为什么数据库这么快?(索引原理揭秘) + +这是数据库最神奇的地方,也是面试中最爱问的问题。 + +如果你在 Excel 里找"所有姓张的人",Excel 可能需要从第一行扫到最后一行。这就是**全表扫描 (Full Table Scan)**——数据越多,速度越慢。 -这是数据库最神奇的地方。 -如果你在 Excel 里找“所有姓张的人”,Excel 可能需要从第一行扫到最后一行。 但在数据库里,即使有 10 亿行数据,查找也只需要几毫秒。 **秘诀就是:索引 (Index)。** -### 4.1 直观演示:全表扫描 vs 索引查找 +### 4.1 直观理解:字典的启示 -让我们通过一个交互演示来看看区别。 -假设我们要查找 `ID = 55` 的数据: +想象你要在一本没有目录、没有页码的 1000 页书里找一个词。你该怎么办? -- **全表扫描 (Full Table Scan)**: 就像在图书馆找一本没编号的书,必须从头到尾一本一本看。数据越多,越慢。 -- **索引查找 (Index Search)**: 就像查字典,或者用二分法。因为数据已经排好序(建立了索引),我们可以迅速跳过无关数据,直奔目标。 +**只能一页一页翻**——这就是全表扫描。 - - - +但现在,你手里有一本**字典**。字典有一个按字母排序的**索引**。 -::: tip 试一试 -在上面的演示中,点击 **“索引查找”**。你会发现查找次数极少。这就是为什么数据库能瞬间响应你的请求。 -::: +你要找"数据库"这个词: +1. 翻到"数"字开头的区域(快速定位)。 +2. 在"数"字区域内,按第二个字"据"的顺序找。 +3. 很快,你就定位到了"数据库"这个词所在的页码。 -### 4.2 底层数据结构:B+ 树 +这就是**索引查找**——不需要翻完整本书,只需要查索引,直接跳转到目标位置。 -真实数据库使用的索引结构叫 **B+ 树**。 -它像一棵倒过来的树,非常“矮胖”。 +### 4.2 全表扫描 vs 索引查找 -- **根节点**指引大方向。 -- **中间节点**指引小范围。 -- **叶子节点**存储真正的数据。 +让我们通过一个具体的例子来感受两者的差异。 -通常,一棵存储了 1 亿条数据的 B+ 树,高度只有 3 到 4 层。这意味着,数据库只需要读取 3 到 4 次磁盘,就能找到这 1 亿条数据中的任意一条! +假设我们有一张用户表,里面有 1000 万条用户记录。 + +**场景:查找 ID = 5,555,555 的用户** + +| 方式 | 过程 | 需要检查的行数 | 耗时(估算) | +|------|------|----------------|--------------| +| **全表扫描** | 从第 1 行开始,一行一行看,直到找到 ID = 5,555,555 | 平均 500 万行 | 数秒 ~ 数十秒 | +| **索引查找** | 查索引树,直接跳到目标位置 | 约 3-4 次比较 | 数毫秒 | + +**速度差距**:数千倍甚至数万倍! + + + +### 4.3 底层数据结构:B+ 树 + +真实的索引并不是简单的"字母排序列表",而是一种精心设计的数据结构,叫做 **B+ 树 (B+ Tree)**。 + +#### 为什么是"树"? + +想象一棵倒过来的树: +- **根节点 (Root)**:在最顶层,像树干。 +- **中间节点 (Internal Nodes)**:在树干和树叶之间,像树枝。 +- **叶子节点 (Leaf Nodes)**:在最底层,像树叶,存储着真正的数据或数据地址。 + +#### B+ 树的特点:矮胖 + +B+ 树有一个非常聪明的设计:**它非常"矮胖"**。 + +- **矮**:从根到叶子,通常只有 3-4 层。 +- **胖**:每个节点可以存储很多个键值(比如几百个)。 + +**为什么要"矮胖"?** + +因为数据库的数据最终是存储在**磁盘**(硬盘或 SSD)上的。 + +每次从磁盘读取数据都需要**一次 I/O 操作**(磁盘寻道),这个操作相对于内存计算来说,非常**慢**(慢几千倍)。 + +所以,B+ 树的设计目标是:**尽量减少磁盘 I/O 次数**。 + +- **矮**:只有 3-4 层,意味着最多只需要 3-4 次磁盘读取就能找到数据。 +- **胖**:每一层能容纳大量数据,保证树不会变高。 + +**实际例子**: + +假设一棵 B+ 树的每个节点可以存储 1000 个键值: + +- 根节点:1000 个键值 → 指向 1000 个中间节点 +- 中间节点:每个存 1000 个键值 → 指向 1000 个叶子节点 +- 叶子节点:每个存 1000 条真实数据 + +**总数据量** = 1000 × 1000 × 1000 = **10 亿条数据** + +**树的高度** = **3 层** + +这意味着,在一个存储了 10 亿条数据的 B+ 树索引中,找到任意一条数据,**只需要 3 次磁盘 I/O**! + +这就是数据库查询飞快的秘密。 + + --- -## 5. 总结 +## 5. 总结与学习路线 -1. **数据库**是处理海量数据的“超级 Excel”。 -2. 我们用 **SQL** 语言来指挥数据库工作。 -3. **索引**(底层是 B+ 树)是数据库查询速度快如闪电的秘密武器。 +现在你已经打通了从"Excel 表格"到"B+ 树索引"的任督二脉: -现在,当你听到后端工程师说“我在查数据库”时,你的脑海里应该浮现出:他在写一句 SQL 指令,通过 B+ 树索引,在毫秒间从亿万数据中抓取到了用户想要的那一行。 +1. **数据库的本质**:处理海量数据的"超级 Excel",专为高并发、高安全、高性能而设计。 +2. **数据的组织**:通过**表**、**列**、**行**、**主键**组织数据,通过**关系**(外键)连接多张表,消除冗余。 +3. **SQL 语言**:使用 `SELECT`、`INSERT`、`UPDATE`、`DELETE` 等命令与数据库对话,通过 `JOIN` 实现多表查询。 +4. **索引原理**:使用 **B+ 树**作为底层数据结构,通过"矮胖"的树形结构,将磁盘 I/O 次数降至最低,实现毫秒级查询。 + +**下一步建议**: + +- 如果你想动手实践,可以尝试安装 **MySQL** 或 **PostgreSQL**,亲手创建几张表,插入数据,体验 SQL 的强大。 +- 如果你对后端开发感兴趣,可以学习如何使用 **ORM**(如 SQLAlchemy、Prisma)在代码中操作数据库,而不需要手写 SQL。 +- 如果你想深入底层,可以研究 **InnoDB 存储引擎**的原理,了解事务、锁、MVCC 等高级概念。 + +--- + +## 6. 名词速查表 (Glossary) + +| 名词 | 英文 | 解释 | +|------|------|------| +| **数据库** | Database | 存储和管理数据的系统,专为海量数据、高并发访问而设计 | +| **关系型数据库** | Relational Database | 基于关系模型组织数据的数据库,如 MySQL、PostgreSQL | +| **表** | Table | 数据库中存储同一类数据的集合,由行和列组成 | +| **列** | Column | 表的垂直维度,代表数据的一个属性(如"姓名"、"年龄") | +| **行** | Row | 表的水平维度,代表一条具体的数据记录 | +| **主键** | Primary Key | 唯一标识表中每一行的列,值不能重复 | +| **外键** | Foreign Key | 建立表与表之间关联的列,指向另一张表的主键 | +| **关系** | Relation | 表与表之间通过主键和外键建立的关联 | +| **SQL** | Structured Query Language | 结构化查询语言,用于与数据库通信的标准语言 | +| **索引** | Index | 加速数据查询的数据结构,类似于书的目录 | +| **B+ 树** | B+ Tree | 数据库索引常用的数据结构,具有矮胖、有序、支持范围查询的特点 | +| **全表扫描** | Full Table Scan | 不通过索引,逐行扫描整张表的查询方式,效率低 | +| **磁盘 I/O** | Disk I/O | 从磁盘读取或写入数据的操作,相对于内存操作非常慢 | diff --git a/docs/zh-cn/appendix/frontend-evolution.md b/docs/zh-cn/appendix/frontend-evolution.md index 1130434..b7e9e5b 100644 --- a/docs/zh-cn/appendix/frontend-evolution.md +++ b/docs/zh-cn/appendix/frontend-evolution.md @@ -1,43 +1,52 @@ -# 前端开发入门:从静态网页到现代工程化 (Interactive Intro) +# 前端开发入门:从"贴海报"到"搭乐高" (Interactive Intro) > 💡 **学习指南**:本章节无需编程基础,通过交互式演示带你回顾前端开发的 20 年变迁。我们将从最基础的 HTML 讲起,一直到现代的 Vue/React 组件化开发。 先把几个最常见的新名词说清楚(后面会反复出现): -- **HTML**:网页的“骨架”,负责内容和结构(标题、段落、图片、按钮)。 -- **CSS**:网页的“皮肤”,负责样式(颜色、大小、布局、动画)。 -- **JavaScript**:网页的“肌肉”,负责交互与逻辑(点击、输入、请求数据)。 +- **HTML**:网页的"骨架",负责内容和结构(标题、段落、图片、按钮)。 +- **CSS**:网页的"皮肤",负责样式(颜色、大小、布局、动画)。 +- **JavaScript**:网页的"肌肉",负责交互与逻辑(点击、输入、请求数据)。 - **框架(Framework)**:一套成熟的开发方式和工具,让你更高效地做复杂页面(比如 Vue/React)。 ## 0. 引言:网页为什么越来越难做? -最早的网页,只是**电子海报**。 -现在的网页,是**桌面级应用** (如 VS Code, Figma)。 +最早的网页,只是**电子海报**——就像你在街上看到的纸质海报,只能看、不能互动。 + +现在的网页,是**桌面级应用** (如 VS Code, Figma)——可以编辑文档、画图、玩游戏,甚至剪辑视频。 为了支撑这种转变,前端技术经历了一场从 "手工作坊" 到 "工业化生产" 的革命。 -核心变化只有一点:**页面越来越复杂,我们需要更高效的“组织方式”和“开发方式”。** +### 一个生活的比喻 + +想象你要盖房子: + +- **2000 年代(静态网页)**:就像**贴海报**。你画好一张图,贴到墙上就完事了,不能改动。 +- **2010 年代(jQuery 时代)**:就像**请工人手动装修**。你需要亲自告诉工人:"把这块墙涂成蓝色"、"把那扇窗户打开"。工人很多、指令很杂,容易出错。 +- **2020 年代(Vue/React 时代)**:就像**用乐高积木搭房子**。你先设计好"房子长什么样"(设计图),然后乐高积木(组件)会自动按设计图组装好,不需要你一块一块手动拼。 + +**核心变化只有一点:页面越来越复杂,我们需要更高效的"组织方式"和"开发方式"。** ### 0.1 前端 vs 大前端(你到底在学什么?) -很多人说“我学前端”,但不同公司口径不一样。 +很多人说"我学前端",但不同公司口径不一样。 -- **前端(Web Frontend)**:主要指“在浏览器里跑的那部分”。典型产物是网站和 H5 页面。 -- **大前端(Big Frontend)**:泛指“所有用户界面相关的开发”。不只 Web,还包括小程序、App、桌面应用等。 +- **前端(Web Frontend)**:主要指"在浏览器里跑的那部分"。典型产物是网站和 H5 页面。 +- **大前端(Big Frontend)**:泛指"所有用户界面相关的开发"。不只 Web,还包括小程序、App、桌面应用等。 这里的几个新词(后面也会用到): - **端**:平台/运行环境的意思,比如 Web 端、移动端、桌面端。 - **H5**:手机网页(本质也是 Web),通常用来做活动页/落地页,传播快、迭代快。 -- **WebView**:App 里用来显示网页的“内置浏览器控件”。很多 App 的部分页面其实就是 WebView。 +- **WebView**:App 里用来显示网页的"内置浏览器控件"。很多 App 的部分页面其实就是 WebView。 - **跨端**:用一套代码同时做多个端(比如同时做 iOS + Android)。 - **原生**:直接用平台官方语言/能力开发(iOS 的 Swift、Android 的 Kotlin)。 -**关键点**:大前端不是一个“新岗位名字”,而是一种范围:把体验交付到更多平台。 +**关键点**:大前端不是一个"新岗位名字",而是一种范围:把体验交付到更多平台。 --- @@ -48,7 +57,7 @@ 这里的几个新词: -- **静态网页**:页面内容基本固定,打开就是一份 HTML 文件(不像现在很多页面是“数据驱动、可交互”的)。 +- **静态网页**:页面内容基本固定,打开就是一份 HTML 文件(不像现在很多页面是"数据驱动、可交互"的)。 - **UI**:User Interface,用户界面。也就是你看到的按钮、颜色、布局。 ### 1.1 为什么会慢? @@ -56,12 +65,18 @@ 网页上的每一张小图,浏览器都要发一次**网络请求**。 请求越多,加载越慢。 - +想象一下你点外卖: +- 如果你一次性下单 10 道菜,餐厅可以一起做完送过来。 +- 但如果你分 10 次下单,每次只点 1 道菜,骑手要跑 10 趟! + +早期的网页就像"分 10 次下单",每张图片都要单独"下单"(发请求)。 + + 补充一个常见技巧:**雪碧图 (Sprite)**。 把很多小图合成一张大图,这样请求数会变少(但制作和维护更麻烦)。 -**关键点**:早期网页慢,常见原因之一是“请求太多”。(图片、脚本、样式都会产生请求) +**关键点**:早期网页慢,常见原因之一是"请求太多"。(图片、脚本、样式都会产生请求) --- @@ -71,15 +86,21 @@ 这就需要**响应式布局**:同一套 HTML/CSS,自动根据屏幕宽度变换布局。 这里用到了**媒体查询 (Media Query)**: -它是 CSS 里的“条件判断”,比如“如果屏幕小于 640px,就用 1 列布局”。 +它是 CSS 里的"条件判断",比如"如果屏幕小于 640px,就用 1 列布局"。 - +想象一下你在不同房间看同一张照片: +- 在**大客厅**(电脑屏幕),照片可以摆大一些,旁边还能放其他装饰品。 +- 在**小卧室**(手机屏幕),照片需要缩小,其他装饰品要收起来,否则会挤不下。 -**关键点**:响应式让网页“会变形”,不再只适配电脑。 +**响应式布局**就是"智能相框",它会自动根据房间大小调整展示方式。 + + + +**关键点**:响应式让网页"会变形",不再只适配电脑。 --- -## 3. 第三阶段:从操作 DOM 到数据驱动 (jQuery -> Vue/React) +## 3. 第三阶段:从"手动搬砖"到"数据驱动" (jQuery -> Vue/React) 网页开始像 App 一样复杂之后,最麻烦的事变成了:**同一份数据变化,要改很多地方**。 @@ -91,40 +112,59 @@ 下面这个可视化演示,专门用来解释:**什么是 jQuery(以及它为什么会累)**。 - +想象一下你在餐厅当服务员: +- **jQuery 时代**:客人点了一份牛排,你要亲自跑厨房告诉厨师、跑吧台拿饮料、跑收银台记账。客人加菜,你又要重新跑一遍。**你成了"跑腿王",累得半死**。 +- **Vue/React 时代**:你只需在单子上写好"客人要什么"(数据),厨房、吧台、收银台会自动看单子做事。客人加菜,你只需改单子,其他地方**自动更新**。**你成了"指挥家",轻松优雅**。 -### 3.1 jQuery 的思路:我来“亲手改页面” + -在 jQuery 时代(2005+),你通常会写很多“命令”去改页面: -“找到某个元素,把文字改掉;找到某个按钮,把它禁用……” +### 3.1 jQuery 的思路:我来"亲手改页面" -它的问题不是“写不出来”,而是:**只要漏改一个地方,页面就会出现前后不一致的 bug**。 +在 jQuery 时代(2005+),你通常会写很多"命令"去改页面: +"找到某个元素,把文字改掉;找到某个按钮,把它禁用……" + +它的问题不是"写不出来",而是:**只要漏改一个地方,页面就会出现前后不一致的 bug**。 页面越大,这种 bug 越多。 +就像你手动修一栋大楼: +- 你要记住"每个房间的长什么样"。 +- 你要亲自"一间一间地修"。 +- 如果你忘了修某间房,或者修错了,整栋楼就不一致了。 + 这里用到的新词(先解释清楚): -- **jQuery**:早期非常流行的 JavaScript 工具库,特点是“很方便地找元素、改元素”。 -- **DOM**:浏览器里保存页面结构的一棵“树”(按钮、文字、图片都在这棵树上)。 -- **ID**:HTML 元素的唯一名字(类似“身份证号”),方便你定位某一个元素。 -- **div**:HTML 里最常用的“盒子”标签,用来做布局和容器。 +- **jQuery**:早期非常流行的 JavaScript 工具库,特点是"很方便地找元素、改元素"。 +- **DOM**:浏览器里保存页面结构的一棵"树"(按钮、文字、图片都在这棵树上)。 +- **ID**:HTML 元素的唯一名字(类似"身份证号"),方便你定位某一个元素。 +- **div**:HTML 里最常用的"盒子"标签,用来做布局和容器。 -### 3.2 Vue/React 的思路:我只改“数据”,页面自己变 +### 3.2 Vue/React 的思路:我只改"数据",页面自己变 后来大家意识到:与其到处改页面,不如只维护一份**状态 (State)**。 状态变了,页面自动刷新到正确的样子。 -这就是“数据驱动 UI”的核心: +这就是"数据驱动 UI"的核心: -- **State(状态)**:页面的“数据”,比如购物车数量、登录状态、输入框内容。 +- **State(状态)**:页面的"数据",比如购物车数量、登录状态、输入框内容。 - **数据驱动**:你只改 State,不直接改 DOM;框架负责把界面同步到正确状态。 -- **Vue/React**:现代前端框架,主要解决“状态变化 -> 界面自动更新”。 +- **Vue/React**:现代前端框架,主要解决"状态变化 -> 界面自动更新"。 -### 3.3 什么是“命令式”和“声明式”? +想象一下你在玩"模拟城市"游戏: +- 你不需要"手动画每一栋房子"(手动改 DOM)。 +- 你只需要调整"城市数据"(比如人口、资金、政策),游戏画面**自动更新**。 + +**这就是数据驱动的魅力:你只关心"数据长什么样",不关心"页面怎么画"。** + +### 3.3 什么是"命令式"和"声明式"? 这就好比你要画一幅画: -- **命令式**:你告诉画家“拿起笔,蘸红颜料,在坐标(10,10)画一个圈”。 -- **声明式**:你直接给画家一张照片,“给我画成这样”。 +- **命令式**:你告诉画家"拿起笔,蘸红颜料,在坐标(10,10)画一个圈"。 +- **声明式**:你直接给画家一张照片,"给我画成这样"。 + +想象一下你点披萨: +- **命令式**:你亲自和面、撒料、烤披萨、切披萨。你要记住每一步,很累。 +- **声明式**:你只需说"我要一个芝士披萨",披萨店自动做好。你只需"声明"你要什么,不关心"怎么做"。 ### 3.4 交互演示:两种写法的区别 @@ -135,12 +175,12 @@ -**关键点**:从 jQuery 到 Vue/React,变化的核心不是“语法”,而是**思维方式**:从“我去改页面”变成“我只改数据”。 +**关键点**:从 jQuery 到 Vue/React,变化的核心不是"语法",而是**思维方式**:从"我去改页面"变成"我只改数据"。 ### 3.5 Vue 和 React 怎么选?先把差异理解清楚 -很多初学者会纠结:“我到底学 Vue 还是 React?” -先别急着站队。你先把它们的“共同点”和“差异点”理解清楚,就不会被带节奏了。 +很多初学者会纠结:"我到底学 Vue 还是 React?" +先别急着站队。你先把它们的"共同点"和"差异点"理解清楚,就不会被带节奏了。 **共同点(它们都在解决同一件事)**: @@ -150,40 +190,50 @@ **差异点(你会在写代码时真实感受到)**: - **写 UI 的方式**:Vue 常用 Template;React 常用 JSX -- **状态变化时怎么更新**:Vue 更偏“依赖追踪”;React 更偏“重新渲染组件函数” +- **状态变化时怎么更新**:Vue 更偏"依赖追踪";React 更偏"重新渲染组件函数" 这里的几个新词(像课件一样解释清楚): - **Template**:Vue 常见写法,用类似 HTML 的语法来写界面。 -- **JSX**:React 常见写法,用“像写 JS 一样”的方式写界面结构。 +- **JSX**:React 常见写法,用"像写 JS 一样"的方式写界面结构。 - **Hook**:React 的一套函数式能力(比如 `useState`),用来保存状态、处理副作用。 - **SFC**:Single File Component,Vue 常见的单文件组件(一个 `.vue` 文件里写模板/逻辑/样式)。 -**关键点**:别死记名词。你只要记住一句话:它们都能做同样的产品,只是“写法和心智模型”不一样。 +**关键点**:别死记名词。你只要记住一句话:它们都能做同样的产品,只是"写法和心智模型"不一样。 --- ## 4. 第四阶段:组件化(像搭积木一样写页面) -解决了“怎么更新页面”的问题,接下来是“怎么组织代码”。 +解决了"怎么更新页面"的问题,接下来是"怎么组织代码"。 以前一个页面可能是一个超大的 HTML 文件,改一个按钮可能牵连全局。 -### 4.1 “积木”是什么? +### 4.1 "积木"是什么? 现代前端把页面拆成了**组件**。 一个按钮、一个导航栏、一个商品卡片,都是独立的积木。 +想象一下你在搭乐高: +- 你不需要"从头开始雕刻每一块积木"(从头写 HTML/CSS)。 +- 你只需要"按说明书把积木拼在一起"(把组件组合起来)。 +- 每个积木都是**独立的**,你可以在不同的套装里**重复使用**。 + ### 4.2 为什么组件能复用? 定义好一个"商品卡片"组件后,你可以由它生成 100 个实例。每个实例都有自己独立的状态(比如点赞状态),互不干扰。 +想象一下你有一个"万能开关"组件: +- 你可以把这个开关放在客厅、卧室、厨房。 +- 每个开关都是**独立的**:你按客厅的开关,不会影响到卧室的灯。 +- 但它们都是**同一个组件**,你只需要设计一次"开关长什么样",就可以到处使用。 + **新名词解释**: -- **组件 (Component)**:页面里的“积木块”,可以单独复用。 +- **组件 (Component)**:页面里的"积木块",可以单独复用。 - **封装**:组件内部的状态不影响别人。 - **复用**:同一个组件可以用很多次。 @@ -193,46 +243,58 @@ ## 5. 第五阶段:页面切换体验(MPA vs SPA) -用户不再想要“每点一次就刷新整页”的体验。 +用户不再想要"每点一次就刷新整页"的体验。 于是出现了**单页应用 (SPA)**:整个网站只加载一次,之后只是切换内容。 与之对应的是**多页应用 (MPA)**:每点一次都会重新加载整个页面。 这里的一个新词:**路由 (Routing)**。 -简单理解:就是“从 A 页面切到 B 页面”的规则和过程。 +简单理解:就是"从 A 页面切到 B 页面"的规则和过程。 再补两个新词(非常重要): - **前端路由**:页面切换主要由浏览器里的 JavaScript 控制(常见于 SPA)。 -- **后端路由**:页面切换主要由服务器决定“返回哪个页面”(常见于 MPA)。 +- **后端路由**:页面切换主要由服务器决定"返回哪个页面"(常见于 MPA)。 + +想象一下你在看一本书: +- **MPA(多页应用)**:就像**翻书**。每翻一页,你都要把旧书合上、去书架上拿一本新书。慢,而且你之前在书上做的笔记(比如折页)都会消失。 +- **SPA(单页应用)**:就像**在同一页纸上换内容**。你只需要擦掉旧内容、写上新内容,**纸还是那张纸**。快,而且你做的笔记一直都在。 ### 5.1 MPA 是什么?(多页应用) -MPA 的直觉很像“翻书”: +MPA 的直觉很像"翻书": -- 点“商品页” -> 浏览器向服务器要一个新的页面(新的 HTML) +- 点"商品页" -> 浏览器向服务器要一个新的页面(新的 HTML) - 旧页面被替换掉 -> 原来的输入、滚动位置、临时数据往往会消失 +想象一下你在逛商场: +- 每进一家店(点一个链接),你都要**走出商场、重新排队进门**(整页刷新)。 +- 你在上一家店试过的衣服(输入的内容)、拿过的购物车,**全部清空**。 + **优点(为什么很多网站仍在用)**: -- 结构简单:服务器负责“出页面”,浏览器负责“展示” +- 结构简单:服务器负责"出页面",浏览器负责"展示" - SEO 友好:搜索引擎更容易直接看到页面内容 - 首屏容易快:因为服务器直接给了 HTML **缺点**: -- 体验偏“跳”:整页刷新会白一下、加载一下 +- 体验偏"跳":整页刷新会白一下、加载一下 - 复杂交互会变难:页面之间共享状态不方便 ### 5.2 SPA 是什么?(单页应用) -SPA 更像“同一本书里换章节”: +SPA 更像"同一本书里换章节": -- 第一次打开:加载一个“外壳页面”(HTML + CSS + JS) +- 第一次打开:加载一个"外壳页面"(HTML + CSS + JS) - 之后切换页面:通常只换内容区域,整页不刷新 +想象一下你在用手机的 App: +- 打开微信(第一次加载),之后你刷朋友圈、看聊天、进公众号,**页面不会重新加载**,只是内容在切换。 +- 你输入了一半的消息、看到的滚动位置,**切换后再回来还在**。 + **优点**: - 体验丝滑:页面切换快 @@ -247,26 +309,39 @@ SPA 更像“同一本书里换章节”: 下面这个小实验更直观:输入一段文字,然后切换页面再回来,看看有没有被清空。 +想象一下你正在填写一张申请表: +- **MPA(翻书模式)**:你填到一半,去另一页查资料,回来发现**表格被清空了**,要重新填。 +- **SPA(同一页模式)**:你填到一半,去另一页查资料,回来发现**表格还在**,继续填就行。 + -**关键点**:从“整页刷新”到“局部更新”,带来的不仅是速度,更是“状态能不能保留”的体验差异。 +**关键点**:从"整页刷新"到"局部更新",带来的不仅是速度,更是"状态能不能保留"的体验差异。 --- -## 6. 第六阶段:工程化(打包、依赖、规范) +## 6. 第六阶段:工程化(从"手工作坊"到"现代化工厂") 前端项目越来越大,不能再靠手动引入脚本文件。 -于是有了**打包工具**(Webpack/Vite):把多个文件和依赖打成一个或多个“优化后的包”。 +于是有了**打包工具**(Webpack/Vite):把多个文件和依赖打成一个或多个"优化后的包"。 + +想象一下你在整理行李: +- **以前(手动引入)**:你要出门,把衣服、裤子、袜子一件一件拿在手里,容易丢、容易乱。 +- **现在(工程化打包)**:你把所有东西**打包进一个行李箱**,拉着就走,整齐又方便。 **依赖**就是你用到的第三方库,比如图表库、编辑器。 +想象一下你在做饭: +- 你要做蛋糕,需要面粉、鸡蛋、糖(**依赖**)。 +- 你不需要自己种小麦、养鸡(**自己写所有代码**),而是去超市买现成的(**使用第三方库**)。 +- **工程化**就是"超市购物清单",帮你自动把所有需要的食材买齐、分类放好。 + 这里的几个新词: -- **工程化**:用工具和规范把项目“像工程一样”管理(目录结构、构建、发布、代码规范等)。 +- **工程化**:用工具和规范把项目"像工程一样"管理(目录结构、构建、发布、代码规范等)。 - **Bundle(包)**:打包后的产物文件。 -- **Tree Shaking**:把“没用到的代码”从包里摇掉,体积更小。 +- **Tree Shaking**:把"没用到的代码"从包里摇掉,体积更小。 **关键点**:工程化让多人协作的大项目变得可控。 @@ -276,16 +351,18 @@ SPA 更像“同一本书里换章节”: 为了更快的首屏、更好的搜索排名,渲染方式也在进化。 -- **CSR**:客户端渲染。浏览器拿到 JS 之后再画页面。 -- **SSR**:服务端渲染。服务器先把 HTML 画好再发给浏览器。 -- **SSG**:静态生成。提前把页面生成好,像静态文件一样直接发。 +想象一下你在餐厅吃饭,有三种服务模式: -这里的几个新词: +- **CSR(客户端渲染)**:服务员给你一个**半成品食材包**(JS 文件),你自己在桌上做饭。等菜时间长(要下载 JS),但做完后你可以随时"热更新"(交互流畅)。 + +- **SSR(服务端渲染)**:服务员在**厨房做好菜**(服务器渲染 HTML),直接端给你。上菜快(首屏快),但你想加辣(交互),还得等厨师(服务器响应)。 + +- **SSG(静态生成)**:餐厅**提前把所有菜做好**,放在保温柜里。你来点餐,立刻就能吃(最快)。但菜单是固定的(静态内容),不能临时加菜。 - **首屏**:用户打开网站时,最先看到的那一屏内容。 - **SEO**:Search Engine Optimization,搜索引擎优化。让页面更容易被搜索到。 -- **TTFB**:Time To First Byte,浏览器收到“第一口数据”的时间(越小越快)。 -- **TTI**:Time To Interactive,页面变得“可以点、可以用”的时间。 +- **TTFB**:Time To First Byte,浏览器收到"第一口数据"的时间(越小越快)。 +- **TTI**:Time To Interactive,页面变得"可以点、可以用"的时间。 @@ -313,4 +390,35 @@ SPA 更像“同一本书里换章节”: - 先把 HTML/CSS 写熟(布局、响应式) - 再学 JavaScript 的基础(变量、函数、事件) -- 最后上手一个框架(Vue/React),理解“状态驱动 UI” +- 最后上手一个框架(Vue/React),理解"状态驱动 UI" + +--- + +## 9. 名词速查表 (Glossary) + +| 名词 | 全称 | 解释 | +| :----------------- | :----------------------------------------- | :--------------------------------------------------------------------------------------------- | +| **HTML** | HyperText Markup Language | 超文本标记语言。网页的骨架,定义内容和结构。 | +| **CSS** | Cascading Style Sheets | 层叠样式表。网页的皮肤,定义颜色、布局、动画。 | +| **JS** | JavaScript | 网页的肌肉,负责交互和逻辑。 | +| **DOM** | Document Object Model | 文档对象模型。浏览器内部表示页面结构的树形对象。 | +| **jQuery** | - | 早期流行的 JS 库,简化了 DOM 操作。 | +| **Vue/React** | - | 现代前端框架,采用数据驱动和组件化开发。 | +| **State** | - | 状态。组件或应用的数据,状态变化驱动 UI 更新。 | +| **组件** | Component | 可复用的 UI 单元,如按钮、卡片、导航栏。 | +| **MPA** | Multi-Page Application | 多页应用。每次跳转都重新加载整个页面。 | +| **SPA** | Single-Page Application | 单页应用。只加载一次,后续切换不刷新页面。 | +| **路由** | Routing | 管理页面之间切换的规则和过程。 | +| **CSR** | Client-Side Rendering | 客户端渲染。浏览器下载 JS 后执行生成页面。 | +| **SSR** | Server-Side Rendering | 服务端渲染。服务器生成 HTML 后发给浏览器。 | +| **SSG** | Static Site Generation | 静态站点生成。构建时预渲染页面为静态 HTML。 | +| **Bundle** | - | 包。打包工具将多个文件合并后的产物。 | +| **Tree Shaking** | - | 摇树优化。自动移除未使用的代码,减小包体积。 | +| **H5** | HTML5 | 通常指手机网页或基于 HTML5 的移动页面。 | +| **WebView** | - | 内嵌网页视图。App 中用于显示网页内容的组件。 | +| **跨端** | Cross-Platform | 一套代码运行在多个平台(iOS、Android、Web 等)。 | +| **原生** | Native | 使用平台官方语言和 API 开发的应用。 | +| **MVVM** | Model-View-ViewModel | 一种架构模式,实现数据(Model)和视图(View)的自动同步。 | +| **SEO** | Search Engine Optimization | 搜索引擎优化,提高网页在搜索结果中的排名。 | +| **TTFB** | Time To First Byte | 首字节时间,从请求到收到第一个字节数据的耗时。 | +| **TTI** | Time To Interactive | 可交互时间,页面变为完全可交互状态所需的时间。 | diff --git a/docs/zh-cn/appendix/frontend-performance.md b/docs/zh-cn/appendix/frontend-performance.md index 928ebf1..f19a7ea 100644 --- a/docs/zh-cn/appendix/frontend-performance.md +++ b/docs/zh-cn/appendix/frontend-performance.md @@ -1,367 +1,133 @@ -# 前端性能优化:从加载到渲染 (Interactive Guide to Frontend Performance) +# 前端性能优化 (Frontend Performance) -> 💡 **学习指南**:本章节通过交互式演示和实战案例,帮你建立前端性能优化的完整知识体系。性能优化不是玄学,而是一套可测量、可优化的工程方法论。 +> 💡 **学习指南**:本章节无需深入的算法背景,通过交互式演示带你掌握前端性能优化的核心逻辑。我们将从最直观的页面加载讲起,一直到浏览器底层的渲染机制和缓存策略。 -## 0. 引言:为什么性能很重要? - -**性能就是用户体验。** - -一个页面加载多慢,用户就会流失?研究数据表明: - -- **3 秒法则**:页面加载超过 3 秒,53% 的用户会离开 -- **0.1 秒延迟**:亚马逊发现页面延迟 0.1 秒,销售额下降 1% -- **移动端更敏感**:移动用户对性能的容忍度更低 - -性能优化不只是"让页面变快",而是: - -- **提升转化率**:更快的加载 = 更多的订单/注册 -- **改善 SEO**:搜索引擎优先索引加载快的页面 -- **降低成本**:优化的资源 = 更少的带宽和服务器成本 - -### 0.1 核心性能指标 (Core Web Vitals) - -Google 定义了三个核心性能指标,所有网页都应该达标: + -**关键点**:性能优化要关注真实用户感受到的体验,而不只是技术指标。 +## 0. 引言:从 "能用" 到 "好用" -### 0.2 性能预算 (Performance Budget) +如果把访问网页比作去餐厅**吃饭**,那么: -**性能预算**是为项目设定的性能限制,比如: +- **加载 (Loading)** 就是食材(HTML/CSS/JS/图片)从仓库(服务器)运送到厨房(浏览器)的过程。 +- **渲染 (Rendering)** 就是厨师(浏览器引擎)把食材加工成美味菜肴(页面)的过程。 +- **交互 (Interaction)** 就是服务员响应你的需求(点击、滚动)。 -- JavaScript 文件不超过 200KB -- 首屏加载时间不超过 2 秒 -- 总资源大小不超过 1MB +**前端性能优化**的本质,就是为了让这三个过程**更快、更顺畅**。 -**为什么需要预算?** +它的核心任务只有一个:**最大限度地减少用户的等待时间。** -- 防止项目膨胀:随着功能增加,性能很容易恶化 -- 团队协作规范:新功能上线前必须通过性能检查 -- 持续优化动力:每次迭代都有改进目标 +为了实现这个目标,我们需要解决三个核心挑战: + +1. **传输**:怎么把“食材”运得更快?(压缩、CDN、懒加载) +2. **渲染**:怎么让“厨师”做得更快?(关键渲染路径、重排重绘) +3. **记忆**:怎么避免重复劳动?(缓存策略) + +本教程将带你一步步拆解这些优化技巧。 --- -## 1. 性能分析工具 +## 1. 第一步:传输 (Loading) -在优化之前,先学会**测量**。只有找到瓶颈,才能精准优化。 +在厨师开始做饭之前,首先得有食材。如果运送食材的卡车堵在路上了,厨房里再厉害的大厨也得干瞪眼。 -### 1.1 Chrome DevTools +### 1.1 为什么网速快了,网页还是很慢? -浏览器自带的开发者工具,功能强大: +你可能会疑惑:现在的 5G 和光纤这么快,为什么有些网页打开还是很慢? -**Performance 面板**: +原因通常有两个: +1. **东西太多**:一张高清大图可能就有 5MB,相当于下载一本书。 +2. **路太堵**:浏览器同时下载的资源数量是有限的(通常 6 个),就像只有 6 辆小卡车在运货,多出来的得排队。 -- 录制页面运行过程 -- 查看 FPS(帧率)、CPU 使用率 -- 分析 JavaScript 执行时间 -- 找出长任务(Long Tasks,超过 50ms 的 JS 任务) +### 1.2 解决方案:瘦身与偷懒 -**Lighthouse 面板**: +为了解决这个问题,我们主要用两招:**压缩(瘦身)**和**懒加载(偷懒)**。 -- 一键生成性能报告 -- 包含性能、可访问性、最佳实践、SEO 等评分 -- 给出具体优化建议 +#### 瘦身:图片与代码压缩 -**Network 面板**: +图片通常是网页里最大的“胖子”。 +现代的图片格式(如 WebP, AVIF)就像是采用了高科技压缩技术的压缩包,在画质几乎不变的情况下,体积能减小 30%-70%。 -- 查看所有资源加载时间 -- 分析瀑布图(Waterfall) -- 找出加载慢的资源 + -### 1.2 WebPageTest +#### 偷懒:懒加载 (Lazy Loading) -在线性能测试工具([webpagetest.org](https://webpagetest.org)): +“偷懒”在这里是个褒义词。 +如果用户只在看第一屏的内容,为什么要把底下第十屏的图片也下载下来呢? -- 多地点测试(全球各地) -- 真实浏览器测试(Chrome、Firefox、Safari) -- 丰富的测试报告和视频 -- 可以模拟不同网速(3G、4G) - -### 1.3 PageSpeed Insights - -Google 官方工具([pagespeed.web.dev](https://pagespeed.web.dev)): - -- 基于 Core Web Vitals 评分 -- 区分移动端和桌面端 -- 提供字段数据(真实用户数据)和实验室数据 - -**关键点**:用数据驱动优化,而不是凭感觉。 - ---- - -## 2. 加载优化 (Loading Optimization) - -加载优化是性能优化的第一步:让资源更快地到达浏览器。 - -### 2.1 资源压缩 - -**文本压缩**:使用 Gzip 或 Brotli 压缩 HTML、CSS、JavaScript - -``` -未压缩: 500 KB -Gzip: 150 KB (压缩率 70%) -Brotli: 120 KB (压缩率 76%) -``` - -**开启方法**(Nginx 配置): - -```nginx -gzip on; -gzip_types text/css application/javascript; -gzip_min_length 1000; - -# Brotli 需要额外模块 -brotli on; -brotli_types text/plain text/css application/javascript; -``` - -**图片压缩**:使用工具(如 ImageOptim、TinyPNG)压缩图片 +**懒加载**的策略是:**只加载用户看得到的内容**。当用户滚动页面,图片快要出现时,再去下载它。 -### 2.2 代码分割 (Code Splitting) - -**问题**:传统打包方式把所有代码打包成一个文件,首屏要下载大量无用代码 - -**解决方案**:按路由或功能分割代码,用户只下载当前页面需要的代码 - -**示例**(Vite + Vue Router): - -```javascript -// 懒加载路由组件 -const Home = () => import('./views/Home.vue') -const About = () => import('./views/About.vue') - -const routes = [ - { path: '/', component: Home }, - { path: '/about', component: About } -] -``` - -**效果**: - -- 首页只加载 100KB(而不是 500KB) -- 用户访问 /about 时才加载额外代码 -- 整体首屏时间减少 60% - -### 2.3 Tree Shaking - -**Tree Shaking**:移除未使用的代码 - -**示例**: - -```javascript -// 整个 lodash 库:70 KB -import _ from 'lodash' - -// 只用某个函数:2 KB -import debounce from 'lodash/debounce' -``` - -**Tree Shaking 原理**: - -- ES Module 的 `import/export` 是静态结构 -- 打包工具(Webpack、Vite)可以分析哪些代码被使用 -- 未使用的代码在打包时被删除 - -### 2.4 预加载 (Preloading) - -**预加载关键资源**:告诉浏览器提前加载重要资源 - -```html - - - - - - - - - - - -``` - -**优先级**: - -- `preload`:立即下载(但可能抢占关键资源) -- `prefetch`:空闲时下载(适合下一页资源) -- `preconnect`:提前建立 TCP 连接 - -### 2.5 CDN 加速 - -**CDN(Content Delivery Network)**:内容分发网络 - -**工作原理**: - -- 把静态资源部署到全球各地的服务器 -- 用户从最近的服务器下载资源 -- 减少网络传输延迟 - -**使用建议**: - -- 图片、字体、CSS、JS 等静态资源放 CDN -- 使用公共 CDN(如 unpkg、jsDelivr)加载第三方库 -- 大型网站使用自建 CDN 或商业 CDN(如 Cloudflare) - -**效果**: - -- 国内用户加载速度提升 50%-80% -- 海外用户体验显著改善 +**关键点**:永远不要让用户下载他们不需要(或者暂时不需要)的资源。 --- -## 3. 渲染优化 (Rendering Optimization) +## 2. 核心难题:渲染 (Rendering) -资源加载完成后,浏览器要"画"出页面。渲染优化让这个过程更快。 +食材运到了,接下来压力给到了厨师(浏览器)。 -### 3.1 关键渲染路径 (Critical Rendering Path) +### 2.1 浏览器的“单线程”困境 -**浏览器渲染流程**: +浏览器里的大厨(主线程)非常忙,他不仅要负责**画页面**(布局、绘制),还要负责**响应用户**(点击事件、JS 逻辑)。 +最糟糕的是,他只有**一个人**(单线程)。 -1. **解析 HTML** → 构建 DOM 树 -2. **解析 CSS** → 构建 CSSOM 树 -3. **合并 DOM + CSSOM** → 构建渲染树 -4. **布局(Layout)**:计算元素位置和大小 -5. **绘制(Paint)**:绘制像素 -6. **合成(Composite)**:合成图层 +如果你让他在切菜(运行复杂的 JS 计算)的时候,顾客(用户)想点菜(点击按钮),他是没法理你的。这就导致了**卡顿**。 -**关键点**:每一步都可能成为性能瓶颈。 +### 2.2 关键渲染路径 (Critical Rendering Path) -### 3.2 DOM 优化 +为了让用户尽快看到东西,浏览器制定了一套标准的工作流程,我们叫它**关键渲染路径**: -**减少 DOM 操作**:DOM 操作很慢,批量处理 +1. **HTML -> DOM**:把菜谱读懂,列出食材清单。 +2. **CSS -> CSSOM**:搞清楚每种食材怎么处理(颜色、大小)。 +3. **Render Tree**:把清单和处理方法结合,决定最后上桌的菜。 +4. **Layout (排版)**:决定每个菜摆在盘子的哪个位置。 +5. **Paint (绘制)**:最后淋上酱汁,上色。 -**示例**(低效): + -```javascript -// 每次循环都触发重排 -for (let i = 0; i < 1000; i++) { - document.body.innerHTML += `
${i}
` -} -``` +### 2.3 避坑指南:重排 (Reflow) 与重绘 (Repaint) -**优化**: +在这个流程中,最累人的步骤是 **Layout (排版)**。 -```javascript -// 使用文档片段,只触发一次重排 -const fragment = document.createDocumentFragment() -for (let i = 0; i < 1000; i++) { - const div = document.createElement('div') - div.textContent = i - fragment.appendChild(div) -} -document.body.appendChild(fragment) -``` - -**使用虚拟 DOM**: - -- Vue、React 使用虚拟 DOM 减少真实 DOM 操作 -- 批量更新,减少重排次数 - -### 3.3 CSS 优化 - -**减少 CSS 大小**: - -- 移除未使用的 CSS(使用 PurgeCSS) -- 压缩 CSS(移除空格、注释) - -**优化 CSS 选择器**: - -```css -/* 慢:从右向左匹配 */ -.container div ul li a { -} - -/* 快:使用类选择器 */ -.link { -} -``` - -**关键 CSS 内联**: - -- 把首屏需要的 CSS 内联到 HTML -- 减少渲染阻塞 - -```html - -``` - -### 3.4 重排与重绘 +- **重排 (Reflow)**:如果你改变了元素的大小或位置,浏览器通过重新计算所有元素的位置。这就像因为桌子移了一下,整个餐厅的椅子都要重新摆一遍。**非常消耗性能!** +- **重绘 (Repaint)**:如果你只是改变了颜色,浏览器只需要重新上色。这就像给桌布换个颜色,简单多了。 -**触发重排的操作**: - -- 改变元素大小、位置 -- 添加/删除 DOM 元素 -- 改变字体大小 -- 改变窗口大小 - -**触发重绘的操作**: - -- 改变颜色 -- 改变背景 -- 改变边框样式 - -**优化建议**: - -- 批量修改样式(使用 class) -- 使用 `transform` 和 `opacity`(触发合成) -- 避免逐帧修改样式(使用 requestAnimationFrame) - -### 3.5 合成层 (Compositing) - -**使用 `will-change` 提示浏览器**: - -```css -.animated-element { - will-change: transform, opacity; -} -``` - -**使用 GPU 加速**: - -```css -.gpu-accelerated { - transform: translateZ(0); - /* 或 */ - transform: translate3d(0, 0, 0); -} -``` - -**注意**:不要滥用合成层,过多会消耗内存。 - -### 3.6 虚拟列表 (Virtual List) - -当需要展示成千上万条数据时(如长列表、聊天记录),如果直接渲染所有 DOM 节点,会导致: - -- **DOM 节点过多**:占用大量内存 -- **渲染缓慢**:样式计算和布局耗时增加 -- **滚动卡顿**:浏览器无法维持 60fps - -**解决方案**:只渲染**可视区域**内的元素,加上少量缓冲区。 - - - -**核心原理**: - -1. 计算可视区域能容纳多少个元素。 -2. 监听滚动事件,根据 `scrollTop` 计算当前应该渲染数据的 `startIndex` 和 `endIndex`。 -3. 使用 `padding-top` 或 `transform` 将渲染的内容定位到正确位置。 +**优化原则**: +- 尽量避免**重排**(比如不要频繁修改 `width`, `top`)。 +- 尽量使用只会触发**合成**(Composite)的属性(如 `transform`, `opacity`),这相当于让 GPU(帮厨)来干活,不占用主厨的时间。 --- -## 4. JavaScript 优化 +## 3. 进阶:处理海量数据 -JavaScript 是页面的"肌肉",优化它让页面更流畅。 +如果你的网页需要展示 10,000 条聊天记录,或者 5,000 个商品列表,该怎么办? + +### 3.1 为什么不能直接 `v-for`? + +如果直接在页面上生成 10,000 个 `
`,浏览器的内存会瞬间爆炸,渲染树会变得巨大无比,每动一下都会卡死。 +这就好比餐厅里只有 10 张桌子,你却非要一次性接待 10,000 个客人,结果就是谁也吃不上饭。 + +### 3.2 解决方案:虚拟列表 (Virtual Scrolling) + +聪明的工程师想出了**虚拟列表**。 +它的核心思想是:**欺骗眼睛**。 + +既然屏幕只能显示 10 条数据,那我就只渲染这 10 条(加上前后一点缓冲)。当用户滚动时,我快速地把移出屏幕的 DOM 销毁,把新进入屏幕的数据填进去。 +用户感觉他在滚一个无限长的列表,但实际上浏览器里永远只有几十个 DOM 节点。 + + + +**关键点**:DOM 节点是昂贵的,能省则省。 + +--- + +## 4. 脚本执行优化 (Script Execution) + +JavaScript 的执行是阻塞主线程的,优化 JS 执行效率对于保持页面流畅至关重要。 ### 4.1 代码压缩 (Minification) @@ -470,87 +236,7 @@ async function processLargeArray(items) { } ``` ---- - -## 5. 图片优化 - -图片通常是网页最大的资源,优化图片能显著提升性能。 - -### 5.1 格式选择 - - - -**格式对比**: - -| 格式 | 大小 | 质量 | 浏览器支持 | 适用场景 | -| ---- | ---- | ---- | ---------- | -------------- | -| JPEG | 小 | 好 | 所有 | 照片 | -| PNG | 大 | 最好 | 所有 | 透明图片、图标 | -| WebP | 很小 | 好 | 现代浏览器 | 大部分场景 | -| AVIF | 最小 | 很好 | 最新浏览器 | 追求极致性能 | - -**建议**: - -- 优先使用 WebP -- 提供降级方案(JPEG/PNG) - -```html - - - 描述 - -``` - -### 5.2 响应式图片 - -**根据屏幕尺寸加载不同尺寸的图片**: - -```html -响应式图片 -``` - -**解释**: - -- `srcset`:定义不同尺寸的图片 -- `sizes`:告诉浏览器图片在不同屏幕上的显示尺寸 -- 浏览器自动选择最合适的图片 - -### 5.3 懒加载 - - - -**图片懒加载**:只有当图片进入视口时才加载 - -**方法 1:使用 `loading` 属性**: - -```html -懒加载图片 -``` - -**方法 2:使用 Intersection Observer**: - -```javascript -const observer = new IntersectionObserver((entries) => { - entries.forEach((entry) => { - if (entry.isIntersecting) { - const img = entry.target - img.src = img.dataset.src - observer.unobserve(img) - } - }) -}) - -document.querySelectorAll('img[data-src]').forEach((img) => { - observer.observe(img) -}) -``` - -### 5.4 压缩与裁剪 +### 5.3 压缩与裁剪 **使用工具压缩图片**: @@ -782,74 +468,39 @@ export default defineConfig({ ## 9. 实战案例 -### 9.1 案例 1:优化一个慢页面 +### 9.1 案例 1:新闻列表页优化 -**问题**:一个电商商品页加载需要 8 秒 +**问题**:首屏加载慢,滚动卡顿 -**诊断**: +**优化**: -- 图片总和:3 MB -- JavaScript:1.2 MB -- CSS:400 KB -- 45 个资源请求 +1. **图片**:WebP + 懒加载 +2. **列表**:虚拟列表(只渲染可见的 10 项) +3. **数据**:分页加载 -**优化措施**: +**结果**:LCP 2.5s -> 0.8s -1. 压缩图片 → 减少 60%(1.2 MB) -2. 使用 WebP → 再减少 30%(800 KB) -3. 图片懒加载 → 首屏只加载 3 张图 -4. 代码分割 → 首屏 JS 减少到 300 KB -5. 启用 Gzip → CSS 减少到 150 KB +### 9.2 案例 2:数据可视化大屏 -**结果**: +**问题**:渲染大量节点卡死 -- 首屏时间:8 秒 → 1.8 秒(减少 77%) -- Lighthouse 性能评分:35 → 92 +**优化**: -### 9.2 案例 2:大型应用的性能优化 +1. **渲染**:Canvas 代替 DOM +2. **计算**:Web Worker 处理数据 -**问题**:单页应用(SPA)首次加载慢 +**结果**:FPS 10 -> 60 -**优化策略**: +### 9.3 案例 3:移动端活动页 -1. **路由懒加载**:每个路由单独打包 -2. **组件懒加载**:非首屏组件延迟加载 -3. **虚拟列表**:长列表只渲染可见部分 -4. **预加载下一页**:用户可能访问的页面预加载 -5. **SSR(服务端渲染)**:首屏由服务器渲染 +**问题**:白屏时间长 -**技术选型**: +**优化**: -- 使用 **Vite**(快速构建) -- 使用 **Vue 3**(更好的性能) -- 使用 **Pinia**(轻量状态管理) -- 使用 **Vant**(按需引入的 UI 组件库) +1. **资源**:预加载 (Preload) 关键图 +2. **体验**:骨架屏 (Skeleton) -**结果**: - -- 首屏加载时间:4.5 秒 → 1.2 秒 -- Time to Interactive:6 秒 → 1.8 秒 - -### 9.3 案例 3:移动端性能优化 - -**移动端特殊挑战**: - -- CPU 性能弱 -- 网络慢 -- 内存有限 - -**优化措施**: - -1. **减少动画**:使用 CSS 动画代替 JS 动画 -2. **触摸优化**:避免 `click` 延迟,使用 `touchstart` -3. **减少重排**:使用 `transform` 代替 `top/left` -4. **减少资源**:移动端加载更小的图片 -5. **PWA**:支持离线访问,提供类原生体验 - -**结果**: - -- 移动端评分:45 → 95 -- 用户留存率提升:+30% +**结果**:白屏减少 60% --- @@ -863,69 +514,46 @@ export default defineConfig({ - ✅ 使用 CDN 加速静态资源 - ✅ 实施代码分割和懒加载 - ✅ 压缩和优化图片 -- ✅ 使用 WebP/AVIF 格式 **渲染优化**: - ✅ 减少重排和重绘 -- ✅ 使用 CSS 动画(transform、opacity) - ✅ 优化关键渲染路径 -- ✅ 内联关键 CSS +- ✅ 使用 CSS 动画代替 JS 动画 -**JavaScript 优化**: +**执行优化**: -- ✅ 压缩和 Tree Shaking -- ✅ 避免长任务(时间切片) -- ✅ 使用 Web Workers -- ✅ 防抖和节流 +- ✅ 使用 Web Workers 处理重计算 +- ✅ 避免长任务(Long Tasks) +- ✅ 合理使用防抖和节流 **缓存优化**: -- ✅ 配置 HTTP 缓存 -- ✅ 使用 Service Worker -- ✅ 合理使用 LocalStorage +- ✅ 配置 HTTP 强缓存和协商缓存 +- ✅ 考虑使用 Service Worker -**监控优化**: +### 10.2 持续学习 -- ✅ 设置性能预算 -- ✅ 使用 RUM 和合成监控 -- ✅ 定期审计性能 - -### 10.2 性能优化原则 - -1. **测量优先**:先测量,再优化 -2. **抓大放小**:先优化最大的瓶颈 -3. **用户体验第一**:关注真实用户感受 -4. **持续改进**:性能优化是持续的过程 -5. **团队协作**:让整个团队都关注性能 - -### 10.3 常见陷阱 - -- ❌ 过早优化:没有测量就开始优化 -- ❌ 过度优化:为了优化而优化,得不偿失 -- ❌ 只关注分数:Lighthouse 分数高不代表用户体验好 -- ❌ 忽视移动端:移动端性能更重要 -- ❌ 一次性优化:性能需要持续监控和改进 - -### 10.4 学习资源 - -**工具**: - -- [Lighthouse](https://developers.google.com/web/tools/lighthouse) -- [WebPageTest](https://www.webpagetest.org) -- [PageSpeed Insights](https://pagespeed.web.dev) - -**文档**: - -- [Web.dev Performance](https://web.dev/performance/) -- [MDN Performance](https://developer.mozilla.org/en-US/docs/Web/Performance) - -**书籍**: - -- 《高性能网站建设指南》 -- 《高性能网站建设进阶指南》 -- 《Web 性能权威指南》 +前端性能优化是一个不断发展的领域,新的标准(如 INP)和新的工具(如 Vite, Turbopack)层出不穷。保持好奇心,多看 Performance 面板,是你最好的老师。 --- -**记住**:性能优化不是炫技,而是为用户创造价值。快的体验就是好的体验。 +## 11. 名词速查表 (Glossary) + +| 名词 | 全称 | 解释 | +| :--- | :--- | :--- | +| **FP / FCP** | First Paint / First Contentful Paint | **首屏时间**。用户看到页面第一个像素/第一块内容的时间。 | +| **LCP** | Largest Contentful Paint | **最大内容绘制**。页面主要内容加载完成的时间(衡量加载速度的核心指标)。 | +| **INP** | Interaction to Next Paint | **交互到下一次绘制**。衡量页面响应速度的新指标(替代 FID),关注点击后的反馈延迟。 | +| **CLS** | Cumulative Layout Shift | **累积布局偏移**。页面加载时元素乱跳的程度(衡量视觉稳定性)。 | +| **TTFB** | Time to First Byte | **首字节时间**。从发出请求到接收到服务器第一个字节的时间(衡量后端响应速度)。 | +| **TBT** | Total Blocking Time | **总阻塞时间**。主线程被长任务阻塞的总时间(衡量页面交互流畅度)。 | +| **Reflow** | Reflow (Layout) | **重排**。浏览器重新计算元素位置和大小的过程。成本高,应避免。 | +| **Repaint** | Repaint | **重绘**。浏览器重新绘制元素外观(如颜色)的过程。成本中等。 | +| **CDN** | Content Delivery Network | **内容分发网络**。把文件存在离用户最近的服务器上,加速下载。 | +| **SSR** | Server-Side Rendering | **服务端渲染**。在服务器端生成 HTML,加快首屏显示,利于 SEO。 | +| **CSR** | Client-Side Rendering | **客户端渲染**。在浏览器端通过 JS 生成 HTML,交互体验好,但首屏慢。 | +| **SSG** | Static Site Generation | **静态站点生成**。构建时生成静态 HTML,访问速度极快。 | +| **Tree Shaking** | Tree Shaking | **摇树优化**。构建时移除未使用的代码,减小包体积。 | +| **Code Splitting** | Code Splitting | **代码分割**。将代码拆分成小块,按需加载。 | +| **Preload / Prefetch** | Preload / Prefetch | **预加载/预获取**。提前告知浏览器加载关键资源或未来可能用到的资源。 | diff --git a/docs/zh-cn/appendix/llm-intro.md b/docs/zh-cn/appendix/llm-intro.md index ca4d079..6a5df75 100644 --- a/docs/zh-cn/appendix/llm-intro.md +++ b/docs/zh-cn/appendix/llm-intro.md @@ -39,11 +39,41 @@ 它就像是一个翻译官,负责将人类的文字翻译成机器能读懂的数字序列。 现代 LLM (如 GPT-4) 通常使用 **Subword Tokenization (子词分词)** 技术(如 BPE 算法)。 -它的聪明之处在于: +它的聪明之处在于:**常用词保持完整,生僻词拆分**。 -- **常用词**(如 "apple")保持完整,作为一个 Token。 -- **生僻词**(如 "applepie")拆分成常见片段("apple" + "pie")。 - 这样既能覆盖所有词汇,又不会让词表变得无限大。 +以下是一个真实的 BPE 分词示例(基于 GPT-4 Tokenizer): + +**Input**: `"The quick brown fox jumps over the lazy dog. \n今天天气真不错!"` + +**Token List**: + +```text +index=791, string='The' +index=4062, string=' quick' +index=14198, string=' brown' +index=39935, string=' fox' +index=83368, string=' jumps' <-- 如果被拆分,可能会是 ' jump' + 's' +index=927, string=' over' +index=279, string=' the' +index=16053, string=' lazy' +index=3290, string=' dog' +index=13, string='.' +index=198, string='\n' <-- 换行符 +index=33838, string='今天' <-- 常用词直接合并 +index=54580, string='天气' +index=20265, string='真' +index=57672, string='不错' +index=171, string='!' +``` + +> **关于生僻字的处理**: +> 如果遇到词表中不存在的生僻字(假设“今”字很生僻),模型会回退到 **Byte 级别** 进行编码。 +> 1. Raw Input: `今` +> 2. Bytes: `\xE4 \xBB \x8A` +> 3. BPE 查找: 先找 `\xE4\xBB\x8A` -> 没找到 -> 拆分为 `\xE4\xBB` (ID=1001) + `\x8A` (ID=2002)。 +> 4. 最终 Token: `[1001, 2002]`。 +> +> 这种机制保证了**无论输入什么字符,模型都能处理,永远不会出现 OOV (Out Of Vocabulary) 问题**。 @@ -340,11 +370,14 @@ Thinking Model 就是学会了这种**慢思考 (System 2)** 能力的模型。 - **代表**:GPT-3, Llama-2。 - **MoE (混合专家模型)**: - - **比喻**:一个**专家团队**。有一个前台(Router)负责分发问题。 - - 问代码 -> 分给程序员专家。 - - 问数学 -> 分给数学家专家。 - - 问文学 -> 分给文学家专家。 - - **特点**:虽然总人数多(参数量大),但回答一个问题时只有几个人干活(激活参数少)。**又博学,又快**。 + - **比喻**:一个**流水线上的专家团**(每处理一个字就换一次人)。 + - **核心机制 (Token-Level Routing)**: + MoE 的精髓在于**原生 Token 级路由**。它**绝不是**按“任务类型”分工(比如把数学题全给数学专家),而是**按“当前生成的字”实时分工**。 + - 当模型生成“`def`”时,路由给**代码专家**。 + - 当模型生成“`love`”时,路由给**文学专家**。 + - 当模型生成“`3.14`”时,路由给**数学专家**。 + 这意味着,哪怕在同一句话里,不同的字也往往由不同的专家处理。 + - **特点**:虽然总人数多(参数量大),但处理每个字时只有几个人干活(激活参数少)。**又博学,又快**。 - **代表**:GPT-4, DeepSeek-V3, Mixtral。 @@ -377,6 +410,16 @@ Thinking Model 就是学会了这种**慢思考 (System 2)** 能力的模型。 +### 7.7 架构大比拼:RNN vs Transformer vs RWKV + +| 架构 | 核心机制 | 复杂度 (长度 N) | 并行训练 | 推理速度 | 遗忘问题 | 代表模型 | +| :--- | :--- | :--- | :--- | :--- | :--- | :--- | +| **RNN** | 串行递归 | $O(N)$ (低) | ❌ 不可 | 慢 (串行) | 严重 (长距离遗忘) | LSTM, GRU | +| **Transformer** | 全局注意力 | $O(N^2)$ (极高) | ✅ 可 | 中 (KV Cache) | 无 (但受限于窗口) | GPT-4, Llama | +| **RWKV / Linear** | 线性注意力 | $O(N)$ (低) | ✅ 可 | 快 (恒定显存) | 轻微 (有压缩损耗) | RWKV, MiniMax | + +> **RWKV / Linear Attention** 试图结合前两者的优点:像 Transformer 一样并行训练,像 RNN 一样高效推理。 + --- ## 8. 总结与学习路线 diff --git a/docs/zh-cn/stage-0/intro.md b/docs/zh-cn/stage-0/index.md similarity index 100% rename from docs/zh-cn/stage-0/intro.md rename to docs/zh-cn/stage-0/index.md diff --git a/docs/zh-tw/stage-0/intro.md b/docs/zh-tw/stage-0/index.md similarity index 100% rename from docs/zh-tw/stage-0/intro.md rename to docs/zh-tw/stage-0/index.md