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