diff --git a/docs/.vitepress/theme/components/appendix/javascript-intro/DataTypeDemo.vue b/docs/.vitepress/theme/components/appendix/javascript-intro/DataTypeDemo.vue index 1388018..3c680c7 100644 --- a/docs/.vitepress/theme/components/appendix/javascript-intro/DataTypeDemo.vue +++ b/docs/.vitepress/theme/components/appendix/javascript-intro/DataTypeDemo.vue @@ -76,15 +76,19 @@
obj2.x = 20
// obj1.x = 20 (变了!)
-
-
-
obj1 →
-
{x: 20}
-
-
指向同一位置
-
-
obj2 →
+
+
+
+
obj1
+
+
+
+
obj2
+
+
+
指向同一位置
+
{x: 20}
@@ -469,6 +473,40 @@ const convertType = () => { text-align: center; } +/* 修复引用类型可视化 */ +.ref-visual { + flex-direction: column; + gap: 0.75rem; +} + +.ref-boxes { + display: flex; + gap: 2rem; + justify-content: center; +} + +.ref-var-box { + display: flex; + align-items: center; + gap: 0.5rem; +} + +.ref-var-name { + font-weight: 600; + color: var(--vp-c-text-1); + font-family: monospace; +} + +.ref-var-arrow { + color: var(--vp-c-brand); + font-weight: bold; +} + +.down-arrow { + color: var(--vp-c-brand); + font-size: 0.8rem; +} + .ref-types-list { display: flex; flex-direction: column; diff --git a/docs/.vitepress/theme/components/appendix/javascript-intro/ReferenceDemo.vue b/docs/.vitepress/theme/components/appendix/javascript-intro/ReferenceDemo.vue index 840ff0f..f76e4d0 100644 --- a/docs/.vitepress/theme/components/appendix/javascript-intro/ReferenceDemo.vue +++ b/docs/.vitepress/theme/components/appendix/javascript-intro/ReferenceDemo.vue @@ -1,140 +1,106 @@ @@ -143,235 +109,221 @@ console.log(obj1.age) // {{ obj1Data.age }} .reference-demo { border: 1px solid var(--vp-c-border); border-radius: 12px; - padding: 24px; - margin: 24px 0; + padding: 20px; + margin: 16px 0; background: var(--vp-c-bg); } -h3 { - margin: 0 0 20px 0; - font-size: 18px; +.demo-title { + font-size: 16px; font-weight: 600; color: var(--vp-c-text-1); + margin-bottom: 16px; } -h4 { - margin: 0 0 16px 0; - font-size: 14px; - font-weight: 600; - color: var(--vp-c-text-2); -} - -.demo-container { +.compare-grid { display: grid; grid-template-columns: 1fr 1fr; - gap: 24px; - margin-bottom: 24px; + gap: 16px; + margin-bottom: 16px; } @media (max-width: 768px) { - .demo-container { + .compare-grid { grid-template-columns: 1fr; } } -.demo-section { - border: 1px dashed var(--vp-c-border); - border-radius: 8px; - padding: 20px; +.compare-box { + border: 1px solid var(--vp-c-border); + border-radius: 10px; + padding: 16px; background: var(--vp-c-bg-soft); } -.visualization { - display: flex; - align-items: center; - justify-content: center; - gap: 20px; - margin-bottom: 16px; - min-height: 120px; +.box-header { + font-size: 13px; + font-weight: 600; + padding: 6px 10px; + border-radius: 6px; + margin-bottom: 12px; + color: white; } -.box { - width: 80px; - height: 80px; - border: 2px solid var(--vp-c-border); - border-radius: 8px; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; +.box-header.blue { + background: #3b82f6; +} + +.box-header.orange { + background: #f59e0b; +} + +.memory-area { background: var(--vp-c-bg); - transition: all 0.3s ease; + border-radius: 8px; + padding: 12px; + margin-bottom: 12px; } -.box.active { - border-color: var(--vp-c-brand-1); - box-shadow: 0 0 0 3px rgba(62, 175, 124, 0.1); -} - -.box-label { - font-size: 12px; - font-weight: 600; - color: var(--vp-c-text-2); - margin-bottom: 4px; -} - -.box-value { - font-size: 24px; - font-weight: 600; - font-family: 'Courier New', monospace; - color: var(--vp-c-brand-1); -} - -.arrow { +.vars-row { display: flex; - align-items: center; - gap: 4px; -} - -.arrow-line { - width: 40px; - height: 2px; - background: var(--vp-c-brand-1); -} - -.arrow-head { - font-size: 24px; - color: var(--vp-c-brand-1); -} - -.data-area { - text-align: center; -} - -.data-label { - font-size: 12px; - font-weight: 600; - color: var(--vp-c-text-2); + gap: 12px; + justify-content: center; margin-bottom: 8px; } -.data-content { - border: 2px solid var(--vp-c-brand-1); - border-radius: 8px; - padding: 12px; - background: var(--vp-c-bg); - font-family: 'Courier New', monospace; - font-size: 12px; - color: var(--vp-c-text-1); -} - -.pointers { +.var-item { display: flex; - gap: 24px; - margin-top: 12px; + flex-direction: column; + align-items: center; + padding: 8px 16px; + border: 2px solid var(--vp-c-border); + border-radius: 6px; + opacity: 0.4; + transition: all 0.3s; } -.pointer { - text-align: center; +.var-item.active { + opacity: 1; + border-color: #3b82f6; } -.pointer-label { - font-size: 12px; - font-weight: 600; +.var-item.changed { + border-color: #10b981; + background: #ecfdf5; +} + +.var-label { + font-size: 11px; color: var(--vp-c-text-2); - margin-bottom: 4px; } -.arrow-line-down { - width: 2px; - height: 30px; - background: var(--vp-c-brand-1); - margin: 0 auto; +.var-val, .var-addr { + font-size: 18px; + font-weight: 600; + font-family: monospace; + color: #3b82f6; } -.message { +.var-addr { + color: #8b5cf6; + font-size: 14px; +} + +.copy-arrow { + text-align: center; + font-size: 11px; + color: var(--vp-c-text-2); + padding: 4px; +} + +.data-box { + border: 2px solid #8b5cf6; + border-radius: 6px; + padding: 8px; + text-align: center; + background: #f3e8ff; + margin-top: 8px; +} + +.data-box.changed { + border-color: #ef4444; + background: #fee2e2; +} + +.data-box.copied { + border-color: #10b981; + background: #d1fae5; +} + +.data-addr { + font-size: 10px; + color: #6b7280; +} + +.data-content { + font-family: monospace; + font-size: 13px; + color: #374151; +} + +.result-text { text-align: center; padding: 8px; border-radius: 6px; + font-size: 12px; margin-bottom: 12px; - font-size: 13px; - font-weight: 500; - background: var(--vp-c-bg-soft); - color: var(--vp-c-text-1); } -.controls { +.result-text.info { + background: #f3f4f6; + color: #4b5563; +} + +.result-text.success { + background: #d1fae5; + color: #065f46; +} + +.result-text.warning { + background: #fee2e2; + color: #991b1b; +} + +.btn-group { display: flex; - gap: 8px; + gap: 6px; flex-wrap: wrap; + justify-content: center; } -button { +.btn-group button { padding: 6px 12px; border: none; - border-radius: 6px; - font-size: 13px; - font-weight: 500; + border-radius: 4px; + font-size: 12px; cursor: pointer; - transition: all 0.2s ease; -} - -button:active { - transform: scale(0.95); -} - -.btn-primary { - background: var(--vp-c-brand-1); + background: #3b82f6; color: white; } -.btn-primary:hover { - background: var(--vp-c-brand-2); +.btn-group button:disabled { + opacity: 0.4; + cursor: not-allowed; } -.btn-secondary { - background: var(--vp-c-bg-soft); +.btn-group button.reset { + background: var(--vp-c-bg-alt); color: var(--vp-c-text-1); + border: 1px solid var(--vp-c-border); } -.btn-secondary:hover { - background: var(--vp-c-bg-soft-hover); +.code-compare { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 12px; } -.btn-danger { - background: #f56565; - color: white; -} - -.btn-danger:hover { - background: #e53e3e; -} - -.btn-success { - background: #38a169; - color: white; -} - -.btn-success:hover { - background: #2f855a; -} - -.code-display { +.code-col { background: #1e1e1e; border-radius: 8px; - padding: 16px; - overflow-x: auto; + padding: 12px; } -.code-display h4 { - color: #d4d4d4; - margin-bottom: 12px; +.code-title { + color: #9ca3af; + font-size: 11px; + margin-bottom: 8px; } -.code-display pre { +.code-col pre { margin: 0; } -.code-display code { - font-family: 'Courier New', monospace; - font-size: 13px; - line-height: 1.6; +.code-col code { + font-family: monospace; + font-size: 11px; + line-height: 1.5; color: #d4d4d4; } diff --git a/docs/.vitepress/theme/components/appendix/javascript-intro/ScopeDemo.vue b/docs/.vitepress/theme/components/appendix/javascript-intro/ScopeDemo.vue index 35fdb6f..9fe05da 100644 --- a/docs/.vitepress/theme/components/appendix/javascript-intro/ScopeDemo.vue +++ b/docs/.vitepress/theme/components/appendix/javascript-intro/ScopeDemo.vue @@ -1,7 +1,7 @@ @@ -92,42 +87,59 @@ const isStudent = true .variable-box-demo { border: 1px solid var(--vp-c-border); border-radius: 12px; - padding: 24px; - margin: 24px 0; + padding: 20px; + margin: 16px 0; background: var(--vp-c-bg); } -h3 { - margin: 0 0 16px 0; - font-size: 18px; +.demo-header { + margin-bottom: 16px; +} + +.title { + font-size: 16px; font-weight: 600; color: var(--vp-c-text-1); } -.boxes-container { +.boxes-row { display: flex; gap: 16px; justify-content: center; - margin-bottom: 20px; + margin-bottom: 16px; flex-wrap: wrap; } -.variable-box { - position: relative; - width: 120px; - height: 120px; +.var-box { + width: 100px; + height: 100px; border: 2px solid var(--vp-c-border); - border-radius: 12px; + border-radius: 10px; display: flex; flex-direction: column; align-items: center; justify-content: center; + position: relative; + background: var(--vp-c-bg); transition: all 0.3s ease; } -.variable-box.success { - border-color: #3eaf7c; - animation: pulse 0.5s ease; +.var-box.error { + border-color: #ef4444; + background: #fef2f2; + animation: shake 0.4s ease; +} + +.var-box.success { + border-color: #10b981; + background: #ecfdf5; + animation: pulse 0.4s ease; +} + +@keyframes shake { + 0%, 100% { transform: translateX(0); } + 25% { transform: translateX(-4px); } + 75% { transform: translateX(4px); } } @keyframes pulse { @@ -135,144 +147,107 @@ h3 { 50% { transform: scale(1.05); } } -.box-label { +.box-tag { position: absolute; - top: -12px; - left: 50%; - transform: translateX(-50%); - background: var(--vp-c-brand-1); + top: -10px; + padding: 2px 8px; + border-radius: 10px; + font-size: 10px; + font-weight: 600; color: white; - padding: 4px 12px; - border-radius: 12px; - font-size: 12px; - font-weight: 500; - white-space: nowrap; } -.let-box .box-label { - background: #42b983; +.box-tag.const { + background: #3b82f6; +} + +.box-tag.let { + background: #10b981; +} + +.box-name { + font-size: 13px; + color: var(--vp-c-text-2); + margin-bottom: 4px; } .box-value { - font-size: 24px; + font-size: 20px; font-weight: 600; - font-family: 'Courier New', monospace; + font-family: monospace; color: var(--vp-c-text-1); - margin-bottom: 8px; } -.box-icon { - font-size: 16px; +.box-lock { + position: absolute; + bottom: 8px; + font-size: 12px; } -.message-bubble { +.message { text-align: center; - padding: 12px; - border-radius: 8px; - margin-bottom: 20px; - font-size: 14px; + padding: 10px; + border-radius: 6px; + margin-bottom: 12px; + font-size: 13px; font-weight: 500; - animation: fadeIn 0.3s ease; } -@keyframes fadeIn { - from { opacity: 0; transform: translateY(-10px); } - to { opacity: 1; transform: translateY(0); } +.message.error { + background: #fef2f2; + color: #dc2626; } -.message-bubble.error { - background: #fee; - color: #c00; -} - -.message-bubble.success { - background: #e8f5e9; - color: #2e7d32; +.message.success { + background: #ecfdf5; + color: #059669; } .controls { display: flex; - gap: 12px; + gap: 8px; justify-content: center; - flex-wrap: wrap; - margin-bottom: 20px; + margin-bottom: 12px; } -button { - padding: 8px 16px; +.btn { + padding: 8px 14px; border: none; border-radius: 6px; - font-size: 14px; + font-size: 13px; font-weight: 500; cursor: pointer; - transition: all 0.2s ease; -} - -button:active { - transform: scale(0.95); + transition: all 0.2s; } .btn-primary { - background: var(--vp-c-brand-1); + background: #3b82f6; color: white; } -.btn-primary:hover { - background: var(--vp-c-brand-2); -} - .btn-danger { - background: #f56565; + background: #ef4444; color: white; } -.btn-danger:hover { - background: #e53e3e; -} - .btn-secondary { background: var(--vp-c-bg-soft); color: var(--vp-c-text-1); + border: 1px solid var(--vp-c-border); } -.btn-secondary:hover { - background: var(--vp-c-bg-soft-hover); -} - -.code-display { +.code-snippet { background: #1e1e1e; - border-radius: 8px; - padding: 16px; - overflow-x: auto; + border-radius: 6px; + padding: 10px 14px; + display: flex; + flex-direction: column; + gap: 4px; } -.code-display pre { - margin: 0; -} - -.code-display code { - font-family: 'Courier New', monospace; - font-size: 13px; - line-height: 1.6; +.code-snippet code { + font-family: monospace; + font-size: 12px; color: #d4d4d4; } - -@media (max-width: 640px) { - .boxes-container { - flex-direction: column; - align-items: center; - } - - .variable-box { - width: 200px; - } - - .controls { - flex-direction: column; - } - - button { - width: 100%; - } -} diff --git a/docs/.vitepress/theme/components/appendix/js-runtime/CallStackDemo.vue b/docs/.vitepress/theme/components/appendix/js-runtime/CallStackDemo.vue new file mode 100644 index 0000000..cfe08b4 --- /dev/null +++ b/docs/.vitepress/theme/components/appendix/js-runtime/CallStackDemo.vue @@ -0,0 +1,542 @@ + + + + + diff --git a/docs/.vitepress/theme/components/appendix/js-runtime/GarbageCollectionDemo.vue b/docs/.vitepress/theme/components/appendix/js-runtime/GarbageCollectionDemo.vue new file mode 100644 index 0000000..9e45a53 --- /dev/null +++ b/docs/.vitepress/theme/components/appendix/js-runtime/GarbageCollectionDemo.vue @@ -0,0 +1,752 @@ + + + + + diff --git a/docs/.vitepress/theme/components/appendix/js-runtime/MemoryLeakDemo.vue b/docs/.vitepress/theme/components/appendix/js-runtime/MemoryLeakDemo.vue new file mode 100644 index 0000000..6a8dc4e --- /dev/null +++ b/docs/.vitepress/theme/components/appendix/js-runtime/MemoryLeakDemo.vue @@ -0,0 +1,682 @@ + + + + + diff --git a/docs/.vitepress/theme/components/appendix/js-runtime/RuntimeEnvironmentDemo.vue b/docs/.vitepress/theme/components/appendix/js-runtime/RuntimeEnvironmentDemo.vue new file mode 100644 index 0000000..d4f8afa --- /dev/null +++ b/docs/.vitepress/theme/components/appendix/js-runtime/RuntimeEnvironmentDemo.vue @@ -0,0 +1,487 @@ + + + + + diff --git a/docs/.vitepress/theme/components/appendix/js-runtime/TaskQueueDemo.vue b/docs/.vitepress/theme/components/appendix/js-runtime/TaskQueueDemo.vue new file mode 100644 index 0000000..7a8ec1a --- /dev/null +++ b/docs/.vitepress/theme/components/appendix/js-runtime/TaskQueueDemo.vue @@ -0,0 +1,652 @@ + + + + + diff --git a/docs/.vitepress/theme/components/appendix/typescript-intro/GenericDemo.vue b/docs/.vitepress/theme/components/appendix/typescript-intro/GenericDemo.vue new file mode 100644 index 0000000..6daba65 --- /dev/null +++ b/docs/.vitepress/theme/components/appendix/typescript-intro/GenericDemo.vue @@ -0,0 +1,556 @@ + + + + + diff --git a/docs/.vitepress/theme/components/appendix/typescript-intro/InterfaceDemo.vue b/docs/.vitepress/theme/components/appendix/typescript-intro/InterfaceDemo.vue new file mode 100644 index 0000000..a0227dd --- /dev/null +++ b/docs/.vitepress/theme/components/appendix/typescript-intro/InterfaceDemo.vue @@ -0,0 +1,445 @@ + + + + + diff --git a/docs/.vitepress/theme/components/appendix/typescript-intro/TypeAnnotationDemo.vue b/docs/.vitepress/theme/components/appendix/typescript-intro/TypeAnnotationDemo.vue new file mode 100644 index 0000000..537fb1b --- /dev/null +++ b/docs/.vitepress/theme/components/appendix/typescript-intro/TypeAnnotationDemo.vue @@ -0,0 +1,402 @@ + + + + + diff --git a/docs/.vitepress/theme/components/appendix/typescript-intro/TypeInferenceDemo.vue b/docs/.vitepress/theme/components/appendix/typescript-intro/TypeInferenceDemo.vue new file mode 100644 index 0000000..c805bd4 --- /dev/null +++ b/docs/.vitepress/theme/components/appendix/typescript-intro/TypeInferenceDemo.vue @@ -0,0 +1,618 @@ + + + + + diff --git a/docs/.vitepress/theme/index.js b/docs/.vitepress/theme/index.js index e89ff8e..5b46221 100644 --- a/docs/.vitepress/theme/index.js +++ b/docs/.vitepress/theme/index.js @@ -494,6 +494,19 @@ import ThisContextDemo from './components/appendix/javascript-intro/ThisContextD import PrototypeDemo from './components/appendix/javascript-intro/PrototypeDemo.vue' import AsyncDemo from './components/appendix/javascript-intro/AsyncDemo.vue' +// JavaScript Runtime Components +import RuntimeEnvironmentDemo from './components/appendix/js-runtime/RuntimeEnvironmentDemo.vue' +import CallStackDemo from './components/appendix/js-runtime/CallStackDemo.vue' +import TaskQueueDemo from './components/appendix/js-runtime/TaskQueueDemo.vue' +import MemoryLeakDemo from './components/appendix/js-runtime/MemoryLeakDemo.vue' +import GarbageCollectionDemo from './components/appendix/js-runtime/GarbageCollectionDemo.vue' + +// TypeScript Intro Components +import TypeAnnotationDemo from './components/appendix/typescript-intro/TypeAnnotationDemo.vue' +import InterfaceDemo from './components/appendix/typescript-intro/InterfaceDemo.vue' +import GenericDemo from './components/appendix/typescript-intro/GenericDemo.vue' +import TypeInferenceDemo from './components/appendix/typescript-intro/TypeInferenceDemo.vue' + export default { extends: DefaultTheme, Layout, @@ -935,6 +948,7 @@ export default { app.component('MacroMicroTaskDemo', MacroMicroTaskDemo) app.component('RenderingPerformanceDemo', RenderingPerformanceDemo) app.component('RenderingPipelineDemo', RenderingPipelineDemo) + app.component('EventLoopDemo', JSEventLoopDemo) // Alias for browser rendering context // Cache Design Extra Components Registration app.component('CacheArchitectureOverview', CacheArchitectureOverview) @@ -986,6 +1000,19 @@ export default { app.component('DOMTreeDemo', DOMTreeDemo) app.component('AsyncRestaurantDemo', AsyncRestaurantDemo) app.component('JSEventLoopDemo', JSEventLoopDemo) + + // JavaScript Runtime Components Registration + app.component('RuntimeEnvironmentDemo', RuntimeEnvironmentDemo) + app.component('CallStackDemo', CallStackDemo) + app.component('TaskQueueDemo', TaskQueueDemo) + app.component('MemoryLeakDemo', MemoryLeakDemo) + app.component('GarbageCollectionDemo', GarbageCollectionDemo) + + // TypeScript Intro Components Registration + app.component('TypeAnnotationDemo', TypeAnnotationDemo) + app.component('InterfaceDemo', InterfaceDemo) + app.component('GenericDemo', GenericDemo) + app.component('TypeInferenceDemo', TypeInferenceDemo) }, setup() { const route = useRoute() diff --git a/docs/zh-cn/appendix/3-browser-and-frontend/browser-as-os-rendering.md b/docs/zh-cn/appendix/3-browser-and-frontend/browser-as-os-rendering.md index 52bb97b..d3250af 100644 --- a/docs/zh-cn/appendix/3-browser-and-frontend/browser-as-os-rendering.md +++ b/docs/zh-cn/appendix/3-browser-and-frontend/browser-as-os-rendering.md @@ -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. 名词对照表 | 英文术语 | 中文对照 | 解释 | | :--- | :--- | :--- | diff --git a/docs/zh-cn/appendix/3-browser-and-frontend/browser-as-os.md b/docs/zh-cn/appendix/3-browser-and-frontend/browser-as-os.md index e26d015..1e4d112 100644 --- a/docs/zh-cn/appendix/3-browser-and-frontend/browser-as-os.md +++ b/docs/zh-cn/appendix/3-browser-and-frontend/browser-as-os.md @@ -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 个步骤: + +
+
+ +**🛒 第 1 步:填写订单** +选好商品,确认收货地址 + +
+
+ +**🗺️ 第 2 步:查找仓库** +系统找到具体的发货仓库 + +
+
+ +**📞 第 3 步:建立通道** +确认仓库营业且能发货 + +
+
+ +
+
+ +**🚚 第 4 步:仓库发货** +快递员把包裹送上门 + +
+
+ +**🎁 第 5 步:拆箱体验** +打开包裹,看到心仪的商品 + +
+
**访问网页的过程和网购惊人地相似!** @@ -20,10 +79,18 @@ +::: info 💡 核心启示 +理解浏览器工作原理的关键是:**把复杂的技术过程映射到熟悉的生活场景**。网购的 5 个步骤完美对应了浏览器访问网页的 5 个技术阶段。 +::: + --- ## 1. 第一步:填写"订单" —— URL 解析 +::: tip 🤔 核心问题 +**为什么网址要写成这样?** `https://www.example.com:8080/path/page.html?id=123#section` — 这串字符到底有什么含义? +::: + ### 生活比喻:填写购物单 假设你只在订单上写"买鞋子",仓库肯定不知道发哪双。你需要写清楚: @@ -49,12 +116,18 @@ -> **关键理解**:URL 的存在是为了让**人类**能记住和输入。计算机最终需要的是 **IP 地址**(就像快递员最终需要的是具体的仓库地址,而不是"Nike 官方店"这个名字)。 +::: info 💡 关键理解 +URL 的存在是为了让**人类**能记住和输入。计算机最终需要的是 **IP 地址**(就像快递员最终需要的是具体的仓库地址,而不是"Nike 官方店"这个名字)。 +::: --- ## 2. 第二步:查"地址簿" —— DNS 查询 +::: tip 🤔 核心问题 +**为什么浏览器能找到网站?** 你输入的是人类可读的域名(如 `baidu.com`),但计算机真正需要的是数字地址(IP)。这中间发生了什么? +::: + ### 生活比喻:查仓库地址 你下单写的是"Nike 官方店",但物流系统不知道仓库在哪。它需要查地址簿: @@ -90,12 +163,20 @@ -> **为什么需要这么多层?** 想象一下如果全世界只有一个地址簿,几十亿人同时查,早就崩溃了。分层设计让每个层级只管理自己的"辖区",既高效又可靠。 +::: 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)**。 -> **💡 你知道吗?** -> -> **布局和绘制**是浏览器最忙碌的时候。网页里的元素越多、结构越复杂,浏览器就需要花更多时间来计算位置和上色。这就是为什么有的复杂网页打开会卡顿的原因。 +::: 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 解析失败、服务器宕机) +- 为什么有的网页快、有的慢(网络延迟、服务器性能、页面复杂度) +- 浏览器是如何把代码变成画面的(渲染管道) + +**这就是理解技术原理的价值** — 遇到问题时,你能知道从哪里找原因,而不是束手无策。 +::: diff --git a/docs/zh-cn/appendix/3-browser-and-frontend/frontend-frameworks.md b/docs/zh-cn/appendix/3-browser-and-frontend/frontend-frameworks.md index ce4e881..2a2cfc9 100644 --- a/docs/zh-cn/appendix/3-browser-and-frontend/frontend-frameworks.md +++ b/docs/zh-cn/appendix/3-browser-and-frontend/frontend-frameworks.md @@ -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 🤔 核心问题 +**最早的网页是什么样的?为什么那时候不需要框架?** 理解这个阶段的局限性,才能明白后来技术演进的必要性。 +::: -### 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 "切图"是什么? - +你可能听说过"切图"这个词。它是早期前端的主要工作: -你可能听说过"切图"这个词。它是早期前端的主要工作: - -**什么是切图?** +**什么是切图?** 设计师用 Photoshop 设计好页面 → 前端把设计切成小图片 → 用 HTML 把图片拼成页面 -**为什么这么慢?** +**为什么这么慢?** -网页上的每张小图片,浏览器都要发一次**网络请求**。请求越多,加载越慢。 +网页上的每张小图片,浏览器都要发一次**网络请求**。请求越多,加载越慢。 -::: tip 💡 雪碧图(Sprite) +👇 **动手试试看**:观察图片请求对加载性能的影响 -为了减少请求数,出现了"雪碧图"技术:把很多小图合成一张大图。 + -优点是请求数变少,缺点是制作和维护都很麻烦。 +::: 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 的思路:亲手改页面 - - -jQuery 的核心思路是**命令式**: 你告诉浏览器"怎么做"。 +jQuery 的核心思路是**命令式**:你告诉浏览器"怎么做"。 ```javascript // 找到标题元素 @@ -149,11 +187,15 @@ $('#submit-btn').attr('disabled', true) $('ul').append('
  • 新项目
  • ') ``` -**问题**: 你需要记住页面上有哪些元素,每次数据变化都要手动更新所有相关元素。 +**问题**:你需要记住页面上有哪些元素,每次数据变化都要手动更新所有相关元素。 + +👇 **动手试试看**:对比 jQuery 和数据驱动的方式 + + ::: warning ⚠️ jQuery 的痛点 -想象你在做一个购物车: +想象你在做一个购物车: ```javascript // 用户点击"添加到购物车" @@ -165,32 +207,30 @@ function addToCart() { $('#cart-page-count').text(cartCount) // 购物车页面 $('#checkout-price').text(calculatePrice()) // 结算按钮 - // 如果漏了一个地方,页面就不一致了! + // 如果漏了一个地方,页面就不一致了! } ``` -**这就是"手动搬砖"的代价**: 容易出错,难以维护。 +**这就是"手动搬砖"的代价**:容易出错,难以维护。 ::: -### 3.3 移动端普及:响应式设计的出现 +### 3.3 移动端普及:响应式设计的出现 -这个阶段还有一个重要变化:**手机和平板开始流行**。 +这个阶段还有一个重要变化:**手机和平板开始流行**。 - +网页必须适配不同屏幕。这需要**响应式布局**:同一套 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() { } ``` +👇 **动手试试看**:调整浏览器宽度,观察响应式布局的效果 + + + ::: 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**:漏更新一个地方,页面就不一致 +- **协作困难**:多人修改同一个文件,容易冲突 - +**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() { } ``` +👇 **动手试试看**:对比命令式和声明式的区别 + + + ::: 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 💡 识别技巧 +- 看到 `` → 这是一个组件 +- 看到 `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 的区别 -**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 友好 +- 缺点:不适合动态内容 + +👇 **动手试试看**:对比不同渲染策略的特点 -## 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 + + + +``` + +**优点**: +- ✅ 学习曲线平缓,中文文档完善 +- ✅ 模板语法直观,易于理解 +- ✅ 单文件组件(.vue)结构清晰 +- ✅ 适合快速开发 + +**缺点**: +- ❌ 大型项目的状态管理需要额外学习 Vuex/Pinia +- ❌ 灵活性略逊于 React + +**适用场景**: +- 中小型 Web 应用 +- 快速原型开发 +- 中文团队(文档友好) + +### 7.3 React:UI 库 + +**核心理念**:只负责视图层,其他问题交给社区 + +```jsx +function App() { + const [message, setMessage] = useState('Hello React') + return
    {message}
    +} +``` + +**优点**: +- ✅ 生态系统最完善,组件库丰富 +- ✅ JSX 语法灵活,表达能力强大 +- ✅ 虚拟 DOM 性能优秀 +- ✅ 适合大型项目 + +**缺点**: +- ❌ 学习曲线较陡,需要掌握额外概念 +- ❌ 需要自己选择和搭配各种库 +- ❌ JSX 需要编译,不能直接在浏览器运行 + +**适用场景**: +- 大型复杂应用 +- 需要丰富生态的项目 +- 跨平台开发(React Native) + +### 7.4 Svelte:编译时框架 + +**核心理念**:没有虚拟 DOM,编译时将组件转换为高效的原生代码 + +```svelte + + +
    {message}
    +``` + +**优点**: +- ✅ **性能最优**(无虚拟 DOM 运行时开销) +- ✅ 包体积最小 +- ✅ 语法简单直观 +- ✅ 响应式系统天然支持 + +**缺点**: +- ❌ 生态相对较小 +- ❌ 社区规模不如 Vue/React +- ❌ 第三方库较少 + +**适用场景**: +- 性能要求极高的应用 +- 包体积敏感的项目 +- 愿意尝试新技术的团队 + +### 7.5 Angular:完整平台 + +**核心理念**:提供完整的解决方案,开箱即用 + +```typescript +@Component({ + selector: 'app-root', + template: '
    {{ message }}
    ' +}) +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 | 把代码分成多个小块,按需加载。 | diff --git a/docs/zh-cn/appendix/3-browser-and-frontend/javascript-deep-dive.md b/docs/zh-cn/appendix/3-browser-and-frontend/javascript-deep-dive.md index 38596ac..2069a76 100644 --- a/docs/zh-cn/appendix/3-browser-and-frontend/javascript-deep-dive.md +++ b/docs/zh-cn/appendix/3-browser-and-frontend/javascript-deep-dive.md @@ -1,206 +1,234 @@ # JavaScript 深度指南 ::: tip 前言 -你已经学了 HTML(网页的骨架)和 CSS(网页的皮肤)。 -但光有骨架和皮肤,网页只能"看",不能"用"—— -点一个按钮什么都不会发生,填一个表单提交不了。 +你已经学会了 HTML 和 CSS,能做出好看的网页了。但你可能会发现:点击按钮没反应,填了表单提交不了,网页就像一张"静态"的图片。 -**JavaScript 就是让网页能响应你操作的语言。** -点击按钮弹出菜单、输入搜索词显示建议、 -下拉页面加载更多内容——这些全靠 JavaScript。 +这就是我们需要 JavaScript 的原因——它让网页"活"起来。点击按钮能弹出菜单,输入文字能实时搜索,滚动页面能加载更多内容……这些交互效果都靠 JavaScript。 -在 vibecoding 的工作流里,AI 会帮你写大部分 JS 代码。 -你的任务不是从零手写,而是: -1. **读懂** AI 写了什么 -2. **判断** 它写得对不对 -3. **精准描述** 需要修改的地方 +在 vibecoding 里,AI 会帮你写大部分代码。但你至少得能看懂代码在做什么,否则 AI 写错了你也发现不了。读完这篇,你就能: -本章从最基础的概念讲起,逐步深入到专业开发者的思维方式。 -读完后,你不只是能"用" JavaScript,而是能"理解"它—— -这会让你在 vibecoding 中如虎添翼。 +- 读懂 AI 写的代码在做什么 +- 看出代码哪里有问题 +- 用清晰的话告诉 AI 怎么改 ::: +**这篇文章会带你学什么?** + +| 章节 | 内容 | 学完能干嘛 | +|-----|------|-----------| +| **第 1 章** | JavaScript 是什么 | 明白它在网页里扮演什么角色 | +| **第 2 章** | 数据与变量 | 知道程序怎么存东西、怎么用东西 | +| **第 3 章** | 函数与逻辑 | 看懂代码的判断、循环和复用逻辑 | +| **第 4 章** | DOM 与事件 | 知道代码怎么控制网页、怎么响应用户操作 | +| **第 5 章** | 实战技巧 | 拿到 AI 代码怎么读、遇到报错怎么说 | + +每一章都从"能识别代码"开始,不需要你会手写。遇到不懂的代码,随时回来查就行。 + --- ## 1. JavaScript 是什么 -### 1.1 从"只能看"到"能交互" +::: tip 🤔 核心问题 +**为什么网页需要 JavaScript?** HTML 和 CSS 已经能让网页有内容、有样式了,为什么还要学一门新语言? +::: -早期的网页就像一本**电子杂志**——你只能看,不能改。内容是固定的,你点击什么都不会改变。 +### 1.1 从"静态网页"到"动态应用" -但现代网页完全不同了。它们更像**桌面软件**: -- 在线文档可以像 Word 一样编辑 -- 地图网站可以像 GPS 一样导航 -- 聊天应用可以像微信一样实时收发消息 +
    +
    -**这种转变的核心技术就是 JavaScript**——它让网页从"展示信息"变成了"可以交互的工具"。 +**📄 没有 JavaScript 的网页** +- 内容固定,无法交互 +- 点击按钮没反应 +- 填写表单提交不了 +- 页面不会自动更新 -用一句话定位: -- **HTML** 是网页的骨架(结构) -- **CSS** 是网页的皮肤(样式) -- **JavaScript** 是网页的肌肉和神经系统(行为) +*就像一张纸质海报,只能看* -### 1.2 Vibecoding 中的 JavaScript +
    +
    -::: warning 💡 从踩坑到顿悟 -小李用 AI 做了一个待办事项应用。AI 生成的代码能添加待办、能标记完成,看起来一切正常。 +**🚀 有 JavaScript 的网页** +- 点击按钮弹出菜单 +- 输入文字实时搜索 +- 滚动自动加载内容 +- 数据实时更新显示 -但当他想加"删除"功能时,对 AI 说:"加一个删除功能。" AI 加了,可每次点删除,删掉的都不是他点的那一项,而是列表最后一项。 +*就像一个真正的应用程序* -小李完全看不懂代码,只能反复说"删除有 bug",AI 改了好几版都不对。 +
    +
    -最后他花了 10 分钟学了"数组"和"索引"的概念,看懂了代码里的 `splice(index, 1)`,然后对 AI 说:"删除时不要用数组索引来定位,改成用每个事项的唯一 id 来匹配删除。" +**用一句话理解三者的关系:** + +| 技术 | 比喻 | 作用 | +|------|------|------| +| **HTML** | 骨架 | 定义网页的结构和内容 | +| **CSS** | 皮肤 | 定义网页的外观和样式 | +| **JavaScript** | 肌肉和神经系统 | 让网页能响应、能交互、能思考 | + +### 1.2 为什么 vibecoding 也需要懂 JavaScript? + +::: warning 刚学 JS 的开发者踩坑记 +一位刚学 JavaScript 的开发者用 AI 做了一个"计数器"应用:点击按钮,数字加 1。AI 生成的代码能正常工作。 + +但他想改成"点击加 2",对 AI 说:"让每次点击加 2。" AI 改了代码,可数字还是只加 1。 + +他问 AI 为啥没效果,AI 解释了一通,但他看不懂代码里的 `count = count + 1` 是什么意思,也不知道 AI 改的是不是这个地方。只能反复说"加 2 没效果",AI 又改了好几版,有的把初始值改成 2,有的在完全不相关的地方加了 2。 + +最后他看了第 2 章"变量"的概念,明白了 `count = count + 1` 是在把 count 的值加 1 再存回去。然后他对 AI 说:"把 `count + 1` 改成 `count + 2`。" 一次就改对了。 -**这就是为什么 vibecoding 也需要读懂代码——不是为了手写,而是为了在 AI 出错时能一句话说到点子上。** +**这就是为什么要懂 JavaScript——不是为了手写代码,而是为了在 AI 没改对时,你能一眼看出问题在哪,一句话说到点子上。** ::: -**你的定位不是从零手写代码**,而是: -- 能看懂 AI 生成的代码在做什么 -- 能判断它写得对不对 -- 能用精准的语言告诉 AI 需要怎么改 +### 1.3 先睹为快:一段真实的 AI 代码 -### 1.3 从一段真实代码开始 +在深入学习之前,让我们先看一段 AI 生成的真实代码。不要担心看不懂,只要有个印象,后面我们会逐一讲解每个部分。 -让我们先看一段 AI 生成的真实代码。不要担心看不懂,我们会在后面的章节逐一讲解每个部分。 - -**场景**:让 AI 做一个"点击按钮切换背景颜色"的网页 +**场景**:做一个"点击按钮切换背景颜色"的功能 ```javascript -// 场景:点击按钮切换背景颜色 -const colors = ['#ff6b6b', '#4ecdc4', '#45b7d1', '#96ceb4'] // ← 数组(第2章 2.2节) -let currentIndex = 0 // ← 变量(第2章 2.1节) +// 定义一组颜色 +const colors = ['#ff6b6b', '#4ecdc4', '#45b7d1', '#96ceb4'] +let currentIndex = 0 -const button = document.querySelector('#changeBtn') // ← DOM 查找(第4章 4.2节) +// 找到页面上的按钮 +const button = document.querySelector('#changeBtn') -button.addEventListener('click', () => { // ← 事件监听 + 箭头函数(第3章 3.2节 + 第4章 4.3节) - currentIndex = (currentIndex + 1) % colors.length // ← 运算(第2章) - document.body.style.backgroundColor = colors[currentIndex] // ← 修改样式(第4章 4.2节) +// 给按钮添加点击事件 +button.addEventListener('click', () => { + currentIndex = (currentIndex + 1) % colors.length + document.body.style.backgroundColor = colors[currentIndex] }) ``` **这段代码在做什么?** -- 定义了一组颜色(数组) -- 记录当前用到了第几个颜色(变量) -- 找到页面上的按钮(DOM 查找) -- 给按钮添加点击事件:每次点击就换一个背景色(事件监听) -现在你不需要理解每一行,只要有个印象即可。接下来我们会按顺序学习每个概念。 +| 代码 | 作用 | 对应章节 | +|------|------|----------| +| `const colors = [...]` | 定义一组颜色数据 | 第 2 章:数组 | +| `let currentIndex = 0` | 记录当前显示第几个颜色 | 第 2 章:变量 | +| `document.querySelector(...)` | 找到页面上的按钮 | 第 4 章:DOM 查找 | +| `button.addEventListener(...)` | 给按钮添加点击事件 | 第 4 章:事件监听 | +| `() => {...}` | 定义点击后要执行的代码 | 第 3 章:箭头函数 | -::: tip 🤖 Vibecoding 备忘 -**AI 代码里你会看到:** -- `const` / `let` → 变量声明(第2章) -- `{}` / `[]` → 对象和数组(第2章) -- `function` / `=>` → 函数定义(第3章) -- `document.querySelector` → 查找网页元素(第4章) -- `addEventListener` → 监听用户操作(第4章) -- `async` / `await` → 等待耗时操作(第4章) - -**遇到问题时这样跟 AI 说:** -- ✅ "第 X 行是什么意思?" -- ✅ "这个代码的执行流程是什么?" -- ✅ "我想让它在点击时做 XXX,该怎么改?" +::: info 💡 核心启示 +你不需要现在就理解每一行代码。只要记住:**JavaScript 代码就是一系列指令,告诉浏览器"当用户做某事时,应该发生什么"。** ::: --- ## 2. 数据篇:变量与数据类型 -### 2.1 变量:给数据贴标签 +::: tip 🤔 核心问题 +**程序是怎么"记住"东西的?** 用户输入的内容、从服务器获取的数据、计算过程中的中间结果——这些信息都存在哪里? +::: -**变量就像一个带名字的盒子**——你可以把数据放进去,需要时再取出来。 +### 2.1 变量:给数据起个名字 + +**变量就像一个有标签的盒子**——你可以把数据放进去,以后通过标签来取用。 ```javascript const name = "张三" // 名字不会变,用 const let age = 25 // 年龄可能会变,用 let ``` -**两种声明方式:** +**为什么要区分 const 和 let?** -| 关键字 | 能否重新赋值 | 使用场景 | -|--------|-------------|---------| -| `const` | ❌ 不能 | 默认首选,值不会变的情况 | -| `let` | ✅ 能 | 需要重新赋值的情况 | -| `var` | (老语法) | 遇到了知道是变量就行,不要用 | +想象一下:你的身份证号码(const)这辈子都不会变,但你的年龄(let)每年都会变。JavaScript 让你用不同的关键字来表达这种"变与不变"的意图。 -**Vibecoding 提示:** -- 看到 `const` → 这个值后面不会变 -- 看到 `let` → 这个值后面会变 +| 关键字 | 能否修改 | 使用场景 | 示例 | +|--------|---------|----------|------| +| `const` | ❌ 不能 | 值不会变的数据 | 身份证号、配置项、颜色列表 | +| `let` | ✅ 能 | 值会变化的数据 | 计数器、当前选中的选项、用户输入 | +::: details 🔍 看一个具体的例子 ```javascript -const score = 0 -score = 10 // ❌ 报错!const 不能重新赋值 +// 用 const:这些值不会变 +const PI = 3.14159 +const MAX_USERS = 100 +const APP_NAME = "TodoList" -let points = 0 -points = 10 // ✅ 正确,let 可以重新赋值 +// 用 let:这些值会变化 +let count = 0 +count = 1 // ✅ 可以修改 + +count = count + 1 // ✅ 可以基于原值计算 + +// 如果用 const 会怎样? +const fixedCount = 0 +fixedCount = 1 // ❌ 报错!const 不能重新赋值 ``` +::: -👇 **动手试试看**: +👇 **动手试试看**:修改下面的代码,看看 const 和 let 的区别 -### 2.2 数据类型:JS 世界里的几种"东西" +### 2.2 数据类型:JavaScript 里的几种"东西" -JavaScript 有几种基本的数据类型,最常用的是这三个: +JavaScript 把数据分成几种类型,最常用的有三种: -**基本类型:** +| 类型 | 说明 | 示例 | 实际场景 | +|------|------|------|----------| +| `string`(字符串)| 文本内容 | `"hello"`, `'你好'` | 用户名、商品描述、提示信息 | +| `number`(数字)| 数值 | `42`, `3.14` | 价格、数量、评分 | +| `boolean`(布尔值)| 是/否 | `true`, `false` | 是否登录、是否完成、是否可见 | -| 类型 | 说明 | 示例 | -|------|------|------| -| `string` | 文本 | `"hello"`, `'你好'` | -| `number` | 数字 | `42`, `3.14`, `NaN` | -| `boolean` | 布尔值(真/假) | `true`, `false` | +**还有两个特殊值需要知道:** -**两个特殊的值:** -- `undefined` → 还没给值 -- `null` → 故意设为空 +- `undefined` → 变量声明了,但还没给值 +- `null` → 故意设为空(表示"这里没有值") -**模板字符串(反引号):** - -AI 代码里你经常会看到这种写法: +::: details 🔍 模板字符串:更方便地拼接文本 +在 AI 代码里,你经常会看到用反引号(`` ` ``)包裹的字符串,里面还有 `${...}`: ```javascript const name = "张三" const age = 25 -// 用反引号(键盘左上角那个键)和 ${} +// 传统写法(麻烦) +const message = "我叫" + name + ",今年" + age + "岁" + +// 模板字符串(简洁) const message = `我叫${name},今年${age}岁` -// message = "我叫张三,今年25岁" +// 结果:"我叫张三,今年25岁" ``` -**Vibecoding 提示:** -- 看到反引号 `` ` `` → 这是模板字符串,里面可以用 `${变量}` 插入值 +**识别要点**:看到反引号和 `${}`,就知道是在把变量插入到文本中。 +::: -### 2.3 对象与数组:把数据组织起来 +### 2.3 对象和数组:把数据组织起来 -**对象 = 一组有名字的属性**(像身份证/个人资料卡) +**对象 = 一组有名字的属性**(像一张个人信息表) ```javascript -const person = { +const user = { name: "张三", age: 25, - isStudent: true + isVIP: true } -// 访问属性 -console.log(person.name) // "张三" -console.log(person.age) // 25 +// 使用点号访问属性 +console.log(user.name) // "张三" +console.log(user.age) // 25 ``` -**数组 = 一组有顺序的数据**(像排队/列表) +**数组 = 一组有顺序的数据**(像一个列表) ```javascript const colors = ['红色', '绿色', '蓝色'] -// 访问元素(索引从 0 开始) +// 用索引访问(从 0 开始) console.log(colors[0]) // "红色" console.log(colors[1]) // "绿色" ``` -**嵌套结构:** +**嵌套结构:对象里套数组、数组里套对象** -在 AI 生成的代码里,你经常会看到对象里套数组、数组里套对象: +这是 AI 代码中最常见的数据结构: ```javascript const todos = [ @@ -209,21 +237,21 @@ const todos = [ { id: 3, text: "写文档", done: false } ] -// 访问:先找数组索引,再找对象属性 +// 访问:先取数组的第 0 项,再取它的 text 属性 console.log(todos[0].text) // "学习 JavaScript" -console.log(todos[1].done) // true ``` -**Vibecoding 提示:** -- 看到 `{}` → 对象(一组有名字的数据) -- 看到 `[]` → 数组(一组有顺序的数据) -- 看到 `data[0].name` → 先取数组的第 0 项,再取它的 name 属性 +::: info 💡 识别技巧 +- 看到 `{}` → 这是一个对象,里面是一组 `名字: 值` +- 看到 `[]` → 这是一个数组,里面是一组按顺序排列的值 +- 看到 `data[0].name` → 先取数组第 0 项,再取它的 name 属性 +::: -### 2.4 值与引用:为什么改了 B,A 也变了? +### 2.4 值与引用:一个容易踩的坑 -这是新手最容易踩的坑! +这是新手最常遇到的问题之一! -**基本类型(string、number、boolean)赋值 = 复制一份副本:** +**基本类型(string、number、boolean)赋值 = 复制一份全新的数据:** ```javascript let a = 10 @@ -232,102 +260,80 @@ b = 20 console.log(a) // 10(a 不受影响) ``` -**对象和数组赋值 = 复制的是"地址":** +**对象和数组赋值 = 复制的是"地址"(指向同一个东西):** ```javascript -let obj1 = { name: "张三" } -let obj2 = obj1 // obj2 指向同一个对象 -obj2.name = "李四" // 修改 obj2 会影响 obj1 -console.log(obj1.name) // "李四"(obj1 也变了!) +let user1 = { name: "张三" } +let user2 = user1 // user2 指向同一个对象 +user2.name = "李四" // 修改 user2 会影响 user1 +console.log(user1.name) // "李四"(user1 也变了!) ``` -这就是为什么 AI 代码里经常看到 `[...array]` 或 `{...obj}`——它在"创建副本",避免互相影响。 +**为什么要创建副本?** + +在 React/Vue 中,直接修改数据会导致界面不更新。所以 AI 代码里经常看到 `[...array]` 或 `{...obj}`——它在创建副本,避免互相影响。 ```javascript // 用展开运算符创建副本 const arr1 = [1, 2, 3] -const arr2 = [...arr1] // 创建新数组,不是复制地址 +const arr2 = [...arr1] // 创建新数组 arr2.push(4) console.log(arr1) // [1, 2, 3](不受影响) console.log(arr2) // [1, 2, 3, 4] ``` -**Vibecoding 场景:** - -如果你发现修改了一条数据,别的地方也莫名其妙变了,十有八九是引用问题。 - -告诉 AI:**"这里需要深拷贝,不要直接修改原数据"** - -👇 **动手试试看**: +👇 **动手试试看**:观察修改副本时原数据的变化 -### 2.5 解构与展开:现代 JS 的快捷写法 +### 2.5 解构与展开:现代 JavaScript 的快捷写法 -这两个语法在 AI 生成的代码里到处都是,不认识就读不懂代码。 +这两个语法在 AI 代码里到处都是,不认识就读不懂代码。 -**解构赋值:从对象或数组里把数据拿出来** +**解构赋值:从对象或数组里快速提取数据** ```javascript -const person = { name: "张三", age: 25, city: "北京" } +const user = { name: "张三", age: 25, city: "北京" } -// 不用解构(传统写法) -const name = person.name -const age = person.age +// 传统写法(麻烦) +const name = user.name +const age = user.age -// 用解构(现代写法) -const { name, age } = person - -// 数组解构 -const colors = ['红色', '绿色', '蓝色'] -const [first, second] = colors -// first = '红色', second = '绿色' +// 解构写法(简洁) +const { name, age } = user +// 效果一样,但一行搞定 ``` -**展开运算符:把数组或对象"展开铺平"** +**展开运算符:复制并扩展数据** ```javascript -// 数组展开 +// 复制数组并添加新元素 const arr1 = [1, 2, 3] const arr2 = [...arr1, 4, 5] // [1, 2, 3, 4, 5] -// 对象展开 -const obj1 = { name: "张三", age: 25 } -const obj2 = { ...obj1, city: "北京" } +// 复制对象并添加新属性 +const user1 = { name: "张三", age: 25 } +const user2 = { ...user1, city: "北京" } // { name: "张三", age: 25, city: "北京" } - -// 合并对象 -const baseConfig = { url: "/api", timeout: 5000 } -const userConfig = { timeout: 10000 } -const finalConfig = { ...baseConfig, ...userConfig } -// { url: "/api", timeout: 10000 }(userConfig 会覆盖 baseConfig 的同名属性) ``` -**Vibecoding 提示:** -- 看到 `const { name, age } = person` → 从 person 对象里把 name 和 age 拿出来 +::: info 💡 识别技巧 +- 看到 `const { name, age } = person` → 从 person 对象里提取 name 和 age - 看到 `...array` 或 `...obj` → 把数组或对象展开铺平 - 你不需要能手写,但必须能读懂 - -::: tip 🤖 Vibecoding 备忘 -**AI 代码里你会看到:** -- `const { data } = response` → 从 response 里提取 data 字段 -- `const [first, ...rest] = array` → 取第一个,剩下的放 rest 里 -- `{ ...obj, newProp: value }` → 复制 obj 并添加新属性 -- `[...arr, newItem]` → 复制数组并添加新元素 - -**遇到问题时这样跟 AI 说:** -- "这个解构是什么意思?" -- "我想从对象里提取 XXX 字段" -- "这里需要创建副本,不要修改原数据" ::: --- ## 3. 逻辑篇:函数与流程控制 -### 3.1 条件判断:if/else 和三元运算符 +::: tip 🤔 核心问题 +**代码是怎么"做决定"和"重复做事"的?** 程序需要根据条件执行不同的操作,也需要重复执行某些任务——这些逻辑怎么表达? +::: -**if/else:如果...就...否则...** +### 3.1 条件判断:如果...就...否则... + +**if/else:最基本的条件判断** ```javascript const age = 18 @@ -339,10 +345,10 @@ if (age >= 18) { } ``` -**三元运算符:简写的条件判断** +**三元运算符:简写的 if/else** ```javascript -// 完整写法 +// 完整写法(4 行) let message if (age >= 18) { message = "成年人" @@ -350,9 +356,9 @@ if (age >= 18) { message = "未成年" } -// 三元运算符(一行搞定) +// 三元运算符(1 行) const message = age >= 18 ? "成年人" : "未成年" -// 格式:条件 ? 真的值 : 假的值 +// 格式:条件 ? 条件为真时的值 : 条件为假时的值 ``` **&& 短路写法:React 代码里常见** @@ -367,11 +373,12 @@ if (isLoggedIn) { } ``` -**Vibecoding 提示:** +::: info 💡 识别技巧 - 看到 `? :` → 这是三元运算符,简写的 if/else - 看到 `&&` → 前面为 true 才执行后面 +::: -### 3.2 函数:可以反复调用的操作 +### 3.2 函数:把操作打包起来 **函数 = 一道菜的配方** @@ -391,50 +398,36 @@ console.log(greet("张三")) // "Hello 张三" console.log(greet("李四")) // "Hello 李四" ``` -**三种写法一眼识别:** +**三种写法,一眼识别:** ```javascript -// 1. function 声明 +// 1. function 声明(传统写法) function greet(name) { return "Hello " + name } -// 2. 函数表达式 -const greet = function(name) { - return "Hello " + name -} - -// 3. 箭头函数(AI 代码里用得最多) +// 2. 箭头函数(AI 代码里用得最多) const greet = (name) => { return "Hello " + name } -// 箭头函数简写(只有一行时可以省略 {} 和 return) +// 3. 箭头函数简写(只有一行时) const greet = (name) => "Hello " + name ``` -**重点:** 能认出来就行,不需要纠结什么时候用哪种。箭头函数最简洁,AI 代码里用得最多。 - -👇 **动手试试看**: +👇 **动手试试看**:输入不同的名字,看看函数怎么工作 -**Vibecoding 提示:** +::: info 💡 识别技巧 - 看到 `function` 或 `=>` → 这是一个函数 - 看到 `fn()` → 在调用这个函数 - 看到 `() => {}` → 箭头函数,现代 JS 的主流写法 +::: -### 3.3 循环与数组方法 +### 3.3 数组方法:处理列表的利器 -**for 循环:基本认识即可** - -```javascript -for (let i = 0; i < 5; i++) { - console.log(i) // 输出 0, 1, 2, 3, 4 -} -``` - -**数组方法:React/Vue 代码里几乎每个列表渲染都用 map** +在 React/Vue 里,几乎每个列表渲染都会用到这些方法。 ```javascript const todos = [ @@ -442,9 +435,9 @@ const todos = [ { id: 2, text: "工作", done: true } ] -// .map():把数组的每一项变成另一个东西(返回新数组) -const todoItems = todos.map(todo => `
  • ${todo.text}
  • `) -// ["
  • 学习
  • ", "
  • 工作
  • "] +// .map():把数组的每一项变成另一个东西 +const texts = todos.map(todo => todo.text) +// ["学习", "工作"] // .filter():筛选出符合条件的项 const unfinished = todos.filter(todo => !todo.done) @@ -455,10 +448,11 @@ const found = todos.find(todo => todo.id === 1) // { id: 1, text: "学习", done: false } ``` -**Vibecoding 提示:** +::: info 💡 识别技巧 - 看到 `.map()` → 对数组做变换,返回新数组 - 看到 `.filter()` → 筛选数组 -- 看到 `items.map(item =>
  • {item.name}
  • )` → 把每个数据项变成一个列表标签 +- 看到 `items.map(item =>
  • {item.name}
  • )` → 把每个数据项变成列表标签 +::: ### 3.4 作用域:变量的"可见范围" @@ -478,102 +472,55 @@ function room() { console.log(local) // ❌ 报错!外面看不到房间里的东西 ``` -**三种作用域:** - -```javascript -// 全局作用域(走廊) -const appName = "Todo" - -function outer() { - // 函数作用域(房间) - const message = "你好" - - if (true) { - // 块级作用域(小房间) - const greeting = message + appName - console.log(greeting) // ✅ 能看到外层的 - } - - console.log(greeting) // ❌ 报错!外层看不到内层 -} -``` - **核心直觉:** 代码写在哪里,决定了它能看到什么变量。 -👇 **动手试试看**: +👇 **动手试试看**:点击不同的作用域,看看能访问哪些变量 ### 3.5 闭包:函数"记住"了它诞生时的环境 -**不要把闭包当成独立的难点概念来讲**,从一个具体场景引入: - -**问题:为什么点击事件的回调函数能使用外面定义的变量?** +**不要把它当成独立的概念,从一个具体场景理解:** ```javascript -function setupButtons() { - let count = 0 - - button.addEventListener('click', () => { - count++ // 为什么这里的 count 能记住上次的值? - console.log(count) - }) -} -``` - -**核心直觉:** 函数在被创建时,会"记住"它周围的变量,即使外层函数已经执行完了。 - -**实际场景:计数器** - -```javascript -function createCounter() { - let count = 0 // 私有变量 +function setupCounter() { + let count = 0 // 这个变量在函数内部 return { add: () => { count++; return count }, - subtract: () => { count--; return count }, getCount: () => count } } -const counter1 = createCounter() -console.log(counter1.add()) // 1 -console.log(counter1.add()) // 2 -console.log(counter1.getCount()) // 2 - -const counter2 = createCounter() -console.log(counter2.add()) // 1(每个计数器独立) +const counter = setupCounter() +console.log(counter.add()) // 1 +console.log(counter.add()) // 2 +console.log(counter.getCount()) // 2 ``` -👇 **动手试试看**: +**核心直觉:** 函数在被创建时,会"记住"它周围的变量,即使外层函数已经执行完了。 + +👇 **动手试试看**:观察闭包如何让函数"记住"状态 -**Vibecoding 场景:** +### 3.6 this:函数被谁调用 -如果 AI 代码里一个内部函数用了外部变量,这就是闭包在工作。一般不需要你干预。 +**不讲复杂的绑定规则,只讲最常见的场景:** -但如果循环里创建函数导致所有函数共享同一个变量值,告诉 AI:**"闭包捕获了循环变量的引用,需要修复"** - -### 3.6 this:谁在调用我? - -**不讲四种绑定规则**,只讲两个最常见的场景: - -**场景 1:在 class 的方法里,this 指向这个 class 的实例** +**场景 1:在对象的方法里,this 指向这个对象** ```javascript -class Counter { - constructor() { - this.count = 0 - } - - increment() { - this.count++ // this 指向 Counter 的实例 +const user = { + name: "张三", + sayHi() { + console.log("你好,我是" + this.name) // this 指向 user } } +user.sayHi() // "你好,我是张三" ``` -**场景 2:在事件监听回调里,this 指向触发事件的 DOM 元素** +**场景 2:在事件监听里,this 指向触发事件的元素** ```javascript button.addEventListener('click', function() { @@ -586,37 +533,21 @@ button.addEventListener('click', () => { }) ``` -**核心直觉:** this 不是固定的,取决于函数怎么被调用。 - -**Vibecoding 场景:** - -如果 AI 代码里出现 this 相关的 bug(比如 `Cannot read property of undefined`),通常是因为函数的 this 指向丢了。 - -告诉 AI:**"这个方法里的 this 指向不对,改成箭头函数或者用 bind"** - -::: tip 🤖 Vibecoding 备忘 -**AI 代码里你会看到:** -- `if/else` → 条件判断 -- `condition ? a : b` → 三元运算符,简写 if/else -- `fn()` → 调用函数 -- `() => {}` → 箭头函数 -- `.map()` / `.filter()` / `.find()` → 数组方法 -- `this` → 取决于函数怎么被调用 - -**遇到问题时这样跟 AI 说:** -- "这个判断条件是什么意思?" -- "这个函数的返回值是什么?" -- "这个 this 指向哪里?" -- "这个闭包为什么会共享变量?" +::: info 💡 遇到问题怎么办? +如果 AI 代码里出现 this 相关的 bug(比如 `Cannot read property of undefined`),告诉 AI:"这个方法里的 this 指向不对,改成箭头函数或者用 bind" ::: --- ## 4. 交互篇:DOM、事件与异步 -### 4.1 DOM:JavaScript 看到的网页长什么样 +::: tip 🤔 核心问题 +**JavaScript 怎么跟网页"互动"?** 怎么找到页面上的元素?怎么响应用户的点击、输入?怎么从服务器获取数据? +::: -网页在 JS 眼里是一棵"树",每个 HTML 标签是树上的一个"节点"。 +### 4.1 DOM:JavaScript 看到的网页 + +网页在 JavaScript 眼里是一棵"树",每个 HTML 标签都是树上的一个"节点"。 ```html @@ -631,9 +562,9 @@ button.addEventListener('click', () => { ``` -**JS 操控网页 = 找到节点、修改节点、创建/删除节点** +**JS 操控网页 = 找到节点 + 修改节点 + 创建/删除节点** -👇 **动手试试看**: +👇 **动手试试看**:点击节点,看看 DOM 树是怎么组织的 @@ -643,12 +574,9 @@ button.addEventListener('click', () => { ```javascript // 根据 CSS 选择器查找(最常用) -const title = document.querySelector('h1') -const button = document.querySelector('#submitBtn') -const items = document.querySelectorAll('.item') - -// 根据 ID 查找 -const button = document.getElementById('submitBtn') +const title = document.querySelector('h1') // 找第一个 h1 +const button = document.querySelector('#btn') // 找 id="btn" 的元素 +const items = document.querySelectorAll('.item') // 找所有 class="item" 的元素 ``` **修改元素:** @@ -656,28 +584,23 @@ const button = document.getElementById('submitBtn') ```javascript // 改文字 title.textContent = "新标题" -title.innerHTML = "粗体标题" // 改样式 element.style.color = "red" element.style.fontSize = "20px" // 改 CSS 类 -element.classList.add('active') -element.classList.remove('hidden') -element.classList.toggle('open') - -// 创建新元素 -const newItem = document.createElement('li') -newItem.textContent = "新项目" -document.querySelector('ul').appendChild(newItem) +element.classList.add('active') // 添加类 +element.classList.remove('hidden') // 移除类 +element.classList.toggle('open') // 切换类(有就移除,没有就添加) ``` -**Vibecoding 提示:** +::: info 💡 识别技巧 - 看到 `document.querySelector` → 在查找网页元素 -- 看到 `.textContent` / `.innerHTML` → 改文字 +- 看到 `.textContent` → 改文字 - 看到 `.style.xxx` → 改样式 - 看到 `.classList.add/remove/toggle` → 改 CSS 类 +::: ### 4.3 事件:当用户做了某个操作时... @@ -691,26 +614,25 @@ button.addEventListener('click', () => { **常见事件:** -| 事件 | 触发时机 | -|------|---------| -| `click` | 点击 | -| `input` | 输入框内容变化 | -| `submit` | 表单提交 | -| `scroll` | 滚动页面 | -| `keydown` | 按下键盘 | +| 事件 | 触发时机 | 实际场景 | +|------|---------|----------| +| `click` | 点击 | 按钮点击、链接跳转 | +| `input` | 输入框内容变化 | 实时搜索、表单验证 | +| `submit` | 表单提交 | 登录、注册、提交数据 | +| `scroll` | 滚动页面 | 懒加载、回到顶部 | -**事件对象:** +**事件对象:获取更多信息** ```javascript input.addEventListener('input', (e) => { console.log(e.target.value) // 获取输入框的值 - e.preventDefault() // 阻止默认行为 + e.preventDefault() // 阻止默认行为(比如表单提交后刷新页面) }) ``` -**Vibecoding 场景:** - -当你想给按钮加一个功能,本质上就是在告诉 AI:**"给这个按钮添加一个点击事件,点击后执行某某操作"** +::: info 💡 实际应用 +当你想给按钮加一个功能,本质上就是在告诉 AI:"给这个按钮添加一个点击事件,点击后执行某某操作" +::: ### 4.4 异步:为什么有些操作不是立刻完成的 @@ -718,14 +640,13 @@ input.addEventListener('input', (e) => { 点菜后不用站在厨房门口等,可以先做别的事,菜好了服务员会端过来。 -**最常见场景:从服务器获取数据(fetch / API 调用)** +**最常见场景:从服务器获取数据** ```javascript -// 同步写法(会卡住页面) -const data = fetch('/api/data') // ❌ 别这样写 -console.log(data) +// 同步写法(会卡住页面,不要用) +const data = fetch('/api/data') // ❌ 这样写会卡住 -// 异步写法(不卡住) +// 异步写法(正确) async function loadData() { try { const response = await fetch('/api/data') @@ -743,28 +664,23 @@ async function loadData() { - `await` → 等待这个操作完成(但不会卡住页面) - `try/catch` → 处理可能出现的错误 -**只讲 async/await**。回调和 Promise.then 链各用一句话提("这是旧的写法,认识就行")。 - -👇 **动手试试看**: +👇 **动手试试看**:观察异步操作的执行顺序 -**Vibecoding 提示:** +::: info 💡 识别技巧 - 看到 `async/await` → 在等待耗时操作 - 看到 `fetch()` → 在从服务器获取数据 - 看到 `try/catch` → 在处理可能的错误 +::: ### 4.5 事件循环:JavaScript 到底怎么工作的 -**不用术语"微任务/宏任务"**,用可视化演示: +**不用术语"微任务/宏任务",用一个简单的模型理解:** **JS 是一个"单人工位"**,同时只做一件事,但有一个"待办便签栏"(任务队列)。 -当遇到要等待的操作(网络请求、定时器),JS 不是傻等,而是把"等好了之后做什么"贴到便签栏,自己继续往下执行。 - -等当前事情做完了,才去看便签栏上有没有该做的事。 - -**这个心智模型解释了**为什么 `console.log` 的打印顺序有时候跟代码顺序不一样。 +当遇到要等待的操作(网络请求、定时器),JS 不是傻等,而是把"等好了之后做什么"贴到便签栏,自己继续往下执行。等当前事情做完了,才去看便签栏。 ```javascript console.log("1") @@ -776,41 +692,22 @@ console.log("3") // 输出:1, 3, 2(不是 1, 2, 3!) ``` -**执行流程:** +**为什么?** 1. 执行 `console.log("1")` → 输出 1 2. 遇到 `setTimeout` → 把回调贴到便签栏,继续往下 3. 执行 `console.log("3")` → 输出 3 4. 当前代码执行完了,去看便签栏 5. 执行 `setTimeout` 的回调 → 输出 2 -👇 **动手试试看**: +👇 **动手试试看**:观察代码的执行顺序 -**Vibecoding 场景:** - -如果 AI 代码里数据还没获取到页面就渲染了,这是异步时序问题。 - -告诉 AI:**"数据还没加载完就开始渲染了,需要添加 loading 状态,等数据到了再渲染"** - -::: tip 🤖 Vibecoding 备忘 -**AI 代码里你会看到:** -- `document.querySelector()` → 查找元素 -- `.addEventListener()` → 监听事件 -- `async/await` → 异步操作 -- `fetch()` → 网络请求 - -**遇到问题时这样跟 AI 说:** -- "我想给按钮添加点击事件" -- "数据加载完了但没有显示" -- "页面在数据加载前就渲染了,需要加 loading" +::: info 💡 遇到问题怎么办? +如果 AI 代码里数据还没获取到页面就渲染了,告诉 AI:"数据还没加载完就开始渲染了,需要添加 loading 状态,等数据到了再渲染" ::: ---- - -## 5. 实战篇:像老手一样读懂和调试代码 - -### 5.1 模块:import 和 export +### 4.6 模块:import 和 export AI 生成的 React/Vue 代码第一行几乎都是 `import`。 @@ -839,65 +736,39 @@ export default function formatDate(date) { } ``` -**npm 包 = 别人写好的工具,你可以直接安装使用** +**npm 包 = 别人写好的工具,安装后就能用** ```javascript -// 安装包 -// npm install lodash - +// 安装包:npm install lodash // 使用包 import _ from 'lodash' ``` -这一节放在实战篇而不是语法篇,因为读者在前 4 章建立了足够基础后,这里只是"识别"。 - -**Vibecoding 提示:** +::: info 💡 识别技巧 - 看到 `import` → 从别的文件引入功能 - 看到 `export` → 把功能暴露给别人用 - 看到 `from 'react'` → 从 React 包引入 - 看到 `from './utils'` → 从本地文件引入 +::: -### 5.2 拿到 AI 代码后的阅读策略 +--- -**第一步:看整体结构** +## 5. 实战篇:读懂代码、看懂报错、精准描述 -有几个函数?分别叫什么名字?大致做什么? +::: tip 🤔 核心问题 +**前面学了这么多语法,实际拿到 AI 代码时怎么用?** 怎么快速读懂代码?遇到报错怎么办?怎么让 AI 准确地帮你改代码? +::: -```javascript -// 一眼看出:三个函数 -function loadData() { } // 加载数据 -function renderList() { } // 渲染列表 -function handleClick() { } // 处理点击 -``` +### 5.1 拿到 AI 代码后怎么读 -**第二步:找入口** +**四步法:** -哪里是程序开始执行的地方?事件监听绑在了哪些元素上? - -```javascript -// 入口点 -document.addEventListener('DOMContentLoaded', () => { - loadData() // 程序从这里开始 -}) - -button.addEventListener('click', handleClick) -``` - -**第三步:追踪数据流** - -数据从哪里来?经过了什么变换?最终渲染到了哪里? - -```javascript -async function loadData() { - const data = await fetch('/api/todos') // 数据从服务器来 - const todos = await data.json() // 解析成 JSON - renderList(todos) // 渲染到页面 -} -``` - -**第四步:看细节逻辑** - -某个具体函数里面是怎么处理的? +| 步骤 | 看什么 | 示例 | +|------|--------|------| +| **第一步:看整体结构** | 有几个函数?分别做什么? | `loadData()` 加载数据,`renderList()` 渲染列表 | +| **第二步:找入口** | 程序从哪里开始执行? | `addEventListener('click', ...)` 点击时开始 | +| **第三步:追踪数据流** | 数据从哪里来?到哪里去? | 从 API 获取 → 解析 → 渲染到页面 | +| **第四步:看细节逻辑** | 具体函数里怎么处理的? | 循环、判断、计算 | **用第 1 章的代码示例做一次完整的"阅读演示":** @@ -918,23 +789,20 @@ async function loadData() { // 这个公式的意思:每次 +1,但不超过数组长度(循环) ``` -### 5.3 常见报错速查与应对 +### 5.2 常见报错速查 -| 报错 | 大白话翻译 | 怎么跟 AI 说 | -|------|-----------|------------| +| 报错 | 大白话解释 | 怎么跟 AI 说 | +|------|-----------|-------------| | `TypeError: Cannot read properties of undefined` | 你想从一个不存在的东西上取值 | "第 X 行报错,某某变量是 undefined,检查它的赋值逻辑" | | `ReferenceError: xxx is not defined` | 用了一个没有声明过的变量名 | "变量 xxx 没有定义,是不是拼写错了或者忘了导入" | | `TypeError: xxx is not a function` | 把一个不是函数的东西当函数调用了 | "xxx 不是函数,检查一下它的类型和来源" | | `SyntaxError: Unexpected token` | 语法写错了(括号不匹配、少了逗号等) | "第 X 行语法错误,检查括号和标点" | | `CORS error` | 浏览器阻止了跨域请求 | "遇到 CORS 错误,需要配置跨域资源共享" | | `404 Not Found` | 请求的资源不存在 | "API 返回 404,检查接口地址是否正确" | -| `500 Internal Server Error` | 服务器出错了 | "服务器返回 500,需要检查后端代码" | -### 5.4 如何用精准的语言让 AI 改代码 +### 5.3 如何精准描述问题 -这是"3-5 年经验 sense"的核心体现:**描述问题的精准度**。 - -**6-8 组对比示例:** +新手和熟练开发者的差距,往往就体现在**描述问题的精准度**上。 | ❌ 差的描述 | ✅ 好的描述 | |-----------|-----------| @@ -943,9 +811,6 @@ async function loadData() { | "数据显示不出来" | "fetch 请求返回了数据(控制台能看到),但页面没有重新渲染" | | "加一个功能" | "在用户列表页面添加一个搜索框,输入时实时过滤列表,按 name 字段模糊匹配" | | "点击没反应" | "点击按钮时控制台报错 'Cannot read property of undefined',错误在第 X 行" | -| "布局乱了" | "在小屏幕上,导航栏和内容区域重叠了,需要调整响应式布局" | -| "太慢了" | "加载 100 条数据时页面卡顿 2 秒,需要做虚拟滚动或分页" | -| "我想做个登录功能" | "实现一个登录表单,包含邮箱和密码输入框,点击登录后调用 /api/login 接口,成功后保存 token 并跳转到首页" | **一个实战练习:** @@ -962,66 +827,31 @@ function deleteTodo(index) { **✅ 好的描述:** "点击删除按钮时,删除的不是当前项而是最后一项。代码里用了 splice(index, 1),但 index 可能不正确。需要改成用每个事项的唯一 id 来匹配删除。" -### 5.5 你的下一步:概念地图 +### 5.4 你现在应该能识别的代码 -**你现在应该能做到:** +- 看到 `const/let` → 知道变量能不能重新赋值 +- 看到 `{}` → 对象 / 看到 `[]` → 数组 +- 看到 `{...obj}` 或 `[...arr]` → 在创建副本 +- 看到 `function` 或 `=>` → 定义了一段可重复执行的操作 +- 看到 `if/else` 或 `? :` → 代码在做判断 +- 看到 `.map()` / `.filter()` → 在变换或筛选数组 +- 看到 `document.querySelector` → 在查找网页元素 +- 看到 `addEventListener` → 在监听用户操作 +- 看到 `async/await` → 在等待耗时操作 +- 看到 `import/export` → 在引入或导出模块 +- 遇到报错 → 能读懂大意并精准描述给 AI -✅ 看到 `const/let` → 知道变量能不能重新赋值 -✅ 看到 `{}` → 对象 / 看到 `[]` → 数组 -✅ 看到 `{...obj}` 或 `[...arr]` → 在创建副本 -✅ 看到 `function` 或 `=>` → 定义了一段可重复执行的操作 -✅ 看到 `if/else` 或 `? :` → 代码在做判断 -✅ 看到 `.map()` / `.filter()` → 在变换或筛选数组 -✅ 看到 `document.querySelector` → 在查找网页元素 -✅ 看到 `addEventListener` → 在监听用户操作 -✅ 看到 `async/await` → 在等待耗时操作 -✅ 看到 `import/export` → 在引入或导出模块 -✅ 遇到报错 → 能读懂大意并精准描述给 AI - -**更深层的理解:** - -如果你读完了每章的"深入"部分,你还建立了这些心智模型: +**如果你认真读了每章的"深入"部分,你还掌握了这些核心概念:** - **值 vs 引用**:基本类型复制值,对象/数组复制的是地址 - **作用域与闭包**:函数能"记住"它诞生时周围的变量 - **this 的本质**:取决于函数被谁调用,而不是写在哪里 - **事件循环**:JS 是单线程的,靠任务队列实现"不阻塞" -这些是区分"能用"和"真懂"的分水岭。 +这些概念会帮你更快定位问题。 -在你的 vibecoding 旅程中,它们会一次又一次帮你快速定位问题。 - -**进阶概念地图:** - -- **TypeScript** → 给 JavaScript 加类型检查 -- **React 状态管理** → useState、useReducer、Zustand -- **Vue 响应式系统** → ref、reactive、computed -- **API 设计** → REST、GraphQL -- **构建工具** → Vite、Webpack -- **性能优化** → 防抖节流、虚拟滚动、懒加载 -- **测试** → 单元测试、集成测试、E2E 测试 - -现在你已经有了坚实的基础,这些概念学起来会更轻松。 - -::: tip 🤖 Vibecoding 备忘 -**AI 代码里你会看到:** -- `import` / `export` → 模块导入导出 -- `try/catch` → 错误处理 -- `.then()` / `.catch()` → Promise 链式调用(旧写法) - -**遇到问题时这样跟 AI 说:** +::: info 💡 遇到问题时这样跟 AI 说 - "第 X 行报错 XXX,帮我看看是什么问题" - "这个函数的逻辑是 XXX,但结果不对,应该是 XXX" - "我想修改 XXX 功能,具体要求是 XXX" -- "这段代码有性能问题,需要优化 XXX" ::: - ---- - -**写在最后:** - -JavaScript 是一门看似简单、实则精妙的语言。 - -通过本章的学习,你已经建立了对这门语言的系统性认识。**深入理解这些概念,你不仅能更好地与 AI 协作,还能更快地学习新技术。** - -记住:**不必一次全学会,循序渐进、持续实践,你终将掌握这门语言的精髓。** diff --git a/docs/zh-cn/appendix/3-browser-and-frontend/javascript-runtime.md b/docs/zh-cn/appendix/3-browser-and-frontend/javascript-runtime.md index c301c92..a32a886 100644 --- a/docs/zh-cn/appendix/3-browser-and-frontend/javascript-runtime.md +++ b/docs/zh-cn/appendix/3-browser-and-frontend/javascript-runtime.md @@ -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 的环境差异 + + + +::: 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('

    Hello World

    ') +}) + +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` | +| **模块加载** | `