Files
test-repo/docs/zh-cn/appendix/3-browser-and-frontend/javascript-deep-dive.md
T
sanbuphy 9ee3312569 feat(docs): add JavaScript intro demos and update content structure
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
2026-02-15 18:15:42 +08:00

30 KiB
Raw Blame History

JavaScript 深度指南

::: tip 前言 你已经学了 HTML(网页的骨架)和 CSS(网页的皮肤)。 但光有骨架和皮肤,网页只能"看",不能"用"—— 点一个按钮什么都不会发生,填一个表单提交不了。

JavaScript 就是让网页能响应你操作的语言。 点击按钮弹出菜单、输入搜索词显示建议、 下拉页面加载更多内容——这些全靠 JavaScript。

在 vibecoding 的工作流里,AI 会帮你写大部分 JS 代码。 你的任务不是从零手写,而是:

  1. 读懂 AI 写了什么
  2. 判断 它写得对不对
  3. 精准描述 需要修改的地方

本章从最基础的概念讲起,逐步深入到专业开发者的思维方式。 读完后,你不只是能"用" 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) // 10a 不受影响)

对象和数组赋值 = 复制的是"地址"

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/else
  • fn() → 调用函数
  • () => {} → 箭头函数
  • .map() / .filter() / .find() → 数组方法
  • this → 取决于函数怎么被调用

遇到问题时这样跟 AI 说:

  • "这个判断条件是什么意思?"
  • "这个函数的返回值是什么?"
  • "这个 this 指向哪里?"
  • "这个闭包为什么会共享变量?" :::

4. 交互篇:DOM、事件与异步

4.1 DOMJavaScript 看到的网页长什么样

网页在 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!)

执行流程:

  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" :::

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 协作,还能更快地学习新技术。

记住:不必一次全学会,循序渐进、持续实践,你终将掌握这门语言的精髓。