2026-02-15 18:15:42 +08:00
|
|
|
|
# JavaScript 深度指南
|
|
|
|
|
|
|
2026-02-15 16:23:15 +08:00
|
|
|
|
::: tip 前言
|
2026-02-15 18:15:42 +08:00
|
|
|
|
你已经学了 HTML(网页的骨架)和 CSS(网页的皮肤)。
|
|
|
|
|
|
但光有骨架和皮肤,网页只能"看",不能"用"——
|
|
|
|
|
|
点一个按钮什么都不会发生,填一个表单提交不了。
|
|
|
|
|
|
|
|
|
|
|
|
**JavaScript 就是让网页能响应你操作的语言。**
|
|
|
|
|
|
点击按钮弹出菜单、输入搜索词显示建议、
|
|
|
|
|
|
下拉页面加载更多内容——这些全靠 JavaScript。
|
|
|
|
|
|
|
|
|
|
|
|
在 vibecoding 的工作流里,AI 会帮你写大部分 JS 代码。
|
|
|
|
|
|
你的任务不是从零手写,而是:
|
|
|
|
|
|
1. **读懂** AI 写了什么
|
|
|
|
|
|
2. **判断** 它写得对不对
|
|
|
|
|
|
3. **精准描述** 需要修改的地方
|
|
|
|
|
|
|
|
|
|
|
|
本章从最基础的概念讲起,逐步深入到专业开发者的思维方式。
|
|
|
|
|
|
读完后,你不只是能"用" JavaScript,而是能"理解"它——
|
|
|
|
|
|
这会让你在 vibecoding 中如虎添翼。
|
2026-02-15 16:23:15 +08:00
|
|
|
|
:::
|
2026-02-15 01:57:52 +08:00
|
|
|
|
|
2026-02-15 16:23:15 +08:00
|
|
|
|
---
|
|
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
## 1. JavaScript 是什么
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
### 1.1 从"只能看"到"能交互"
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
|
|
|
|
|
早期的网页就像一本**电子杂志**——你只能看,不能改。内容是固定的,你点击什么都不会改变。
|
|
|
|
|
|
|
|
|
|
|
|
但现代网页完全不同了。它们更像**桌面软件**:
|
|
|
|
|
|
- 在线文档可以像 Word 一样编辑
|
|
|
|
|
|
- 地图网站可以像 GPS 一样导航
|
|
|
|
|
|
- 聊天应用可以像微信一样实时收发消息
|
|
|
|
|
|
|
|
|
|
|
|
**这种转变的核心技术就是 JavaScript**——它让网页从"展示信息"变成了"可以交互的工具"。
|
|
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
用一句话定位:
|
|
|
|
|
|
- **HTML** 是网页的骨架(结构)
|
|
|
|
|
|
- **CSS** 是网页的皮肤(样式)
|
|
|
|
|
|
- **JavaScript** 是网页的肌肉和神经系统(行为)
|
|
|
|
|
|
|
|
|
|
|
|
### 1.2 Vibecoding 中的 JavaScript
|
|
|
|
|
|
|
|
|
|
|
|
::: warning 💡 从踩坑到顿悟
|
|
|
|
|
|
小李用 AI 做了一个待办事项应用。AI 生成的代码能添加待办、能标记完成,看起来一切正常。
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
但当他想加"删除"功能时,对 AI 说:"加一个删除功能。" AI 加了,可每次点删除,删掉的都不是他点的那一项,而是列表最后一项。
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
小李完全看不懂代码,只能反复说"删除有 bug",AI 改了好几版都不对。
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
最后他花了 10 分钟学了"数组"和"索引"的概念,看懂了代码里的 `splice(index, 1)`,然后对 AI 说:"删除时不要用数组索引来定位,改成用每个事项的唯一 id 来匹配删除。"
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
一次就改对了。
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
**这就是为什么 vibecoding 也需要读懂代码——不是为了手写,而是为了在 AI 出错时能一句话说到点子上。**
|
2026-02-15 16:23:15 +08:00
|
|
|
|
:::
|
|
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
**你的定位不是从零手写代码**,而是:
|
|
|
|
|
|
- 能看懂 AI 生成的代码在做什么
|
|
|
|
|
|
- 能判断它写得对不对
|
|
|
|
|
|
- 能用精准的语言告诉 AI 需要怎么改
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
### 1.3 从一段真实代码开始
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
让我们先看一段 AI 生成的真实代码。不要担心看不懂,我们会在后面的章节逐一讲解每个部分。
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
**场景**:让 AI 做一个"点击按钮切换背景颜色"的网页
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
```javascript
|
|
|
|
|
|
// 场景:点击按钮切换背景颜色
|
|
|
|
|
|
const colors = ['#ff6b6b', '#4ecdc4', '#45b7d1', '#96ceb4'] // ← 数组(第2章 2.2节)
|
|
|
|
|
|
let currentIndex = 0 // ← 变量(第2章 2.1节)
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
const button = document.querySelector('#changeBtn') // ← DOM 查找(第4章 4.2节)
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
button.addEventListener('click', () => { // ← 事件监听 + 箭头函数(第3章 3.2节 + 第4章 4.3节)
|
|
|
|
|
|
currentIndex = (currentIndex + 1) % colors.length // ← 运算(第2章)
|
|
|
|
|
|
document.body.style.backgroundColor = colors[currentIndex] // ← 修改样式(第4章 4.2节)
|
|
|
|
|
|
})
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**这段代码在做什么?**
|
|
|
|
|
|
- 定义了一组颜色(数组)
|
|
|
|
|
|
- 记录当前用到了第几个颜色(变量)
|
|
|
|
|
|
- 找到页面上的按钮(DOM 查找)
|
|
|
|
|
|
- 给按钮添加点击事件:每次点击就换一个背景色(事件监听)
|
|
|
|
|
|
|
|
|
|
|
|
现在你不需要理解每一行,只要有个印象即可。接下来我们会按顺序学习每个概念。
|
|
|
|
|
|
|
|
|
|
|
|
::: tip 🤖 Vibecoding 备忘
|
|
|
|
|
|
**AI 代码里你会看到:**
|
|
|
|
|
|
- `const` / `let` → 变量声明(第2章)
|
|
|
|
|
|
- `{}` / `[]` → 对象和数组(第2章)
|
|
|
|
|
|
- `function` / `=>` → 函数定义(第3章)
|
|
|
|
|
|
- `document.querySelector` → 查找网页元素(第4章)
|
|
|
|
|
|
- `addEventListener` → 监听用户操作(第4章)
|
|
|
|
|
|
- `async` / `await` → 等待耗时操作(第4章)
|
|
|
|
|
|
|
|
|
|
|
|
**遇到问题时这样跟 AI 说:**
|
|
|
|
|
|
- ✅ "第 X 行是什么意思?"
|
|
|
|
|
|
- ✅ "这个代码的执行流程是什么?"
|
|
|
|
|
|
- ✅ "我想让它在点击时做 XXX,该怎么改?"
|
2026-02-15 16:23:15 +08:00
|
|
|
|
:::
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
## 2. 数据篇:变量与数据类型
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
### 2.1 变量:给数据贴标签
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
**变量就像一个带名字的盒子**——你可以把数据放进去,需要时再取出来。
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
```javascript
|
|
|
|
|
|
const name = "张三" // 名字不会变,用 const
|
|
|
|
|
|
let age = 25 // 年龄可能会变,用 let
|
|
|
|
|
|
```
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
**两种声明方式:**
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
| 关键字 | 能否重新赋值 | 使用场景 |
|
|
|
|
|
|
|--------|-------------|---------|
|
|
|
|
|
|
| `const` | ❌ 不能 | 默认首选,值不会变的情况 |
|
|
|
|
|
|
| `let` | ✅ 能 | 需要重新赋值的情况 |
|
|
|
|
|
|
| `var` | (老语法) | 遇到了知道是变量就行,不要用 |
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
**Vibecoding 提示:**
|
|
|
|
|
|
- 看到 `const` → 这个值后面不会变
|
|
|
|
|
|
- 看到 `let` → 这个值后面会变
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
```javascript
|
|
|
|
|
|
const score = 0
|
|
|
|
|
|
score = 10 // ❌ 报错!const 不能重新赋值
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
let points = 0
|
|
|
|
|
|
points = 10 // ✅ 正确,let 可以重新赋值
|
|
|
|
|
|
```
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
👇 **动手试试看**:
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
<VariableBoxDemo />
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
### 2.2 数据类型:JS 世界里的几种"东西"
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
JavaScript 有几种基本的数据类型,最常用的是这三个:
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
**基本类型:**
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
| 类型 | 说明 | 示例 |
|
|
|
|
|
|
|------|------|------|
|
|
|
|
|
|
| `string` | 文本 | `"hello"`, `'你好'` |
|
|
|
|
|
|
| `number` | 数字 | `42`, `3.14`, `NaN` |
|
|
|
|
|
|
| `boolean` | 布尔值(真/假) | `true`, `false` |
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
**两个特殊的值:**
|
|
|
|
|
|
- `undefined` → 还没给值
|
|
|
|
|
|
- `null` → 故意设为空
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
**模板字符串(反引号):**
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
AI 代码里你经常会看到这种写法:
|
|
|
|
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
|
|
const name = "张三"
|
|
|
|
|
|
const age = 25
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
// 用反引号(键盘左上角那个键)和 ${}
|
|
|
|
|
|
const message = `我叫${name},今年${age}岁`
|
|
|
|
|
|
// message = "我叫张三,今年25岁"
|
|
|
|
|
|
```
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
**Vibecoding 提示:**
|
|
|
|
|
|
- 看到反引号 `` ` `` → 这是模板字符串,里面可以用 `${变量}` 插入值
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
### 2.3 对象与数组:把数据组织起来
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
**对象 = 一组有名字的属性**(像身份证/个人资料卡)
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
|
|
|
|
|
```javascript
|
2026-02-15 18:15:42 +08:00
|
|
|
|
const person = {
|
|
|
|
|
|
name: "张三",
|
|
|
|
|
|
age: 25,
|
|
|
|
|
|
isStudent: true
|
|
|
|
|
|
}
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
// 访问属性
|
|
|
|
|
|
console.log(person.name) // "张三"
|
|
|
|
|
|
console.log(person.age) // 25
|
2026-02-15 16:23:15 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
**数组 = 一组有顺序的数据**(像排队/列表)
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
```javascript
|
|
|
|
|
|
const colors = ['红色', '绿色', '蓝色']
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
// 访问元素(索引从 0 开始)
|
|
|
|
|
|
console.log(colors[0]) // "红色"
|
|
|
|
|
|
console.log(colors[1]) // "绿色"
|
|
|
|
|
|
```
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
**嵌套结构:**
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
在 AI 生成的代码里,你经常会看到对象里套数组、数组里套对象:
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
```javascript
|
|
|
|
|
|
const todos = [
|
|
|
|
|
|
{ id: 1, text: "学习 JavaScript", done: false },
|
|
|
|
|
|
{ id: 2, text: "做项目", done: true },
|
|
|
|
|
|
{ id: 3, text: "写文档", done: false }
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
// 访问:先找数组索引,再找对象属性
|
|
|
|
|
|
console.log(todos[0].text) // "学习 JavaScript"
|
|
|
|
|
|
console.log(todos[1].done) // true
|
|
|
|
|
|
```
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
**Vibecoding 提示:**
|
|
|
|
|
|
- 看到 `{}` → 对象(一组有名字的数据)
|
|
|
|
|
|
- 看到 `[]` → 数组(一组有顺序的数据)
|
|
|
|
|
|
- 看到 `data[0].name` → 先取数组的第 0 项,再取它的 name 属性
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
### 2.4 值与引用:为什么改了 B,A 也变了?
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
这是新手最容易踩的坑!
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
**基本类型(string、number、boolean)赋值 = 复制一份副本:**
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
|
|
let a = 10
|
|
|
|
|
|
let b = a // b 得到 a 的副本
|
|
|
|
|
|
b = 20
|
|
|
|
|
|
console.log(a) // 10(a 不受影响)
|
2026-02-15 18:15:42 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**对象和数组赋值 = 复制的是"地址":**
|
|
|
|
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
|
|
let obj1 = { name: "张三" }
|
|
|
|
|
|
let obj2 = obj1 // obj2 指向同一个对象
|
|
|
|
|
|
obj2.name = "李四" // 修改 obj2 会影响 obj1
|
|
|
|
|
|
console.log(obj1.name) // "李四"(obj1 也变了!)
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
这就是为什么 AI 代码里经常看到 `[...array]` 或 `{...obj}`——它在"创建副本",避免互相影响。
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
```javascript
|
|
|
|
|
|
// 用展开运算符创建副本
|
|
|
|
|
|
const arr1 = [1, 2, 3]
|
|
|
|
|
|
const arr2 = [...arr1] // 创建新数组,不是复制地址
|
|
|
|
|
|
arr2.push(4)
|
|
|
|
|
|
console.log(arr1) // [1, 2, 3](不受影响)
|
|
|
|
|
|
console.log(arr2) // [1, 2, 3, 4]
|
2026-02-15 16:23:15 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
**Vibecoding 场景:**
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
如果你发现修改了一条数据,别的地方也莫名其妙变了,十有八九是引用问题。
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
告诉 AI:**"这里需要深拷贝,不要直接修改原数据"**
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
|
|
|
|
|
👇 **动手试试看**:
|
|
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
<ReferenceDemo />
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
### 2.5 解构与展开:现代 JS 的快捷写法
|
|
|
|
|
|
|
|
|
|
|
|
这两个语法在 AI 生成的代码里到处都是,不认识就读不懂代码。
|
|
|
|
|
|
|
|
|
|
|
|
**解构赋值:从对象或数组里把数据拿出来**
|
|
|
|
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
|
|
const person = { name: "张三", age: 25, city: "北京" }
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
// 不用解构(传统写法)
|
|
|
|
|
|
const name = person.name
|
|
|
|
|
|
const age = person.age
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
// 用解构(现代写法)
|
|
|
|
|
|
const { name, age } = person
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
// 数组解构
|
|
|
|
|
|
const colors = ['红色', '绿色', '蓝色']
|
|
|
|
|
|
const [first, second] = colors
|
|
|
|
|
|
// first = '红色', second = '绿色'
|
|
|
|
|
|
```
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
**展开运算符:把数组或对象"展开铺平"**
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
```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: "北京" }
|
|
|
|
|
|
// { name: "张三", age: 25, city: "北京" }
|
|
|
|
|
|
|
|
|
|
|
|
// 合并对象
|
|
|
|
|
|
const baseConfig = { url: "/api", timeout: 5000 }
|
|
|
|
|
|
const userConfig = { timeout: 10000 }
|
|
|
|
|
|
const finalConfig = { ...baseConfig, ...userConfig }
|
|
|
|
|
|
// { url: "/api", timeout: 10000 }(userConfig 会覆盖 baseConfig 的同名属性)
|
|
|
|
|
|
```
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
**Vibecoding 提示:**
|
|
|
|
|
|
- 看到 `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 字段"
|
|
|
|
|
|
- "这里需要创建副本,不要修改原数据"
|
2026-02-15 16:23:15 +08:00
|
|
|
|
:::
|
|
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 3. 逻辑篇:函数与流程控制
|
|
|
|
|
|
|
|
|
|
|
|
### 3.1 条件判断:if/else 和三元运算符
|
|
|
|
|
|
|
|
|
|
|
|
**if/else:如果...就...否则...**
|
|
|
|
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
|
|
const age = 18
|
|
|
|
|
|
|
|
|
|
|
|
if (age >= 18) {
|
|
|
|
|
|
console.log("成年人")
|
|
|
|
|
|
} else {
|
|
|
|
|
|
console.log("未成年")
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**三元运算符:简写的条件判断**
|
|
|
|
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
|
|
// 完整写法
|
|
|
|
|
|
let message
|
|
|
|
|
|
if (age >= 18) {
|
|
|
|
|
|
message = "成年人"
|
|
|
|
|
|
} else {
|
|
|
|
|
|
message = "未成年"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 三元运算符(一行搞定)
|
|
|
|
|
|
const message = age >= 18 ? "成年人" : "未成年"
|
|
|
|
|
|
// 格式:条件 ? 真的值 : 假的值
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**&& 短路写法:React 代码里常见**
|
|
|
|
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
|
|
// 只有 isLoggedIn 为 true 时才显示用户面板
|
|
|
|
|
|
isLoggedIn && <UserPanel />
|
|
|
|
|
|
|
|
|
|
|
|
// 等价于
|
|
|
|
|
|
if (isLoggedIn) {
|
|
|
|
|
|
return <UserPanel />
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
**Vibecoding 提示:**
|
|
|
|
|
|
- 看到 `? :` → 这是三元运算符,简写的 if/else
|
|
|
|
|
|
- 看到 `&&` → 前面为 true 才执行后面
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
### 3.2 函数:可以反复调用的操作
|
|
|
|
|
|
|
|
|
|
|
|
**函数 = 一道菜的配方**
|
|
|
|
|
|
|
|
|
|
|
|
- 定义函数 = 写下配方
|
|
|
|
|
|
- 调用函数 = 按配方做菜
|
|
|
|
|
|
- 参数 = 原料
|
|
|
|
|
|
- 返回值 = 成品
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
|
|
|
|
|
```javascript
|
2026-02-15 18:15:42 +08:00
|
|
|
|
// 定义函数(写下配方)
|
2026-02-15 16:23:15 +08:00
|
|
|
|
function greet(name) {
|
|
|
|
|
|
return "Hello " + name
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
// 调用函数(按配方做菜)
|
|
|
|
|
|
console.log(greet("张三")) // "Hello 张三"
|
|
|
|
|
|
console.log(greet("李四")) // "Hello 李四"
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**三种写法一眼识别:**
|
|
|
|
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
|
|
// 1. function 声明
|
|
|
|
|
|
function greet(name) {
|
|
|
|
|
|
return "Hello " + name
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 函数表达式
|
2026-02-15 16:23:15 +08:00
|
|
|
|
const greet = function(name) {
|
|
|
|
|
|
return "Hello " + name
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
// 3. 箭头函数(AI 代码里用得最多)
|
|
|
|
|
|
const greet = (name) => {
|
|
|
|
|
|
return "Hello " + name
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 箭头函数简写(只有一行时可以省略 {} 和 return)
|
|
|
|
|
|
const greet = (name) => "Hello " + name
|
2026-02-15 16:23:15 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
**重点:** 能认出来就行,不需要纠结什么时候用哪种。箭头函数最简洁,AI 代码里用得最多。
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
👇 **动手试试看**:
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
<FunctionMachineDemo />
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
**Vibecoding 提示:**
|
|
|
|
|
|
- 看到 `function` 或 `=>` → 这是一个函数
|
|
|
|
|
|
- 看到 `fn()` → 在调用这个函数
|
|
|
|
|
|
- 看到 `() => {}` → 箭头函数,现代 JS 的主流写法
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
### 3.3 循环与数组方法
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
**for 循环:基本认识即可**
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
|
|
|
|
|
```javascript
|
2026-02-15 18:15:42 +08:00
|
|
|
|
for (let i = 0; i < 5; i++) {
|
|
|
|
|
|
console.log(i) // 输出 0, 1, 2, 3, 4
|
2026-02-15 16:23:15 +08:00
|
|
|
|
}
|
2026-02-15 18:15:42 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**数组方法:React/Vue 代码里几乎每个列表渲染都用 map**
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
```javascript
|
|
|
|
|
|
const todos = [
|
|
|
|
|
|
{ id: 1, text: "学习", done: false },
|
|
|
|
|
|
{ id: 2, text: "工作", done: true }
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
// .map():把数组的每一项变成另一个东西(返回新数组)
|
|
|
|
|
|
const todoItems = todos.map(todo => `<li>${todo.text}</li>`)
|
|
|
|
|
|
// ["<li>学习</li>", "<li>工作</li>"]
|
|
|
|
|
|
|
|
|
|
|
|
// .filter():筛选出符合条件的项
|
|
|
|
|
|
const unfinished = todos.filter(todo => !todo.done)
|
|
|
|
|
|
// [{ id: 1, text: "学习", done: false }]
|
|
|
|
|
|
|
|
|
|
|
|
// .find():找到第一个符合条件的项
|
|
|
|
|
|
const found = todos.find(todo => todo.id === 1)
|
|
|
|
|
|
// { id: 1, text: "学习", done: false }
|
2026-02-15 16:23:15 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
**Vibecoding 提示:**
|
|
|
|
|
|
- 看到 `.map()` → 对数组做变换,返回新数组
|
|
|
|
|
|
- 看到 `.filter()` → 筛选数组
|
|
|
|
|
|
- 看到 `items.map(item => <li>{item.name}</li>)` → 把每个数据项变成一个列表标签
|
|
|
|
|
|
|
|
|
|
|
|
### 3.4 作用域:变量的"可见范围"
|
|
|
|
|
|
|
|
|
|
|
|
**用"房间"比喻:**
|
|
|
|
|
|
|
|
|
|
|
|
- 函数内部的变量就像房间里的东西,外面看不到
|
|
|
|
|
|
- 但房间里的人可以看到走廊(外层作用域)的东西
|
|
|
|
|
|
|
2026-02-15 16:23:15 +08:00
|
|
|
|
```javascript
|
2026-02-15 18:15:42 +08:00
|
|
|
|
const global = "全局变量" // 走廊里的东西
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
function room() {
|
|
|
|
|
|
const local = "房间里的东西" // 房间里的东西
|
|
|
|
|
|
console.log(global) // ✅ 能看到走廊
|
|
|
|
|
|
}
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
console.log(local) // ❌ 报错!外面看不到房间里的东西
|
2026-02-15 16:23:15 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
**三种作用域:**
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
|
|
|
|
|
```javascript
|
2026-02-15 18:15:42 +08:00
|
|
|
|
// 全局作用域(走廊)
|
|
|
|
|
|
const appName = "Todo"
|
|
|
|
|
|
|
|
|
|
|
|
function outer() {
|
|
|
|
|
|
// 函数作用域(房间)
|
|
|
|
|
|
const message = "你好"
|
|
|
|
|
|
|
|
|
|
|
|
if (true) {
|
|
|
|
|
|
// 块级作用域(小房间)
|
|
|
|
|
|
const greeting = message + appName
|
|
|
|
|
|
console.log(greeting) // ✅ 能看到外层的
|
2026-02-15 16:23:15 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
console.log(greeting) // ❌ 报错!外层看不到内层
|
|
|
|
|
|
}
|
2026-02-15 16:23:15 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
**核心直觉:** 代码写在哪里,决定了它能看到什么变量。
|
|
|
|
|
|
|
|
|
|
|
|
👇 **动手试试看**:
|
|
|
|
|
|
|
|
|
|
|
|
<ScopeDemo />
|
|
|
|
|
|
|
|
|
|
|
|
### 3.5 闭包:函数"记住"了它诞生时的环境
|
|
|
|
|
|
|
|
|
|
|
|
**不要把闭包当成独立的难点概念来讲**,从一个具体场景引入:
|
|
|
|
|
|
|
|
|
|
|
|
**问题:为什么点击事件的回调函数能使用外面定义的变量?**
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
|
|
|
|
|
```javascript
|
2026-02-15 18:15:42 +08:00
|
|
|
|
function setupButtons() {
|
|
|
|
|
|
let count = 0
|
|
|
|
|
|
|
|
|
|
|
|
button.addEventListener('click', () => {
|
|
|
|
|
|
count++ // 为什么这里的 count 能记住上次的值?
|
|
|
|
|
|
console.log(count)
|
2026-02-15 16:23:15 +08:00
|
|
|
|
})
|
|
|
|
|
|
}
|
2026-02-15 18:15:42 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**核心直觉:** 函数在被创建时,会"记住"它周围的变量,即使外层函数已经执行完了。
|
|
|
|
|
|
|
|
|
|
|
|
**实际场景:计数器**
|
|
|
|
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
|
|
function createCounter() {
|
|
|
|
|
|
let count = 0 // 私有变量
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
return {
|
|
|
|
|
|
add: () => { count++; return count },
|
|
|
|
|
|
subtract: () => { count--; return count },
|
|
|
|
|
|
getCount: () => count
|
|
|
|
|
|
}
|
2026-02-15 16:23:15 +08:00
|
|
|
|
}
|
2026-02-15 18:15:42 +08:00
|
|
|
|
|
|
|
|
|
|
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(每个计数器独立)
|
2026-02-15 16:23:15 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
👇 **动手试试看**:
|
|
|
|
|
|
|
|
|
|
|
|
<ClosureDemo />
|
|
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
**Vibecoding 场景:**
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
如果 AI 代码里一个内部函数用了外部变量,这就是闭包在工作。一般不需要你干预。
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
但如果循环里创建函数导致所有函数共享同一个变量值,告诉 AI:**"闭包捕获了循环变量的引用,需要修复"**
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
### 3.6 this:谁在调用我?
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
**不讲四种绑定规则**,只讲两个最常见的场景:
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
**场景 1:在 class 的方法里,this 指向这个 class 的实例**
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
```javascript
|
|
|
|
|
|
class Counter {
|
|
|
|
|
|
constructor() {
|
|
|
|
|
|
this.count = 0
|
|
|
|
|
|
}
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
increment() {
|
|
|
|
|
|
this.count++ // this 指向 Counter 的实例
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
**场景 2:在事件监听回调里,this 指向触发事件的 DOM 元素**
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
```javascript
|
|
|
|
|
|
button.addEventListener('click', function() {
|
|
|
|
|
|
console.log(this) // this 指向 button 元素
|
|
|
|
|
|
})
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
// 但箭头函数不会改变 this
|
|
|
|
|
|
button.addEventListener('click', () => {
|
|
|
|
|
|
console.log(this) // this 指向外层的 this
|
|
|
|
|
|
})
|
|
|
|
|
|
```
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
**核心直觉:** this 不是固定的,取决于函数怎么被调用。
|
|
|
|
|
|
|
|
|
|
|
|
**Vibecoding 场景:**
|
|
|
|
|
|
|
|
|
|
|
|
如果 AI 代码里出现 this 相关的 bug(比如 `Cannot read property of undefined`),通常是因为函数的 this 指向丢了。
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
告诉 AI:**"这个方法里的 this 指向不对,改成箭头函数或者用 bind"**
|
|
|
|
|
|
|
|
|
|
|
|
::: tip 🤖 Vibecoding 备忘
|
|
|
|
|
|
**AI 代码里你会看到:**
|
|
|
|
|
|
- `if/else` → 条件判断
|
|
|
|
|
|
- `condition ? a : b` → 三元运算符,简写 if/else
|
|
|
|
|
|
- `fn()` → 调用函数
|
|
|
|
|
|
- `() => {}` → 箭头函数
|
|
|
|
|
|
- `.map()` / `.filter()` / `.find()` → 数组方法
|
|
|
|
|
|
- `this` → 取决于函数怎么被调用
|
|
|
|
|
|
|
|
|
|
|
|
**遇到问题时这样跟 AI 说:**
|
|
|
|
|
|
- "这个判断条件是什么意思?"
|
|
|
|
|
|
- "这个函数的返回值是什么?"
|
|
|
|
|
|
- "这个 this 指向哪里?"
|
|
|
|
|
|
- "这个闭包为什么会共享变量?"
|
|
|
|
|
|
:::
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
## 4. 交互篇:DOM、事件与异步
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
### 4.1 DOM:JavaScript 看到的网页长什么样
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
网页在 JS 眼里是一棵"树",每个 HTML 标签是树上的一个"节点"。
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
```html
|
|
|
|
|
|
<html>
|
|
|
|
|
|
<body>
|
|
|
|
|
|
<h1>标题</h1>
|
|
|
|
|
|
<p>段落</p>
|
|
|
|
|
|
<ul>
|
|
|
|
|
|
<li>项目1</li>
|
|
|
|
|
|
<li>项目2</li>
|
|
|
|
|
|
</ul>
|
|
|
|
|
|
</body>
|
|
|
|
|
|
</html>
|
|
|
|
|
|
```
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
**JS 操控网页 = 找到节点、修改节点、创建/删除节点**
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
👇 **动手试试看**:
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
<DOMTreeDemo />
|
|
|
|
|
|
|
|
|
|
|
|
### 4.2 查找与修改元素
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
**查找元素:**
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
```javascript
|
|
|
|
|
|
// 根据 CSS 选择器查找(最常用)
|
|
|
|
|
|
const title = document.querySelector('h1')
|
|
|
|
|
|
const button = document.querySelector('#submitBtn')
|
|
|
|
|
|
const items = document.querySelectorAll('.item')
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
// 根据 ID 查找
|
|
|
|
|
|
const button = document.getElementById('submitBtn')
|
2026-02-15 16:23:15 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
**修改元素:**
|
|
|
|
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
|
|
// 改文字
|
|
|
|
|
|
title.textContent = "新标题"
|
|
|
|
|
|
title.innerHTML = "<strong>粗体标题</strong>"
|
|
|
|
|
|
|
|
|
|
|
|
// 改样式
|
|
|
|
|
|
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)
|
2026-02-15 16:23:15 +08:00
|
|
|
|
```
|
2026-02-15 18:15:42 +08:00
|
|
|
|
|
|
|
|
|
|
**Vibecoding 提示:**
|
|
|
|
|
|
- 看到 `document.querySelector` → 在查找网页元素
|
|
|
|
|
|
- 看到 `.textContent` / `.innerHTML` → 改文字
|
|
|
|
|
|
- 看到 `.style.xxx` → 改样式
|
|
|
|
|
|
- 看到 `.classList.add/remove/toggle` → 改 CSS 类
|
|
|
|
|
|
|
|
|
|
|
|
### 4.3 事件:当用户做了某个操作时...
|
|
|
|
|
|
|
|
|
|
|
|
**addEventListener:给元素添加事件监听**
|
|
|
|
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
|
|
button.addEventListener('click', () => {
|
|
|
|
|
|
console.log("按钮被点击了")
|
|
|
|
|
|
})
|
2026-02-15 16:23:15 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
**常见事件:**
|
|
|
|
|
|
|
|
|
|
|
|
| 事件 | 触发时机 |
|
|
|
|
|
|
|------|---------|
|
|
|
|
|
|
| `click` | 点击 |
|
|
|
|
|
|
| `input` | 输入框内容变化 |
|
|
|
|
|
|
| `submit` | 表单提交 |
|
|
|
|
|
|
| `scroll` | 滚动页面 |
|
|
|
|
|
|
| `keydown` | 按下键盘 |
|
|
|
|
|
|
|
|
|
|
|
|
**事件对象:**
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
|
|
|
|
|
```javascript
|
2026-02-15 18:15:42 +08:00
|
|
|
|
input.addEventListener('input', (e) => {
|
|
|
|
|
|
console.log(e.target.value) // 获取输入框的值
|
|
|
|
|
|
e.preventDefault() // 阻止默认行为
|
|
|
|
|
|
})
|
|
|
|
|
|
```
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
**Vibecoding 场景:**
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
当你想给按钮加一个功能,本质上就是在告诉 AI:**"给这个按钮添加一个点击事件,点击后执行某某操作"**
|
|
|
|
|
|
|
|
|
|
|
|
### 4.4 异步:为什么有些操作不是立刻完成的
|
|
|
|
|
|
|
|
|
|
|
|
**餐厅比喻:**
|
|
|
|
|
|
|
|
|
|
|
|
点菜后不用站在厨房门口等,可以先做别的事,菜好了服务员会端过来。
|
|
|
|
|
|
|
|
|
|
|
|
**最常见场景:从服务器获取数据(fetch / API 调用)**
|
|
|
|
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
|
|
// 同步写法(会卡住页面)
|
|
|
|
|
|
const data = fetch('/api/data') // ❌ 别这样写
|
|
|
|
|
|
console.log(data)
|
|
|
|
|
|
|
|
|
|
|
|
// 异步写法(不卡住)
|
|
|
|
|
|
async function loadData() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const response = await fetch('/api/data')
|
|
|
|
|
|
const data = await response.json()
|
|
|
|
|
|
console.log(data)
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('出错了:', error)
|
|
|
|
|
|
}
|
2026-02-15 16:23:15 +08:00
|
|
|
|
}
|
|
|
|
|
|
```
|
2026-02-15 18:15:42 +08:00
|
|
|
|
|
|
|
|
|
|
**async/await 语法:**
|
|
|
|
|
|
|
|
|
|
|
|
- `async` → 标记这个函数里有异步操作
|
|
|
|
|
|
- `await` → 等待这个操作完成(但不会卡住页面)
|
|
|
|
|
|
- `try/catch` → 处理可能出现的错误
|
|
|
|
|
|
|
|
|
|
|
|
**只讲 async/await**。回调和 Promise.then 链各用一句话提("这是旧的写法,认识就行")。
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
|
|
|
|
|
👇 **动手试试看**:
|
|
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
<AsyncRestaurantDemo />
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
**Vibecoding 提示:**
|
|
|
|
|
|
- 看到 `async/await` → 在等待耗时操作
|
|
|
|
|
|
- 看到 `fetch()` → 在从服务器获取数据
|
|
|
|
|
|
- 看到 `try/catch` → 在处理可能的错误
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
### 4.5 事件循环:JavaScript 到底怎么工作的
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
**不用术语"微任务/宏任务"**,用可视化演示:
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
**JS 是一个"单人工位"**,同时只做一件事,但有一个"待办便签栏"(任务队列)。
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
当遇到要等待的操作(网络请求、定时器),JS 不是傻等,而是把"等好了之后做什么"贴到便签栏,自己继续往下执行。
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
等当前事情做完了,才去看便签栏上有没有该做的事。
|
|
|
|
|
|
|
|
|
|
|
|
**这个心智模型解释了**为什么 `console.log` 的打印顺序有时候跟代码顺序不一样。
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
|
|
console.log("1")
|
|
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
setTimeout(() => console.log("2"), 0) // 即使是 0 秒,也会推迟
|
|
|
|
|
|
|
2026-02-15 16:23:15 +08:00
|
|
|
|
console.log("3")
|
2026-02-15 18:15:42 +08:00
|
|
|
|
|
|
|
|
|
|
// 输出:1, 3, 2(不是 1, 2, 3!)
|
2026-02-15 16:23:15 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
**执行流程:**
|
|
|
|
|
|
1. 执行 `console.log("1")` → 输出 1
|
|
|
|
|
|
2. 遇到 `setTimeout` → 把回调贴到便签栏,继续往下
|
|
|
|
|
|
3. 执行 `console.log("3")` → 输出 3
|
|
|
|
|
|
4. 当前代码执行完了,去看便签栏
|
|
|
|
|
|
5. 执行 `setTimeout` 的回调 → 输出 2
|
|
|
|
|
|
|
|
|
|
|
|
👇 **动手试试看**:
|
|
|
|
|
|
|
|
|
|
|
|
<JSEventLoopDemo />
|
|
|
|
|
|
|
|
|
|
|
|
**Vibecoding 场景:**
|
|
|
|
|
|
|
|
|
|
|
|
如果 AI 代码里数据还没获取到页面就渲染了,这是异步时序问题。
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
告诉 AI:**"数据还没加载完就开始渲染了,需要添加 loading 状态,等数据到了再渲染"**
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
::: tip 🤖 Vibecoding 备忘
|
|
|
|
|
|
**AI 代码里你会看到:**
|
|
|
|
|
|
- `document.querySelector()` → 查找元素
|
|
|
|
|
|
- `.addEventListener()` → 监听事件
|
|
|
|
|
|
- `async/await` → 异步操作
|
|
|
|
|
|
- `fetch()` → 网络请求
|
|
|
|
|
|
|
|
|
|
|
|
**遇到问题时这样跟 AI 说:**
|
|
|
|
|
|
- "我想给按钮添加点击事件"
|
|
|
|
|
|
- "数据加载完了但没有显示"
|
|
|
|
|
|
- "页面在数据加载前就渲染了,需要加 loading"
|
|
|
|
|
|
:::
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 5. 实战篇:像老手一样读懂和调试代码
|
|
|
|
|
|
|
|
|
|
|
|
### 5.1 模块:import 和 export
|
|
|
|
|
|
|
|
|
|
|
|
AI 生成的 React/Vue 代码第一行几乎都是 `import`。
|
|
|
|
|
|
|
|
|
|
|
|
**import = 从别的文件引入功能**
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
|
|
|
|
|
```javascript
|
2026-02-15 18:15:42 +08:00
|
|
|
|
// 从工具文件引入函数
|
|
|
|
|
|
import { formatDate } from './utils'
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
// 从第三方包引入
|
|
|
|
|
|
import React from 'react'
|
|
|
|
|
|
import { useState } from 'react'
|
|
|
|
|
|
```
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
**export = 把功能暴露出去给别人用**
|
|
|
|
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
|
|
// utils.js
|
|
|
|
|
|
export function formatDate(date) {
|
|
|
|
|
|
// ...
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 或者默认导出
|
|
|
|
|
|
export default function formatDate(date) {
|
|
|
|
|
|
// ...
|
2026-02-15 16:23:15 +08:00
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
**npm 包 = 别人写好的工具,你可以直接安装使用**
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
```javascript
|
|
|
|
|
|
// 安装包
|
|
|
|
|
|
// npm install lodash
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
// 使用包
|
|
|
|
|
|
import _ from 'lodash'
|
|
|
|
|
|
```
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
这一节放在实战篇而不是语法篇,因为读者在前 4 章建立了足够基础后,这里只是"识别"。
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
**Vibecoding 提示:**
|
|
|
|
|
|
- 看到 `import` → 从别的文件引入功能
|
|
|
|
|
|
- 看到 `export` → 把功能暴露给别人用
|
|
|
|
|
|
- 看到 `from 'react'` → 从 React 包引入
|
|
|
|
|
|
- 看到 `from './utils'` → 从本地文件引入
|
|
|
|
|
|
|
|
|
|
|
|
### 5.2 拿到 AI 代码后的阅读策略
|
|
|
|
|
|
|
|
|
|
|
|
**第一步:看整体结构**
|
|
|
|
|
|
|
|
|
|
|
|
有几个函数?分别叫什么名字?大致做什么?
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
|
|
|
|
|
```javascript
|
2026-02-15 18:15:42 +08:00
|
|
|
|
// 一眼看出:三个函数
|
|
|
|
|
|
function loadData() { } // 加载数据
|
|
|
|
|
|
function renderList() { } // 渲染列表
|
|
|
|
|
|
function handleClick() { } // 处理点击
|
|
|
|
|
|
```
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
**第二步:找入口**
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
哪里是程序开始执行的地方?事件监听绑在了哪些元素上?
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
```javascript
|
|
|
|
|
|
// 入口点
|
|
|
|
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
|
|
|
|
loadData() // 程序从这里开始
|
|
|
|
|
|
})
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
button.addEventListener('click', handleClick)
|
2026-02-15 16:23:15 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
**第三步:追踪数据流**
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
数据从哪里来?经过了什么变换?最终渲染到了哪里?
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
```javascript
|
|
|
|
|
|
async function loadData() {
|
|
|
|
|
|
const data = await fetch('/api/todos') // 数据从服务器来
|
|
|
|
|
|
const todos = await data.json() // 解析成 JSON
|
|
|
|
|
|
renderList(todos) // 渲染到页面
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**第四步:看细节逻辑**
|
|
|
|
|
|
|
|
|
|
|
|
某个具体函数里面是怎么处理的?
|
|
|
|
|
|
|
|
|
|
|
|
**用第 1 章的代码示例做一次完整的"阅读演示":**
|
|
|
|
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
|
|
// 第一步:整体结构
|
|
|
|
|
|
// - 一个颜色数组
|
|
|
|
|
|
// - 一个变量记录当前索引
|
|
|
|
|
|
// - 一个按钮的点击事件
|
|
|
|
|
|
|
|
|
|
|
|
// 第二步:入口点
|
|
|
|
|
|
// button.addEventListener('click', ...) → 点击按钮时执行
|
|
|
|
|
|
|
|
|
|
|
|
// 第三步:数据流
|
|
|
|
|
|
// colors(颜色数组)→ currentIndex(当前索引)→ backgroundColor(背景色)
|
|
|
|
|
|
|
|
|
|
|
|
// 第四步:细节逻辑
|
|
|
|
|
|
// currentIndex = (currentIndex + 1) % colors.length
|
|
|
|
|
|
// 这个公式的意思:每次 +1,但不超过数组长度(循环)
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 5.3 常见报错速查与应对
|
|
|
|
|
|
|
|
|
|
|
|
| 报错 | 大白话翻译 | 怎么跟 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 改代码
|
|
|
|
|
|
|
|
|
|
|
|
这是"3-5 年经验 sense"的核心体现:**描述问题的精准度**。
|
|
|
|
|
|
|
|
|
|
|
|
**6-8 组对比示例:**
|
|
|
|
|
|
|
|
|
|
|
|
| ❌ 差的描述 | ✅ 好的描述 |
|
|
|
|
|
|
|-----------|-----------|
|
|
|
|
|
|
| "代码有 bug" | "点击删除按钮时,删除的不是当前项而是最后一项" |
|
|
|
|
|
|
| "样式不对" | "标题应该居中,现在是左对齐" |
|
|
|
|
|
|
| "数据显示不出来" | "fetch 请求返回了数据(控制台能看到),但页面没有重新渲染" |
|
|
|
|
|
|
| "加一个功能" | "在用户列表页面添加一个搜索框,输入时实时过滤列表,按 name 字段模糊匹配" |
|
|
|
|
|
|
| "点击没反应" | "点击按钮时控制台报错 'Cannot read property of undefined',错误在第 X 行" |
|
|
|
|
|
|
| "布局乱了" | "在小屏幕上,导航栏和内容区域重叠了,需要调整响应式布局" |
|
|
|
|
|
|
| "太慢了" | "加载 100 条数据时页面卡顿 2 秒,需要做虚拟滚动或分页" |
|
|
|
|
|
|
| "我想做个登录功能" | "实现一个登录表单,包含邮箱和密码输入框,点击登录后调用 /api/login 接口,成功后保存 token 并跳转到首页" |
|
|
|
|
|
|
|
|
|
|
|
|
**一个实战练习:**
|
|
|
|
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
|
|
// 有 bug 的代码
|
|
|
|
|
|
function deleteTodo(index) {
|
|
|
|
|
|
todos.splice(index, 1) // 总是删除最后一项
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 错误现象:无论点哪个删除按钮,删的都是最后一项
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**❌ 差的描述:** "删除功能有 bug"
|
|
|
|
|
|
|
|
|
|
|
|
**✅ 好的描述:** "点击删除按钮时,删除的不是当前项而是最后一项。代码里用了 splice(index, 1),但 index 可能不正确。需要改成用每个事项的唯一 id 来匹配删除。"
|
|
|
|
|
|
|
|
|
|
|
|
### 5.5 你的下一步:概念地图
|
|
|
|
|
|
|
|
|
|
|
|
**你现在应该能做到:**
|
|
|
|
|
|
|
|
|
|
|
|
✅ 看到 `const/let` → 知道变量能不能重新赋值
|
|
|
|
|
|
✅ 看到 `{}` → 对象 / 看到 `[]` → 数组
|
|
|
|
|
|
✅ 看到 `{...obj}` 或 `[...arr]` → 在创建副本
|
|
|
|
|
|
✅ 看到 `function` 或 `=>` → 定义了一段可重复执行的操作
|
|
|
|
|
|
✅ 看到 `if/else` 或 `? :` → 代码在做判断
|
|
|
|
|
|
✅ 看到 `.map()` / `.filter()` → 在变换或筛选数组
|
|
|
|
|
|
✅ 看到 `document.querySelector` → 在查找网页元素
|
|
|
|
|
|
✅ 看到 `addEventListener` → 在监听用户操作
|
|
|
|
|
|
✅ 看到 `async/await` → 在等待耗时操作
|
|
|
|
|
|
✅ 看到 `import/export` → 在引入或导出模块
|
|
|
|
|
|
✅ 遇到报错 → 能读懂大意并精准描述给 AI
|
|
|
|
|
|
|
|
|
|
|
|
**更深层的理解:**
|
|
|
|
|
|
|
|
|
|
|
|
如果你读完了每章的"深入"部分,你还建立了这些心智模型:
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
- **值 vs 引用**:基本类型复制值,对象/数组复制的是地址
|
|
|
|
|
|
- **作用域与闭包**:函数能"记住"它诞生时周围的变量
|
|
|
|
|
|
- **this 的本质**:取决于函数被谁调用,而不是写在哪里
|
|
|
|
|
|
- **事件循环**:JS 是单线程的,靠任务队列实现"不阻塞"
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
这些是区分"能用"和"真懂"的分水岭。
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
在你的 vibecoding 旅程中,它们会一次又一次帮你快速定位问题。
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
**进阶概念地图:**
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
- **TypeScript** → 给 JavaScript 加类型检查
|
|
|
|
|
|
- **React 状态管理** → useState、useReducer、Zustand
|
|
|
|
|
|
- **Vue 响应式系统** → ref、reactive、computed
|
|
|
|
|
|
- **API 设计** → REST、GraphQL
|
|
|
|
|
|
- **构建工具** → Vite、Webpack
|
|
|
|
|
|
- **性能优化** → 防抖节流、虚拟滚动、懒加载
|
|
|
|
|
|
- **测试** → 单元测试、集成测试、E2E 测试
|
2026-02-15 16:23:15 +08:00
|
|
|
|
|
2026-02-15 18:15:42 +08:00
|
|
|
|
现在你已经有了坚实的基础,这些概念学起来会更轻松。
|
|
|
|
|
|
|
|
|
|
|
|
::: tip 🤖 Vibecoding 备忘
|
|
|
|
|
|
**AI 代码里你会看到:**
|
|
|
|
|
|
- `import` / `export` → 模块导入导出
|
|
|
|
|
|
- `try/catch` → 错误处理
|
|
|
|
|
|
- `.then()` / `.catch()` → Promise 链式调用(旧写法)
|
|
|
|
|
|
|
|
|
|
|
|
**遇到问题时这样跟 AI 说:**
|
|
|
|
|
|
- "第 X 行报错 XXX,帮我看看是什么问题"
|
|
|
|
|
|
- "这个函数的逻辑是 XXX,但结果不对,应该是 XXX"
|
|
|
|
|
|
- "我想修改 XXX 功能,具体要求是 XXX"
|
|
|
|
|
|
- "这段代码有性能问题,需要优化 XXX"
|
2026-02-15 16:23:15 +08:00
|
|
|
|
:::
|
2026-02-15 18:15:42 +08:00
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
**写在最后:**
|
|
|
|
|
|
|
|
|
|
|
|
JavaScript 是一门看似简单、实则精妙的语言。
|
|
|
|
|
|
|
|
|
|
|
|
通过本章的学习,你已经建立了对这门语言的系统性认识。**深入理解这些概念,你不仅能更好地与 AI 协作,还能更快地学习新技术。**
|
|
|
|
|
|
|
|
|
|
|
|
记住:**不必一次全学会,循序渐进、持续实践,你终将掌握这门语言的精髓。**
|