From 9c1a395962b14dbb1c0166b9c72475235da98162 Mon Sep 17 00:00:00 2001 From: sanbuphy Date: Sun, 15 Feb 2026 16:23:15 +0800 Subject: [PATCH] feat(docs): add JavaScript introduction components and content --- .../appendix/javascript-intro/AsyncDemo.vue | 1044 +++++++++++++++++ .../appendix/javascript-intro/ClosureDemo.vue | 766 ++++++++++++ .../javascript-intro/DataTypeDemo.vue | 620 ++++++++++ .../javascript-intro/PrototypeDemo.vue | 827 +++++++++++++ .../javascript-intro/ThisContextDemo.vue | 899 ++++++++++++++ .../javascript-intro/VariableScopeDemo.vue | 366 ++++++ docs/.vitepress/theme/index.js | 16 + .../3-browser-and-frontend/browser-as-os.md | 75 ++ .../javascript-deep-dive.md | 632 +++++++++- 9 files changed, 5243 insertions(+), 2 deletions(-) create mode 100644 docs/.vitepress/theme/components/appendix/javascript-intro/AsyncDemo.vue create mode 100644 docs/.vitepress/theme/components/appendix/javascript-intro/ClosureDemo.vue create mode 100644 docs/.vitepress/theme/components/appendix/javascript-intro/DataTypeDemo.vue create mode 100644 docs/.vitepress/theme/components/appendix/javascript-intro/PrototypeDemo.vue create mode 100644 docs/.vitepress/theme/components/appendix/javascript-intro/ThisContextDemo.vue create mode 100644 docs/.vitepress/theme/components/appendix/javascript-intro/VariableScopeDemo.vue diff --git a/docs/.vitepress/theme/components/appendix/javascript-intro/AsyncDemo.vue b/docs/.vitepress/theme/components/appendix/javascript-intro/AsyncDemo.vue new file mode 100644 index 0000000..b93a837 --- /dev/null +++ b/docs/.vitepress/theme/components/appendix/javascript-intro/AsyncDemo.vue @@ -0,0 +1,1044 @@ + + + + + diff --git a/docs/.vitepress/theme/components/appendix/javascript-intro/ClosureDemo.vue b/docs/.vitepress/theme/components/appendix/javascript-intro/ClosureDemo.vue new file mode 100644 index 0000000..1b1335f --- /dev/null +++ b/docs/.vitepress/theme/components/appendix/javascript-intro/ClosureDemo.vue @@ -0,0 +1,766 @@ + + + + + diff --git a/docs/.vitepress/theme/components/appendix/javascript-intro/DataTypeDemo.vue b/docs/.vitepress/theme/components/appendix/javascript-intro/DataTypeDemo.vue new file mode 100644 index 0000000..1388018 --- /dev/null +++ b/docs/.vitepress/theme/components/appendix/javascript-intro/DataTypeDemo.vue @@ -0,0 +1,620 @@ + + + + + diff --git a/docs/.vitepress/theme/components/appendix/javascript-intro/PrototypeDemo.vue b/docs/.vitepress/theme/components/appendix/javascript-intro/PrototypeDemo.vue new file mode 100644 index 0000000..48c656e --- /dev/null +++ b/docs/.vitepress/theme/components/appendix/javascript-intro/PrototypeDemo.vue @@ -0,0 +1,827 @@ + + + + + diff --git a/docs/.vitepress/theme/components/appendix/javascript-intro/ThisContextDemo.vue b/docs/.vitepress/theme/components/appendix/javascript-intro/ThisContextDemo.vue new file mode 100644 index 0000000..d65c6de --- /dev/null +++ b/docs/.vitepress/theme/components/appendix/javascript-intro/ThisContextDemo.vue @@ -0,0 +1,899 @@ + + + + + diff --git a/docs/.vitepress/theme/components/appendix/javascript-intro/VariableScopeDemo.vue b/docs/.vitepress/theme/components/appendix/javascript-intro/VariableScopeDemo.vue new file mode 100644 index 0000000..bfbdafe --- /dev/null +++ b/docs/.vitepress/theme/components/appendix/javascript-intro/VariableScopeDemo.vue @@ -0,0 +1,366 @@ + + + + + diff --git a/docs/.vitepress/theme/index.js b/docs/.vitepress/theme/index.js index 13d0a4e..3299267 100644 --- a/docs/.vitepress/theme/index.js +++ b/docs/.vitepress/theme/index.js @@ -479,6 +479,14 @@ import ResourceAnalogy from './components/appendix/api-design/ResourceAnalogy.vu import RequestStructureDemo from './components/appendix/api-design/RequestStructureDemo.vue' import ResponseStructureDemo from './components/appendix/api-design/ResponseStructureDemo.vue' +// JavaScript Intro Components +import VariableScopeDemo from './components/appendix/javascript-intro/VariableScopeDemo.vue' +import DataTypeDemo from './components/appendix/javascript-intro/DataTypeDemo.vue' +import ClosureDemo from './components/appendix/javascript-intro/ClosureDemo.vue' +import ThisContextDemo from './components/appendix/javascript-intro/ThisContextDemo.vue' +import PrototypeDemo from './components/appendix/javascript-intro/PrototypeDemo.vue' +import AsyncDemo from './components/appendix/javascript-intro/AsyncDemo.vue' + export default { extends: DefaultTheme, Layout, @@ -965,6 +973,14 @@ export default { app.component('ReliabilityDemo', ReliabilityDemo) app.component('IdempotenceDemo', IdempotenceDemo) app.component('MQComparisonDemo', MQComparisonDemo) + + // JavaScript Intro Components Registration + app.component('VariableScopeDemo', VariableScopeDemo) + app.component('DataTypeDemo', DataTypeDemo) + app.component('ClosureDemo', ClosureDemo) + app.component('ThisContextDemo', ThisContextDemo) + app.component('PrototypeDemo', PrototypeDemo) + app.component('AsyncDemo', AsyncDemo) }, setup() { const route = useRoute() 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 61b9af6..e26d015 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 @@ -304,6 +304,81 @@ DOM 树 + CSSOM 树 = **渲染树 (Render Tree)**。 --- +## 5.5 网页是怎么"生成"的?静态网站 vs 动态网站 + +前面我们讲的都是浏览器如何"拆开包裹"——把服务器发来的 HTML/CSS/JS 渲染成页面。但你有没有想过一个问题:**服务器上那个 HTML 文件是怎么来的?** + +答案是:**有两种方式**,这就是静态网站和动态网站的区别。 + +### 静态网站:提前做好、直接给你 + +想象你去超市买饼干。货架上的饼干都是工厂已经生产好的,你直接拿走就行,不需要等。 + +**静态网站**就是这样的"成品"——网页在服务器上已经准备好了,你访问时服务器直接把现成的 HTML 文件发给你,不做任何额外处理。 + +**特点:** +- ✅ 访问速度快(服务器直接发文件,不用计算) +- ✅ 制作简单(写好 HTML 就能用) +- ✅ 承载力强(可以用 CDN 分发,多少人访问都不怕) +- ❌ 内容难更新(想改内容就要重新生成文件) + +**常见例子:** 公司介绍页、产品文档、帮助中心、个人博客 + +### 动态网站:现点现做、每次不同 + +这次想象你去餐厅点餐。厨师根据你的订单现做,你点宫保鸡丁不会给你上糖醋里脊。 + +**动态网站**就是你访问时才"现场制作"的页面——服务器收到你的请求后,去数据库查资料、计算数据,然后生成一个全新的 HTML 发给你。 + +**特点:** +- ✅ 内容实时(购物车显示最新库存、新闻随时更新) +- ✅ 因人而异(登录后看到你的个人信息) +- ✅ 功能强大(搜索、评论、推荐、支付都能实现) +- ❌ 访问速度慢(服务器需要时间计算) +- ❌ 服务器压力大(同时很多人访问要排队) + +**常见例子:** 淘宝、微博、在线银行、在线文档 + +**需要服务器吗?** 动态网站确实需要某种"后端"来生成内容,但形式多样: +- **传统服务器**:自己买/租服务器(阿里云 ECS、AWS EC2) +- **Serverless**:不用管服务器,云厂商帮你运行代码(AWS Lambda、阿里云函数计算、Cloudflare Workers) +- **调用第三方 API**:支付用 Stripe、天气用气象局 API,自己不写后端代码 + +::: tip 💡 静动态结合 +现在很多网站是"混合"的:网页主体是静态的,但某些部分(比如评论区、搜索框)是动态加载的。JavaScript 可以在页面加载后调用 API 获取数据,实现"静态页面 + 动态功能"。 +::: + +### 📊 静态 vs 动态,一对比就清楚 + +| | 静态网站 | 动态网站 | +|---|---------|---------| +| **怎么来的** | 提前做好,存服务器上 | 访问时现做 | +| **像什么** | 超市货架上的商品 | 餐厅现点的菜 | +| **速度** | 快 | 慢(需要计算) | +| **能改内容吗** | 难(要重新生成) | 容易(后台直接改) | +| **适合做什么** | 展示型内容(介绍页、文档) | 交互型应用(购物、社交) | +| **典型例子** | 公司官网、帮助文档 | 淘宝、微信、在线银行 | + +### 🤔 常见疑问 + +**Q: 静态网站是不是不能用 JavaScript?** + +当然不是!轮播图、折叠菜单、表单验证这些交互功能,静态网站都能用 JavaScript 实现。我们说的"静态""动态",是指**页面内容是不是提前准备好的**,跟有没有交互功能是两回事。 + +**Q: 动态网站一定要自己买服务器吗?** + +不一定。除了传统服务器,你还可以用 Serverless(云函数)、或者直接调用第三方 API。现在的趋势是"能不动服务器就不动"——用静态网站 + JavaScript 调用 API 的方式,既快又省成本。 + +::: tip 💡 重要提示 +无论静态网站还是动态网站,**浏览器渲染的原理都是一样的**!服务器发来的是什么,浏览器就渲染什么。区别只在于: +- 静态网站:服务器发来的是"成品" +- 动态网站:服务器发来的是"现做的" + +作为前端开发者,你主要关注的是浏览器如何处理收到的内容,而不是服务器怎么生成的。 +::: + +--- + ## 6. 总结:一次完整的"网购"之旅 让我们回顾整个旅程: 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 e2df853..deea5de 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,3 +1,631 @@ -# JavaScript 语言深入 +# JavaScript 语言由浅入深 +::: tip 前言 +**现代网页为什么可以像应用程序一样交互?** 淘宝可以筛选商品、地图可以缩放平移、文档可以实时保存——这些功能背后的"控制逻辑"是什么?HTML 定义了网页的结构,CSS 负责视觉呈现,而 JavaScript 则是让网页"动起来"的编程语言。本章将带你理解 JavaScript 的核心工作机制,掌握实现网页交互的关键技术。 +::: -> 待实现 +--- + +## 1. JavaScript 是什么? + +### 1.1 从"只能看"到"可以用":网页的进化 + +早期的网页就像一本**电子杂志**——你只能看,不能改。内容是固定的,你点击什么都不会改变。 + +但现代网页完全不同了。它们更像**桌面软件**: +- 在线文档可以像 Word 一样编辑 +- 地图网站可以像 GPS 一样导航 +- 聊天应用可以像微信一样实时收发消息 + +**这种转变的核心技术就是 JavaScript**——它让网页从"展示信息"变成了"可以交互的工具"。 + +### 1.2 JavaScript 在网页中的角色 + +想象你在经营一家**网上商店**: + +| 层次 | 角色 | 做什么 | +|------|------|--------| +| **HTML** | 店铺的"货架" | 摆放商品、标注价格 | +| **CSS** | 店铺的"装修" | 美化布局、调整颜色 | +| **JavaScript** | 店铺的"收银系统" | 处理订单、计算总价、更新库存 | + +**没有 JavaScript,网页就像一个没有收银员的商店**——顾客只能看商品,但无法完成购买。 + +::: tip 💡 三者如何配合 +HTML 搭建结构,CSS 负责美观,JavaScript 处理交互。三者缺一不可,但 JavaScript 是让网页"能用"的关键。 + +打个比方:HTML 是房子的框架,CSS 是装修风格,JavaScript 是让房子"智能"的电器系统。没有电器,房子仍然可以住人,但不会有现代生活的便利。 +::: + +### 1.2 一个真实的成长故事 + +::: warning 从踩坑到顿悟 +小王是一名前端工程师,刚入职时只会简单的 DOM 操作。 + +有一次,他遇到一个 bug:修改数组中的某个元素后,页面上显示的却是旧数据。他花了整整一天调试,最后才发现 —— 原来数组是引用类型,直接修改元素不会触发 Vue 的响应式更新。 + +**这个坑让他明白了一个道理:** 不理解 JavaScript 的核心概念(比如引用类型、响应式原理),写出的代码就像在雷区跳舞,随时可能踩中看不见的坑。 + +从那以后,他开始系统地学习 JavaScript 的底层原理。半年后,他不仅能快速定位问题,还能写出性能更好的代码,甚至能帮团队解决复杂的技术难题。 + +**深入理解一门语言,是成为高级工程师的必经之路。** +::: + +::: info 💡 核心启示 +JavaScript 看起来简单,实则精妙。它的设计哲学和实现机制,值得每个前端开发者深入理解。掌握了这些,你不仅能写出更好的代码,还能更快地学习新技术(因为很多框架都是基于 JavaScript 的特性构建的)。 +::: + +--- + +## 2. JavaScript 的核心概念全景 + +::: tip 🗺️ 学习路线图 +JavaScript 的知识点很多,但我们不必一次全学完。按照下面这个顺序,逐步掌握核心概念: + +1. **基础语法** → 变量、数据类型、操作符 +2. **函数与作用域** → 函数声明、闭包、this +3. **对象与原型** → 对象创建、原型链、继承 +4. **异步编程** → 回调、Promise、async/await +5. **ES6+ 新特性** → let/const、箭头函数、解构等 + +**本章节将聚焦于最核心、最容易出错的部分**,帮你建立坚实的知识体系。 +::: + +JavaScript 的核心概念可以分为以下几个层次: + +| 层次 | 包含内容 | 学习重点 | 实际应用 | +|------|---------|---------|---------| +| **基础语法** | 变量、数据类型、操作符 | let/const/var、原始类型 vs 引用类型 | 避免常见的类型错误和变量泄漏 | +| **函数与作用域** | 函数声明、闭包、this | 作用域链、闭包机制、this 指向 | 理解函数的工作方式和数据私有化 | +| **对象与原型** | 对象创建、原型链、继承 | 原型链查找机制、继承方式 | 掌握面向对象编程和框架原理 | +| **异步编程** | 回调、Promise、async/await | 事件循环、Promise 链式调用 | 处理网络请求、动画等异步操作 | +| **ES6+ 新特性** | 箭头函数、解构、模块 | 新语法的使用场景 | 写出更简洁、更现代的代码 | + +::: tip 💡 从表格中你能看到什么? +**基础语法** 是所有其他知识的基础,必须掌握。 + +**函数与作用域**、**对象与原型** 是 JavaScript 的核心机制,理解了它们,你就理解了这门语言的灵魂。 + +**异步编程** 是 JavaScript 的特色,因为它是单线程的,必须依赖异步来处理耗时操作。 + +**ES6+ 新特性** 让代码更简洁,但本质都是基于前面提到的核心机制。 + +**学习建议:** 先打好基础(变量、类型、函数),再深入理解机制(作用域、原型、异步),最后学习新特性(ES6+)。这样循序渐进,不会感到混乱。 +::: + +--- + +## 3. 变量与数据类型 + +### 3.1 变量声明:let、const、var 的区别 + +在 JavaScript 中,有三种声明变量的方式:`var`、`let`、`const`。它们的区别看似简单,实则影响深远。 + +::: details 🤔 为什么有三种方式? +这是 JavaScript 的历史遗留问题。 + +- **`var`** 是 ES5 时代的产物,有"变量提升"和"没有块级作用域"的问题 +- **`let`** 和 **`const`** 是 ES6 新增的,解决了 `var` 的问题,更安全 + +现在推荐**始终使用 `const`**,需要重新赋值时才用 `let`,完全不要用 `var`。 +::: + +**三个关键词的区别:** + +| 特性 | var | let | const | +|------|-----|-----|-------| +| **作用域** | 函数作用域 | 块级作用域 | 块级作用域 | +| **重复声明** | ✅ 可以 | ❌ 不可以 | ❌ 不可以 | +| **重新赋值** | ✅ 可以 | ✅ 可以 | ❌ 不可以 | +| **变量提升** | ✅ 提升 | ✅ 提升(但不可访问) | ✅ 提升(但不可访问) | +| **全局对象属性** | ✅ 是 | ❌ 否 | ❌ 否 | +| **推荐使用** | ❌ 不推荐 | ✅ 需要重新赋值时 | ✅ 默认首选 | + +::: tip 💡 如何选择? +记住这个简单规则: + +- **默认用 `const`**:因为大多数变量不需要重新赋值,使用 `const` 更安全(防止意外修改) +- **需要重新赋值时用 `let`**:比如循环计数器、累加器 +- **永远不要用 `var`**:除非你在维护老项目 + +**示例:** +```javascript +// ✅ 好的做法 +const name = "张三" // 名字不会变 +const age = 25 // 年龄不会变 +let score = 0 // 分数会变化,用 let + +score += 10 // ✓ 正确 +// name = "李四" // ✗ 报错!const 不能重新赋值 + +// ❌ 不好的做法 +var name = "张三" // 不要用 var +``` +::: + +👇 **动手试试看**: +下面这个演示展示了 let、const、var 的区别: + + + +### 3.2 数据类型:原始类型 vs 引用类型 + +JavaScript 有两种数据类型:**原始类型**(Primitive)和**引用类型**(Reference)。理解它们的区别,能帮你避免很多莫名其妙的 bug。 + +::: tip 🎯 什么是"类型"? +简单来说,类型就是数据的"种类"。 + +- **原始类型**是最基本的数据,比如数字、字符串、布尔值 +- **引用类型**是更复杂的数据结构,比如对象、数组、函数 + +它们的区别在于存储方式:原始类型存储"实际的值",引用类型存储"指向数据的地址"。 +::: + +**七种原始类型:** + +| 类型 | 说明 | 示例 | +|------|------|------| +| **Number** | 数字(整数和小数) | `42`, `3.14`, `NaN` | +| **String** | 字符串(文本) | `"hello"`, `'你好'` | +| **Boolean** | 布尔值(真/假) | `true`, `false` | +| **Undefined** | 未定义 | `let x; // x 是 undefined` | +| **Null** | 空值 | `let x = null;` | +| **Symbol** | 独一无二的值(ES6) | `Symbol("id")` | +| **BigInt** | 大整数(ES2020) | `9007199254740991n` | + +**引用类型:** + +| 类型 | 说明 | 示例 | +|------|------|------| +| **Object** | 对象(键值对) | `{name: "张三", age: 25}` | +| **Array** | 数组(有序列表) | `[1, 2, 3]` | +| **Function** | 函数(可执行的代码) | `function() {}` | +| **Date** | 日期对象 | `new Date()` | +| **RegExp** | 正则表达式 | `/^test$/` | + +::: details 🔍 原始类型 vs 引用类型的区别 +这是最容易踩坑的地方! + +**赋值时的区别:** +```javascript +// 原始类型:复制值 +let a = 10 +let b = a // b 得到 a 的副本 +b = 20 +console.log(a) // 10(a 不受影响) + +// 引用类型:复制引用(地址) +let obj1 = {x: 10} +let obj2 = obj1 // obj2 指向同一个对象 +obj2.x = 20 // 修改 obj2 会影响 obj1 +console.log(obj1.x) // 20(obj1 也变了!) +``` + +**为什么引用类型会这样?** +因为引用类型存储的是"地址",而不是"实际的值"。当你把 `obj1` 赋值给 `obj2` 时,只是复制了地址,两个变量指向内存中的同一个对象。 + +**实际影响:** +- 函数参数传递时,引用类型可能会被修改 +- 比较两个对象时,比较的是地址,不是内容 +- 深拷贝 vs 浅拷贝的问题 + +**解决方法:** +- 如果需要复制对象,使用"深拷贝"(`JSON.parse(JSON.stringify(obj))` 或 `structuredClone(obj)`) +- 如果只需要复制第一层,使用"浅拷贝"(`Object.assign({}, obj)` 或 `{...obj}`) +::: + +👇 **动手试试看**: +下面这个演示展示了原始类型和引用类型的区别: + + + +--- + +## 4. 函数与闭包 + +### 4.1 函数是"一等公民" + +在 JavaScript 中,函数是"一等公民"(First-Class Citizen)。这意味着函数可以: +- 被赋值给变量 +- 作为参数传递给其他函数 +- 作为返回值从函数中返回 +- 存储在数据结构中(如数组、对象) + +::: tip 🤔 什么是"一等公民"? +"一等公民"是编程语言的一个术语,意思是某种东西可以像其他数据一样被使用。 + +在 JavaScript 中,数字、字符串是"一等公民",函数也是。这让 JavaScript 非常灵活。 + +**在其他语言中(如 Java 8 之前),函数不是一等公民,你必须用对象或接口来包装它。** +::: + +**函数的声明方式:** + +| 方式 | 语法 | 特点 | 使用场景 | +|------|------|------|---------| +| **函数声明** | `function name() {}` | 会提升,可被提前调用 | 普通函数 | +| **函数表达式** | `const name = function() {}` | 不会提升 | 需要条件性创建函数 | +| **箭头函数** | `const name = () => {}` | 没有 `this`,更简洁 | 回调函数、简短函数 | + +::: tip 💡 如何选择? +- **普通函数**:用函数声明或函数表达式 +- **回调函数**:优先用箭头函数(更简洁) +- **需要 `this` 指向调用者**:不要用箭头函数(箭头函数没有自己的 `this`) + +**示例:** +```javascript +// 函数声明 +function greet(name) { + return "Hello " + name +} + +// 函数表达式 +const greet = function(name) { + return "Hello " + name +} + +// 箭头函数(最简洁) +const greet = name => "Hello " + name +``` +::: + +### 4.2 闭包:函数"记住"了它的出生环境 + +闭包(Closure)是 JavaScript 中最重要、也最容易被误解的概念之一。 + +::: tip 🎯 什么是闭包? +**简单来说:** 闭包是函数和它的词法环境的组合。 + +**更直白地说:** 内部函数可以访问外部函数的变量,即使外部函数已经执行完毕。 + +**打个比方:** +你出门时背了个背包,把当时看到的东西装进包里。即使你走了很远的路,依然可以从包里拿出当时装的东西 —— 这个背包就是"闭包"。 +::: + +**闭包的实际应用:** + +1. **数据私有化**:模拟私有变量 +```javascript +function createCounter() { + let count = 0 // 私有变量,外部无法直接访问 + return function() { + count++ + return count + } +} + +const counter = createCounter() +console.log(counter()) // 1 +console.log(counter()) // 2 +// count 变量无法被外部直接修改,只能通过返回的函数操作 +``` + +2. **函数工厂**:批量创建相似的函数 +```javascript +function makeMultiplier(times) { + return function(n) { + return n * times + } +} + +const double = makeMultiplier(2) +const triple = makeMultiplier(3) + +console.log(double(5)) // 10 +console.log(triple(5)) // 15 +``` + +3. **模块化**:在 ES6 模块出现之前,常用闭包实现模块 + +::: warning 闭包的坑 +闭包虽然强大,但使用不当会导致内存泄漏。 + +**问题示例:** +```javascript +function createHandlers() { + const handlers = [] + for (var i = 0; i < 3; i++) { + handlers.push(function() { + console.log(i) + }) + } + return handlers +} + +const handlers = createHandlers() +handlers[0]() // 输出 3(不是 0!) +handlers[1]() // 输出 3(不是 1!) +handlers[2]() // 输出 3(不是 2!) +``` + +**原因:** `var` 没有块级作用域,所有闭包共享同一个 `i` 变量。 + +**解决方法:** +1. 用 `let` 代替 `var`(推荐) +2. 用 IIFE(立即执行函数)创建独立作用域 +```javascript +// 方法 1:用 let +for (let i = 0; i < 3; i++) { // ← 用 let + handlers.push(function() { + console.log(i) + }) +} + +// 方法 2:用 IIFE +for (var i = 0; i < 3; i++) { + (function(j) { // ← 用 IIFE 捕获当前值 + handlers.push(function() { + console.log(j) + }) + })(i) +} +``` +::: + +👇 **动手试试看**: +下面这个演示展示了闭包的工作原理: + + + +--- + +## 5. this 与执行上下文 + +### 5.1 this 是什么? + +`this` 是 JavaScript 中最让人困惑的关键字之一。它的值取决于**函数如何被调用**,而不是**函数在哪里定义**。 + +::: tip 🎯 核心规则 +**记住这句话:** `this` 的值是在函数调用时确定的,不是定义时确定的。 + +**判断 `this` 指向的四个规则:** +1. **默认绑定**:`fn()` → `this` 指向全局对象(浏览器中是 `window`) +2. **隐式绑定**:`obj.fn()` → `this` 指向 `obj` +3. **显式绑定**:`fn.call(obj)` → `this` 指向 `obj` +4. **new 绑定**:`new Fn()` → `this` 指向新创建的对象 +::: + +**this 指向规则速查表:** + +| 调用方式 | this 指向 | 示例 | +|---------|----------|------| +| **普通函数调用** | 全局对象(非严格模式)或 `undefined`(严格模式) | `fn()` | +| **对象方法调用** | 调用方法的对象 | `obj.method()` | +| **构造函数调用** | 新创建的对象 | `new Constructor()` | +| **call/apply/bind** | 显式指定的对象 | `fn.call(obj)` | +| **箭头函数** | 外层作用域的 `this` | `() => {}` | + +::: tip 💡 常见误区 +**误区 1:** "箭头函数的 `this` 指向定义它的对象" +- ❌ 错误:箭头函数没有自己的 `this` +- ✅ 正确:箭头函数的 `this` 继承外层作用域 + +**误区 2:** "`this` 总是指向函数本身" +- ❌ 错误:`this` 不是指向函数本身 +- ✅ 正确:`this` 指向调用函数的对象 + +**误区 3:** "嵌套函数的 `this` 不变" +- ❌ 错误:嵌套的普通函数有自己的 `this` +- ✅ 正确:用箭头函数可以继承外层 `this` +::: + +👇 **动手试试看**: +下面这个演示展示了不同场景下 `this` 的指向: + + + +--- + +## 6. 原型与继承 + +### 6.1 原型链:JavaScript 的继承机制 + +JavaScript 没有"类"(ES6 之前),它通过"原型链"(Prototype Chain)实现继承。 + +::: tip 🎯 什么是原型链? +每个对象都有一个"原型"(`__proto__`),当我们访问对象的属性时: +1. 先在对象自身查找 +2. 找不到就去它的原型对象上查找 +3. 还找不到就去原型的原型查找 +4. 一直查到 `Object.prototype`(最顶层的原型) +5. 如果还找不到,返回 `undefined` + +这条查找链条就是"原型链"。 +::: + +**原型链示例:** +```javascript +function Person(name) { + this.name = name +} + +Person.prototype.greet = function() { + return "Hello, I'm " + this.name +} + +const p = new Person("张三") + +// 访问 p.greet() 时的查找过程: +// 1. 在 p 自身上查找 → 没有 greet 方法 +// 2. 在 p.__proto__(即 Person.prototype)上查找 → 找到了! +// 3. 执行 greet 方法 + +console.log(p.greet()) // "Hello, I'm 张三" +``` + +**原型链的关系:** +``` +实例对象 (p) + __proto__ → Person.prototype + __proto__ → Object.prototype + __proto__ → null +``` + +::: tip 💡 class 语法 +ES6 引入了 `class` 语法,让面向对象编程更接近传统语言。但记住:**`class` 只是语法糖,底层仍然是原型链**。 + +**示例:** +```javascript +// ES6 class 语法 +class Person { + constructor(name) { + this.name = name + } + + greet() { + return "Hello, I'm " + this.name + } +} + +// 等价于 ES5 的写法 +function Person(name) { + this.name = name +} +Person.prototype.greet = function() { + return "Hello, I'm " + this.name +} +``` +::: + +👇 **动手试试看**: +下面这个演示展示了原型链的工作原理: + + + +--- + +## 7. 异步编程 + +### 7.1 为什么需要异步? + +JavaScript 是**单线程**的,这意味着它同一时间只能做一件事。如果所有操作都是同步的,那么耗时的操作(如网络请求)会阻塞整个程序,页面就会"卡死"。 + +::: tip 🎯 同步 vs 异步 +**同步(Synchronous):** 按顺序执行,前一个任务完成后才开始下一个任务。 +- 优点:简单直观 +- 缺点:耗时操作会阻塞 + +**异步(Asynchronous):** 不等待耗时操作完成,先去做其他事,操作完成后再回来处理。 +- 优点:不阻塞,性能更好 +- 缺点:代码更复杂,需要处理回调 +::: + +**实际对比:** +```javascript +// 同步方式(会阻塞) +console.log("1") +console.log("2") // 等上面执行完 +console.log("3") +// 输出:1, 2, 3 + +// 异步方式(不阻塞) +console.log("1") +setTimeout(() => console.log("2"), 1000) // 1秒后执行 +console.log("3") +// 输出:1, 3, 2(注意顺序!) +``` + +### 7.2 异步编程的演进 + +JavaScript 的异步编程经历了三个阶段: + +| 阶段 | 方式 | 优点 | 缺点 | +|------|------|------|------| +| **回调函数** | `callback` | 简单直接 | 回调地狱 | +| **Promise** | `then/catch` | 链式调用,可读性更好 | 仍然不够简洁 | +| **async/await** | `async/await` | 像同步代码一样写异步 | 需要 Promise 支持 | + +**代码对比:** +```javascript +// 1. 回调函数(回调地狱) +getData(function(a) { + getMoreData(a, function(b) { + getMoreData(b, function(c) { + // 无限嵌套... + }) + }) +}) + +// 2. Promise 链式调用 +getData() + .then(a => getMoreData(a)) + .then(b => getMoreData(b)) + .then(c => console.log(c)) + .catch(err => console.error(err)) + +// 3. async/await(最优雅) +async function fetchData() { + try { + const a = await getData() + const b = await getMoreData(a) + const c = await getMoreData(b) + console.log(c) + } catch (err) { + console.error(err) + } +} +``` + +### 7.3 事件循环(Event Loop) + +JavaScript 如何实现异步?答案是**事件循环**。 + +::: tip 🎯 事件循环的工作原理 +JavaScript 的执行机制: + +1. **执行同步代码**(所有同步任务都在主线程执行) +2. **主线程为空时**,检查微任务队列(Microtask Queue) +3. **执行所有微任务**(Promise.then、MutationObserver) +4. **执行一个宏任务**(setTimeout、setInterval、I/O) +5. **重复步骤 2-4** + +**关键点:** +- 微任务优先级高于宏任务 +- 每次执行完一个宏任务后,都会清空所有微任务 +- setTimeout 是宏任务,Promise.then 是微任务 +::: + +**经典面试题:** +```javascript +console.log("1") + +setTimeout(() => console.log("2"), 0) // 宏任务 + +Promise.resolve().then(() => console.log("3")) // 微任务 + +console.log("4") + +// 输出顺序:1, 4, 3, 2 +// 解析: +// 1. 执行同步代码:输出 1, 4 +// 2. 执行微任务:输出 3 +// 3. 执行宏任务:输出 2 +``` + +👇 **动手试试看**: +下面这个演示展示了异步编程和事件循环: + + + +--- + +## 8. 总结 + +让我们用一张表格回顾 JavaScript 的核心概念: + +| 概念 | 一句话总结 | 关键要点 | 常见坑 | +|------|-----------|---------|--------| +| **变量声明** | 优先用 `const`,其次 `let`,不用 `var` | 块级作用域、不可变性 | var 的变量提升、作用域泄漏 | +| **数据类型** | 原始类型存值,引用类型存地址 | 七种原始类型、引用类型 | 引用类型的赋值和比较 | +| **函数** | JavaScript 的"一等公民" | 函数声明、箭头函数 | 箭头函数没有自己的 `this` | +| **闭包** | 函数"记住"了外部变量 | 数据私有化、函数工厂 | 内存泄漏、循环中的闭包 | +| **this** | 取决于函数如何被调用 | 四种绑定规则 | 嵌套函数的 `this` 丢失 | +| **原型链** | JavaScript 的继承机制 | `__proto__`、原型链查找 | 属性查找的顺序 | +| **异步编程** | 用同步的方式写异步代码 | 事件循环、微任务/宏任务 | 回调地狱、执行顺序 | + +::: info 写在最后 +JavaScript 是一门看似简单、实则精妙的语言。它的核心概念——作用域、闭包、this、原型链、异步——构成了前端开发的基础。 + +**深入理解这些概念,你不仅能写出更好的代码,还能更快地学习新技术(因为 Vue、React 等框架都是基于这些特性构建的)。** + +希望这篇文章能帮助你建立起对 JavaScript 的系统性认识。记住:**不必一次全学会,循序渐进、持续实践,你终将掌握这门语言的精髓。** +:::