Skip to content

JavaScript 深度指南

前言

你已经学会了 HTML 和 CSS,能做出好看的网页了。但你可能会发现:点击按钮没反应,填了表单提交不了,网页就像一张"静态"的图片。

这就是我们需要 JavaScript 的原因——它让网页"活"起来。点击按钮能弹出菜单,输入文字能实时搜索,滚动页面能加载更多内容……这些交互效果都靠 JavaScript。

在 vibecoding 里,AI 会帮你写大部分代码。但你至少得能看懂代码在做什么,否则 AI 写错了你也发现不了。读完这篇,你就能:

  • 读懂 AI 写的代码在做什么
  • 看出代码哪里有问题
  • 用清晰的话告诉 AI 怎么改

这篇文章会带你学什么?

章节内容学完能干嘛
第 1 章JavaScript 是什么明白它在网页里扮演什么角色
第 2 章数据与变量知道程序怎么存东西、怎么用东西
第 3 章函数与逻辑看懂代码的判断、循环和复用逻辑
第 4 章DOM 与事件知道代码怎么控制网页、怎么响应用户操作
第 5 章实战技巧拿到 AI 代码怎么读、遇到报错怎么说

每一章都从"能识别代码"开始,不需要你会手写。遇到不懂的代码,随时回来查就行。


1. JavaScript 是什么

🤔 核心问题

为什么网页需要 JavaScript? HTML 和 CSS 已经能让网页有内容、有样式了,为什么还要学一门新语言?

1.1 从"静态网页"到"动态应用"

📄 没有 JavaScript 的网页

  • 内容固定,无法交互
  • 点击按钮没反应
  • 填写表单提交不了
  • 页面不会自动更新

就像一张纸质海报,只能看

🚀 有 JavaScript 的网页

  • 点击按钮弹出菜单
  • 输入文字实时搜索
  • 滚动自动加载内容
  • 数据实时更新显示

就像一个真正的应用程序

用一句话理解三者的关系:

技术比喻作用
HTML骨架定义网页的结构和内容
CSS皮肤定义网页的外观和样式
JavaScript肌肉和神经系统让网页能响应、能交互、能思考

1.2 为什么 vibecoding 也需要懂 JavaScript?

刚学 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。"

一次就改对了。

这就是为什么要懂 JavaScript——不是为了手写代码,而是为了在 AI 没改对时,你能一眼看出问题在哪,一句话说到点子上。

1.3 先睹为快:一段真实的 AI 代码

在深入学习之前,让我们先看一段 AI 生成的真实代码。不要担心看不懂,只要有个印象,后面我们会逐一讲解每个部分。

场景:做一个"点击按钮切换背景颜色"的功能

javascript
// 定义一组颜色
const colors = ['#ff6b6b', '#4ecdc4', '#45b7d1', '#96ceb4']
let currentIndex = 0

// 找到页面上的按钮
const button = document.querySelector('#changeBtn')

// 给按钮添加点击事件
button.addEventListener('click', () => {
  currentIndex = (currentIndex + 1) % colors.length
  document.body.style.backgroundColor = colors[currentIndex]
})

这段代码在做什么?

代码作用对应章节
const colors = [...]定义一组颜色数据第 2 章:数组
let currentIndex = 0记录当前显示第几个颜色第 2 章:变量
document.querySelector(...)找到页面上的按钮第 4 章:DOM 查找
button.addEventListener(...)给按钮添加点击事件第 4 章:事件监听
() => {...}定义点击后要执行的代码第 3 章:箭头函数

💡 核心启示

你不需要现在就理解每一行代码。只要记住:JavaScript 代码就是一系列指令,告诉浏览器"当用户做某事时,应该发生什么"。


2. 数据篇:变量与数据类型

🤔 核心问题

程序是怎么"记住"东西的? 用户输入的内容、从服务器获取的数据、计算过程中的中间结果——这些信息都存在哪里?

2.1 变量:给数据起个名字

变量就像一个有标签的盒子——你可以把数据放进去,以后通过标签来取用。

javascript
const name = "张三"   // 名字不会变,用 const
let age = 25          // 年龄可能会变,用 let

为什么要区分 const 和 let?

想象一下:你的身份证号码(const)这辈子都不会变,但你的年龄(let)每年都会变。JavaScript 让你用不同的关键字来表达这种"变与不变"的意图。

关键字能否修改使用场景示例
const❌ 不能值不会变的数据身份证号、配置项、颜色列表
let✅ 能值会变化的数据计数器、当前选中的选项、用户输入
🔍 看一个具体的例子
javascript
// 用 const:这些值不会变
const PI = 3.14159
const MAX_USERS = 100
const APP_NAME = "TodoList"

// 用 let:这些值会变化
let count = 0
count = 1  // ✅ 可以修改

count = count + 1  // ✅ 可以基于原值计算

// 如果用 const 会怎样?
const fixedCount = 0
fixedCount = 1  // ❌ 报错!const 不能重新赋值

👇 动手试试看:修改下面的代码,看看 const 和 let 的区别

📦 变量就像带名字的盒子
const
name
张三
🔒
let
age
25
🔓
const
isStudent
true
🔒
const name = "张三"let age = 25

2.2 数据类型:JavaScript 里的几种"东西"

JavaScript 把数据分成几种类型,最常用的有三种:

类型说明示例实际场景
string(字符串)文本内容"hello", '你好'用户名、商品描述、提示信息
number(数字)数值42, 3.14价格、数量、评分
boolean(布尔值)是/否true, false是否登录、是否完成、是否可见

还有两个特殊值需要知道:

  • undefined → 变量声明了,但还没给值
  • null → 故意设为空(表示"这里没有值")
🔍 模板字符串:更方便地拼接文本

在 AI 代码里,你经常会看到用反引号(`)包裹的字符串,里面还有 ${...}

javascript
const name = "张三"
const age = 25

// 传统写法(麻烦)
const message = "我叫" + name + ",今年" + age + "岁"

// 模板字符串(简洁)
const message = `我叫${name},今年${age}岁`
// 结果:"我叫张三,今年25岁"

识别要点:看到反引号和 ${},就知道是在把变量插入到文本中。

2.3 对象和数组:把数据组织起来

对象 = 一组有名字的属性(像一张个人信息表)

javascript
const user = {
  name: "张三",
  age: 25,
  isVIP: true
}

// 使用点号访问属性
console.log(user.name)    // "张三"
console.log(user.age)     // 25

数组 = 一组有顺序的数据(像一个列表)

javascript
const colors = ['红色', '绿色', '蓝色']

// 用索引访问(从 0 开始)
console.log(colors[0])  // "红色"
console.log(colors[1])  // "绿色"

嵌套结构:对象里套数组、数组里套对象

这是 AI 代码中最常见的数据结构:

javascript
const todos = [
  { id: 1, text: "学习 JavaScript", done: false },
  { id: 2, text: "做项目", done: true },
  { id: 3, text: "写文档", done: false }
]

// 访问:先取数组的第 0 项,再取它的 text 属性
console.log(todos[0].text)  // "学习 JavaScript"

💡 识别技巧

  • 看到 {} → 这是一个对象,里面是一组 名字: 值
  • 看到 [] → 这是一个数组,里面是一组按顺序排列的值
  • 看到 data[0].name → 先取数组第 0 项,再取它的 name 属性

2.4 值与引用:一个容易踩的坑

这是新手最常遇到的问题之一!

基本类型(string、number、boolean)赋值 = 复制一份全新的数据:

javascript
let a = 10
let b = a      // b 得到 a 的副本
b = 20
console.log(a) // 10(a 不受影响)

对象和数组赋值 = 复制的是"地址"(指向同一个东西):

javascript
let user1 = { name: "张三" }
let user2 = user1      // user2 指向同一个对象
user2.name = "李四"     // 修改 user2 会影响 user1
console.log(user1.name) // "李四"(user1 也变了!)

为什么要创建副本?

在 React/Vue 中,直接修改数据会导致界面不更新。所以 AI 代码里经常看到 [...array]{...obj}——它在创建副本,避免互相影响。

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]

👇 动手试试看:观察修改副本时原数据的变化

🔄 值 vs 引用
基本类型(复制值)
a10
b?
点击复制
引用类型(复制地址)
obj10x001
obj2?
0x001
{ age: 25 }
点击复制
基本类型
let a = 10
let b = a  // b=10
b = 20     // a还是10
引用类型
let obj1 = {age:25}
let obj2 = obj1
obj2.age=30 // obj1也变了!
// 用 {...obj1} 复制

2.5 解构与展开:现代 JavaScript 的快捷写法

这两个语法在 AI 代码里到处都是,不认识就读不懂代码。

解构赋值:从对象或数组里快速提取数据

javascript
const user = { name: "张三", age: 25, city: "北京" }

// 传统写法(麻烦)
const name = user.name
const age = user.age

// 解构写法(简洁)
const { name, age } = user
// 效果一样,但一行搞定

展开运算符:复制并扩展数据

javascript
// 复制数组并添加新元素
const arr1 = [1, 2, 3]
const arr2 = [...arr1, 4, 5]  // [1, 2, 3, 4, 5]

// 复制对象并添加新属性
const user1 = { name: "张三", age: 25 }
const user2 = { ...user1, city: "北京" }
// { name: "张三", age: 25, city: "北京" }

💡 识别技巧

  • 看到 const { name, age } = person → 从 person 对象里提取 name 和 age
  • 看到 ...array...obj → 把数组或对象展开铺平
  • 你不需要能手写,但必须能读懂

3. 逻辑篇:函数与流程控制

🤔 核心问题

代码是怎么"做决定"和"重复做事"的? 程序需要根据条件执行不同的操作,也需要重复执行某些任务——这些逻辑怎么表达?

3.1 条件判断:如果...就...否则...

if/else:最基本的条件判断

javascript
const age = 18

if (age >= 18) {
  console.log("成年人")
} else {
  console.log("未成年")
}

三元运算符:简写的 if/else

javascript
// 完整写法(4 行)
let message
if (age >= 18) {
  message = "成年人"
} else {
  message = "未成年"
}

// 三元运算符(1 行)
const message = age >= 18 ? "成年人" : "未成年"
// 格式:条件 ? 条件为真时的值 : 条件为假时的值

&& 短路写法:React 代码里常见

javascript
// 只有 isLoggedIn 为 true 时才显示用户面板
isLoggedIn && <UserPanel />

// 等价于
if (isLoggedIn) {
  return <UserPanel />
}

💡 识别技巧

  • 看到 ? : → 这是三元运算符,简写的 if/else
  • 看到 && → 前面为 true 才执行后面

3.2 函数:把操作打包起来

函数 = 一道菜的配方

  • 定义函数 = 写下配方
  • 调用函数 = 按配方做菜
  • 参数 = 原料
  • 返回值 = 成品
javascript
// 定义函数(写下配方)
function greet(name) {
  return "Hello " + name
}

// 调用函数(按配方做菜)
console.log(greet("张三"))  // "Hello 张三"
console.log(greet("李四"))  // "Hello 李四"

三种写法,一眼识别:

javascript
// 1. function 声明(传统写法)
function greet(name) {
  return "Hello " + name
}

// 2. 箭头函数(AI 代码里用得最多)
const greet = (name) => {
  return "Hello " + name
}

// 3. 箭头函数简写(只有一行时)
const greet = (name) => "Hello " + name

👇 动手试试看:输入不同的名字,看看函数怎么工作

函数就像一台机器

参数(输入)

函数

calculatePrice
price * discount

返回值(输出)

?

当前函数定义

const calculatePrice = (price, discount) => {
  return price * discount
}

// 或者更简洁:
const calculatePrice = (price, discount) => price * discount

💡 识别技巧

  • 看到 function=> → 这是一个函数
  • 看到 fn() → 在调用这个函数
  • 看到 () => {} → 箭头函数,现代 JS 的主流写法

3.3 数组方法:处理列表的利器

在 React/Vue 里,几乎每个列表渲染都会用到这些方法。

javascript
const todos = [
  { id: 1, text: "学习", done: false },
  { id: 2, text: "工作", done: true }
]

// .map():把数组的每一项变成另一个东西
const texts = todos.map(todo => todo.text)
// ["学习", "工作"]

// .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 }

💡 识别技巧

  • 看到 .map() → 对数组做变换,返回新数组
  • 看到 .filter() → 筛选数组
  • 看到 items.map(item => <li>{item.name}</li>) → 把每个数据项变成列表标签

3.4 作用域:变量的"可见范围"

用"房间"比喻:

  • 函数内部的变量就像房间里的东西,外面看不到
  • 但房间里的人可以看到走廊(外层作用域)的东西
javascript
const global = "全局变量"  // 走廊里的东西

function room() {
  const local = "房间里的东西"  // 房间里的东西
  console.log(global)  // ✅ 能看到走廊
}

console.log(local)  // ❌ 报错!外面看不到房间里的东西

核心直觉: 代码写在哪里,决定了它能看到什么变量。

👇 动手试试看:点击不同的作用域,看看能访问哪些变量

🔍 作用域:变量的"可见范围"

全局作用域
appName= "Todo"
函数 greet() 作用域
appName= "Todo"← 全局
message= "你好"
if 块作用域
appName= "Todo"← 全局
message= "你好"← 函数
greeting= message+appName
💡 当前位置可见的变量
在全局作用域,只能使用全局变量 appName

对应代码

const appName = "Todo"  // 全局作用域

function greet() {
  const message = "你好"  // 函数作用域

  if (true) {
    const greeting = message + appName  // 块级作用域
    console.log(greeting)
  }

  console.log(greeting)  // ❌ 报错!外层看不到内层
}

3.5 闭包:函数"记住"了它诞生时的环境

不要把它当成独立的概念,从一个具体场景理解:

javascript
function setupCounter() {
  let count = 0  // 这个变量在函数内部

  return {
    add: () => { count++; return count },
    getCount: () => count
  }
}

const counter = setupCounter()
console.log(counter.add())      // 1
console.log(counter.add())      // 2
console.log(counter.getCount()) // 2

核心直觉: 函数在被创建时,会"记住"它周围的变量,即使外层函数已经执行完了。

👇 动手试试看:观察闭包如何让函数"记住"状态

🎁函数与闭包理解作用域链和闭包机制
想象你有个背包(函数),每次出门时都会把当时看到的 风景(外部变量)装进去。 闭包就是这个背包——即使离开了那个地方,你依然能拿出当时装的风景
函数声明方式
// 1. 函数声明
function greet(name) {
return "Hello " + name
}
// 2. 函数表达式
const greet = function(name) {
return "Hello " + name
}
// 3. 箭头函数 (ES6)
const greet = (name) => {
return "Hello " + name
}
// 简化版(单行可省略 return)
const greet = name => "Hello " + name
试试调用函数
点击"调用"按钮看结果...
💡核心思想:函数是 JavaScript 中的一等公民,可以赋值给变量、作为参数传递、作为返回值。箭头函数更简洁,且不绑定自己的 this。

3.6 this:函数被谁调用

不讲复杂的绑定规则,只讲最常见的场景:

场景 1:在对象的方法里,this 指向这个对象

javascript
const user = {
  name: "张三",
  sayHi() {
    console.log("你好,我是" + this.name)  // this 指向 user
  }
}
user.sayHi()  // "你好,我是张三"

场景 2:在事件监听里,this 指向触发事件的元素

javascript
button.addEventListener('click', function() {
  console.log(this)  // this 指向 button 元素
})

// 但箭头函数不会改变 this
button.addEventListener('click', () => {
  console.log(this)  // this 指向外层的 this
})

💡 遇到问题怎么办?

如果 AI 代码里出现 this 相关的 bug(比如 Cannot read property of undefined),告诉 AI:"这个方法里的 this 指向不对,改成箭头函数或者用 bind"


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

🤔 核心问题

JavaScript 怎么跟网页"互动"? 怎么找到页面上的元素?怎么响应用户的点击、输入?怎么从服务器获取数据?

4.1 DOM:JavaScript 看到的网页

网页在 JavaScript 眼里是一棵"树",每个 HTML 标签都是树上的一个"节点"。

html
<html>
  <body>
    <h1>标题</h1>
    <p>段落</p>
    <ul>
      <li>项目1</li>
      <li>项目2</li>
    </ul>
  </body>
</html>

JS 操控网页 = 找到节点 + 修改节点 + 创建/删除节点

👇 动手试试看:点击节点,看看 DOM 树是怎么组织的

DOM 树:JavaScript 看到的网页

我的网页

欢迎光临

  • 项目1
  • 项目2
<html>
<body>
<h1>我的网页
<p>欢迎光临
<ul>
<li>项目1
<li>项目2

对应代码

点击上方按钮查看对应代码

4.2 查找与修改元素

查找元素:

javascript
// 根据 CSS 选择器查找(最常用)
const title = document.querySelector('h1')      // 找第一个 h1
const button = document.querySelector('#btn')   // 找 id="btn" 的元素
const items = document.querySelectorAll('.item') // 找所有 class="item" 的元素

修改元素:

javascript
// 改文字
title.textContent = "新标题"

// 改样式
element.style.color = "red"
element.style.fontSize = "20px"

// 改 CSS 类
element.classList.add('active')      // 添加类
element.classList.remove('hidden')   // 移除类
element.classList.toggle('open')     // 切换类(有就移除,没有就添加)

💡 识别技巧

  • 看到 document.querySelector → 在查找网页元素
  • 看到 .textContent → 改文字
  • 看到 .style.xxx → 改样式
  • 看到 .classList.add/remove/toggle → 改 CSS 类

4.3 事件:当用户做了某个操作时...

addEventListener:给元素添加事件监听

javascript
button.addEventListener('click', () => {
  console.log("按钮被点击了")
})

常见事件:

事件触发时机实际场景
click点击按钮点击、链接跳转
input输入框内容变化实时搜索、表单验证
submit表单提交登录、注册、提交数据
scroll滚动页面懒加载、回到顶部

事件对象:获取更多信息

javascript
input.addEventListener('input', (e) => {
  console.log(e.target.value)  // 获取输入框的值
  e.preventDefault()            // 阻止默认行为(比如表单提交后刷新页面)
})

💡 实际应用

当你想给按钮加一个功能,本质上就是在告诉 AI:"给这个按钮添加一个点击事件,点击后执行某某操作"

4.4 异步:为什么有些操作不是立刻完成的

餐厅比喻:

点菜后不用站在厨房门口等,可以先做别的事,菜好了服务员会端过来。

最常见场景:从服务器获取数据

javascript
// 同步写法(会卡住页面,不要用)
const data = fetch('/api/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 → 处理可能出现的错误

👇 动手试试看:观察异步操作的执行顺序

异步:同步 vs 异步

厨房

灶位 1
空闲
灶位 2
空闲
灶位 3
空闲

顾客

👤
顾客 A
煮面 (2秒)
👤
顾客 B
炒饭 (3秒)
👤
顾客 C
烤鱼 (5秒)

代码对比

同步(阻塞)
console.log("1")
console.log("2")  // 等上面执行完
console.log("3")
// 输出:1, 2, 3
异步(不阻塞)
console.log("1")
setTimeout(() => console.log("2"), 1000)
console.log("3")
// 输出:1, 3, 2

💡 识别技巧

  • 看到 async/await → 在等待耗时操作
  • 看到 fetch() → 在从服务器获取数据
  • 看到 try/catch → 在处理可能的错误

4.5 事件循环:JavaScript 到底怎么工作的

不用术语"微任务/宏任务",用一个简单的模型理解:

JS 是一个"单人工位",同时只做一件事,但有一个"待办便签栏"(任务队列)。

当遇到要等待的操作(网络请求、定时器),JS 不是傻等,而是把"等好了之后做什么"贴到便签栏,自己继续往下执行。等当前事情做完了,才去看便签栏。

javascript
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

👇 动手试试看:观察代码的执行顺序

事件循环:JavaScript 的执行机制

代码队列

1
console.log("1")
执行中
2
setTimeout(() => console.log("2"), 0)
3
console.log("3")
4
fetch("/api").then(() => console.log("4"))
5
console.log("5")

工位(单线程)

👨‍💻
正在执行
执行 console.log("1")

便签栏(任务队列)

暂无待办任务

输出日志

等待输出...

执行顺序:还未开始

代码书写顺序:1, 2, 3, 4, 5

代码从上到下写的,但执行顺序不一定从上到下——因为异步操作会被"推迟"到当前代码执行完之后。

💡 遇到问题怎么办?

如果 AI 代码里数据还没获取到页面就渲染了,告诉 AI:"数据还没加载完就开始渲染了,需要添加 loading 状态,等数据到了再渲染"

4.6 模块:import 和 export

AI 生成的 React/Vue 代码第一行几乎都是 import

import = 从别的文件引入功能

javascript
// 从工具文件引入函数
import { formatDate } from './utils'

// 从第三方包引入
import React from 'react'
import { useState } from 'react'

export = 把功能暴露出去给别人用

javascript
// utils.js
export function formatDate(date) {
  // ...
}

// 或者默认导出
export default function formatDate(date) {
  // ...
}

npm 包 = 别人写好的工具,安装后就能用

javascript
// 安装包:npm install lodash
// 使用包
import _ from 'lodash'

💡 识别技巧

  • 看到 import → 从别的文件引入功能
  • 看到 export → 把功能暴露给别人用
  • 看到 from 'react' → 从 React 包引入
  • 看到 from './utils' → 从本地文件引入

5. 实战篇:读懂代码、看懂报错、精准描述

🤔 核心问题

前面学了这么多语法,实际拿到 AI 代码时怎么用? 怎么快速读懂代码?遇到报错怎么办?怎么让 AI 准确地帮你改代码?

5.1 拿到 AI 代码后怎么读

四步法:

步骤看什么示例
第一步:看整体结构有几个函数?分别做什么?loadData() 加载数据,renderList() 渲染列表
第二步:找入口程序从哪里开始执行?addEventListener('click', ...) 点击时开始
第三步:追踪数据流数据从哪里来?到哪里去?从 API 获取 → 解析 → 渲染到页面
第四步:看细节逻辑具体函数里怎么处理的?循环、判断、计算

用第 1 章的代码示例做一次完整的"阅读演示":

javascript
// 第一步:整体结构
// - 一个颜色数组
// - 一个变量记录当前索引
// - 一个按钮的点击事件

// 第二步:入口点
// button.addEventListener('click', ...) → 点击按钮时执行

// 第三步:数据流
// colors(颜色数组)→ currentIndex(当前索引)→ backgroundColor(背景色)

// 第四步:细节逻辑
// currentIndex = (currentIndex + 1) % colors.length
// 这个公式的意思:每次 +1,但不超过数组长度(循环)

5.2 常见报错速查

报错大白话解释怎么跟 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,检查接口地址是否正确"

5.3 如何精准描述问题

新手和熟练开发者的差距,往往就体现在描述问题的精准度上。

❌ 差的描述✅ 好的描述
"代码有 bug""点击删除按钮时,删除的不是当前项而是最后一项"
"样式不对""标题应该居中,现在是左对齐"
"数据显示不出来""fetch 请求返回了数据(控制台能看到),但页面没有重新渲染"
"加一个功能""在用户列表页面添加一个搜索框,输入时实时过滤列表,按 name 字段模糊匹配"
"点击没反应""点击按钮时控制台报错 'Cannot read property of undefined',错误在第 X 行"

一个实战练习:

javascript
// 有 bug 的代码
function deleteTodo(index) {
  todos.splice(index, 1)  // 总是删除最后一项
}

// 错误现象:无论点哪个删除按钮,删的都是最后一项

❌ 差的描述: "删除功能有 bug"

✅ 好的描述: "点击删除按钮时,删除的不是当前项而是最后一项。代码里用了 splice(index, 1),但 index 可能不正确。需要改成用每个事项的唯一 id 来匹配删除。"

5.4 你现在应该能识别的代码

  • 看到 const/let → 知道变量能不能重新赋值
  • 看到 {} → 对象 / 看到 [] → 数组
  • 看到 {...obj}[...arr] → 在创建副本
  • 看到 function=> → 定义了一段可重复执行的操作
  • 看到 if/else? : → 代码在做判断
  • 看到 .map() / .filter() → 在变换或筛选数组
  • 看到 document.querySelector → 在查找网页元素
  • 看到 addEventListener → 在监听用户操作
  • 看到 async/await → 在等待耗时操作
  • 看到 import/export → 在引入或导出模块
  • 遇到报错 → 能读懂大意并精准描述给 AI

如果你认真读了每章的"深入"部分,你还掌握了这些核心概念:

  • 值 vs 引用:基本类型复制值,对象/数组复制的是地址
  • 作用域与闭包:函数能"记住"它诞生时周围的变量
  • this 的本质:取决于函数被谁调用,而不是写在哪里
  • 事件循环:JS 是单线程的,靠任务队列实现"不阻塞"

这些概念会帮你更快定位问题。

💡 遇到问题时这样跟 AI 说

  • "第 X 行报错 XXX,帮我看看是什么问题"
  • "这个函数的逻辑是 XXX,但结果不对,应该是 XXX"
  • "我想修改 XXX 功能,具体要求是 XXX"