夜雨聆风学习资料网

ARTICLE · 1112932

AI编程必知必会(第二期)|菜鸟错题本

AI编程必知必会(第二期)|菜鸟错题本

LAW × AI

法律、AI技术·研究、应用

RESEARCH · PRACTICE · TECH NOTES

Vibe Coding 

底层知识补全 · 第二期

从「能跑就行」到「能读懂、能调试、能改」

杨显光 · 2026-10-2

第一期我们把操作系统层打通了:命令行、PATH、ENV、部署。这一期往代码层走——让你看到一段代码时不再发懵,遇到 bug 时不再瞎试,改别人的代码时不再手抖。

01

PART ONE

一、从环境到代码,你还缺什么

第一期解决了「为什么跑不起来」,第二期解决「为什么它是这么写的」。

代码阅读能力差,通常卡在这四点

卡点
典型表现
对应章节
不知道怎么「看」代码
只会从上往下读,一遇到异步、回调就断线
②③
看不懂框架的「魔法」
一个 @app.get 就绑定了路由?凭什么?
④⑩
不会调试,只能瞎试
报错了就改一下刷新、再改一下、再刷新
②⑧
协议和工具不熟
不懂 HTTP 状态码、不会用 Git、SQL 只会 SELECT *
⑤⑥⑦

🔑 本期核心心法

「读代码」不是逐行看,而是「追踪数据流」。一个变量从哪来,经过哪些函数,变成什么样,最后去了哪——把这条线抓住,代码就不再是天书,而是一条可以跟着走的路径。本期所有章节,都是在帮你练这条线。

02

PART TWO

二、调试:不会调试 = 不会编程

这是你最该补上的一课,没有之一。

vibe coding 的人有个通病:遇到 bug 就改一下、刷新、再改一下、再刷新。这种「盲改」方式,运气好能撞对,运气差就陷入死循环。真正的高手用的是调试——让程序自己告诉你哪里错了。

console.log 的正确姿势

① 给日志加标签,否则你分不清谁是谁

JAVASCRIPT

// ❌ 差

console.log(user)

console.log(data)

console.log(res)

// ✅ 好

console.log('用户信息:', user)

console.log('请求数据:', data)

console.log('接口返回:', res)

② 用 console.table 看数组

JAVASCRIPT

const users = [

  { name: '张三', age: 20 },

  { name: '李四', age: 25 },

]

console.log(users)      // 展开一个个看,很累

console.table(users)    // ✅ 直接变成表格

③ 用 console.time 测性能

JAVASCRIPT

console.time('请求耗时')

await fetchData()

console.timeEnd('请求耗时')

// 输出:请求耗时: 234.56ms

④ 用 debugger 打断点(比 log 更强)

JAVASCRIPT

function handleLogin(form) {

  debugger   // 浏览器执行到这里会自动停下

  const res = validate(form)

  // 此时你能看到所有变量的值,还能一步步往下走

}

✅ debugger 相比 console.log 的优势

• 能看所有变量的当前值,不用一个个打印

• 能一步步执行,看代码怎么走的

• 能修改变量的值再继续跑

• 不污染代码(用完记得删)

浏览器 DevTools 五大面板

F12 打开它,看懂这五个面板,你的调试能力立刻翻倍。

面板
用来干什么
最常用的操作
Elements
看 HTML 结构、改样式
右键「检查」定位元素;实时改 CSS
Console
看日志、执行 JS
直接敲 JS 试代码;看报错
Network看所有网络请求
(最重要)
看接口参数、返回值、状态码、耗时
Application
看存储
查看 localStorage、sessionStorage、Cookie
Sources
断点调试
在源码上点行号打断点;单步执行

Network 面板必会技巧

前后端联调,90% 的问题都靠它定位。

• 打开 Network,勾选 Preserve log页面跳转后请求记录不会消失,能看到完整链路。

• 按 Fetch/XHR 筛选,只看接口请求不然图片、CSS、JS 一大堆,看不清。

• 点开一个请求,看四个地方- Headers — 请求地址对不对?带了 token 吗?- Payload — 传的参数是什么?- Response — 后端返回了什么?- Status — 状态码是多少?

💡 一个真实场景

前端报「登录失败」,你不知道哪错了。打开 Network → 点开 /api/login:

• Status 是 404 → 后端没这个路由(路径写错)

• 401 → 密码错或 token 无效

• 500 → 后端崩了,去看后端日志

• Status 200 但 Response 里 code: -1 → 业务逻辑错误,看返回的 message

Python 断点调试

Python 也有类似 debugger 的利器:breakpoint()(Python 3.7+ 内置)。

PYTHON

def handle_order(order_id):

    order = db.get(order_id)

    breakpoint()   # 执行到这里会进入交互式调试器

    total = calc_total(order)

    return total

运行后程序会停在 (Pdb) 提示符,常用命令:

命令
作用
n
(next)
执行下一行
s
(step)
进入函数内部
c
(continue)
继续执行到下一个断点
p 变量名
打印变量的值
pp 变量名
美化打印(适合字典/对象)
l
(list)
显示当前代码上下文
q
(quit)
退出调试

✅ VS Code 里更爽

在 VS Code 里直接点行号左边打个红点(断点),按 F5 启动调试。鼠标悬停就能看变量值,不用敲命令。

🔑 调试的核心思维

不要猜,要让程序告诉你。每当你想「改一下试试」的时候,停下来问自己:「我能不能先打印/断点,确认问题到底出在哪?」这个问题问多了,你就从「盲改」升级成「精准定位」了。

03

PART THREE

三、读懂异步 JavaScript

这是 JS 代码阅读的第一大障碍。

你读 JS 时最常卡住的地方是不是这种:

JAVASCRIPT

fetchUser().then(user => {

  fetchOrders(user.id).then(orders => {

    fetchDetail(orders[0].id).then(detail => {

      console.log(detail)

    })

  })

})

一层套一层,读着读着就不知道自己在哪了。

为什么需要异步

JS 是单线程的——一次只能做一件事。但网络请求可能要等几百毫秒,如果傻等,页面就卡死了。

类比

你在餐厅点了一份牛排。同步:厨师做完牛排才去接下一单,你等 10 分钟,别人也跟着等。异步:厨师把牛排下锅后,先去做别的单子,牛排好了再端给你。异步的核心就是:不要堵住主线程。等结果的时候,去做别的事。

回调 → Promise → async/await 的进化

同样是「先拿用户,再拿订单」,三种写法对比:

阶段一:回调函数(callback)

JAVASCRIPT

fetchUser(function(err, user) {

  if (err) { return console.error(err) }

  fetchOrders(user.id, function(err, orders) {

    if (err) { return console.error(err) }

    console.log(orders)

  })

})

问题:嵌套深、错误处理重复、难维护。俗称「回调地狱」。

阶段二:Promise

JAVASCRIPT

fetchUser()

  .then(user => fetchOrders(user.id))

  .then(orders => console.log(orders))

  .catch(err => console.error(err))   // 统一错误处理

好处:链式调用、扁平化、一个 catch 捕获所有错误。

阶段三:async/await(现代写法,最推荐)

JAVASCRIPT

async function loadData() {

  try {

    const user = await fetchUser()

    const orders = await fetchOrders(user.id)

    console.log(orders)

  } catch (err) {

    console.error(err)

  }

}

看起来像同步代码一样从上到下,但实际上是异步的。这就是为什么现代项目里你看到的多半是 async/await。

✅ 阅读 async/await 的秘诀

你就把 await 理解成「等这个操作完成,把结果赋给左边」。遇到 await 就停下来想:这个函数返回什么?赋值给了谁?其他的阅读方式和普通同步代码一模一样。

事件循环:理解异步的关键

为什么下面这段代码输出顺序不是 1、2、3?

JAVASCRIPT

console.log('1')

setTimeout(() => console.log('2'), 0)

console.log('3')

// 实际输出:1 → 3 → 2

哪怕 setTimeout 的延迟是 0,它也不会立刻执行。原因就是事件循环。

类比

想象一个服务员(JS 主线程)和一块后厨订单板(任务队列)。所以 setTimeout 里的回调一定会等到当前所有同步代码跑完才执行。

• 服务员先按顺序做前台的活(同步代码)

• 遇到「需要等」的事(setTimeout、网络请求),就写个订单贴到后厨板上,然后继续做前台

• 前台活全部做完后,服务员才回头看后厨板上有没有完成的订单

简化版的执行顺序:

执行同步代码 → 清空微任务队列(Promise.then) → 执行宏任务(setTimeout) → 循环

看一个更完整的例子:

JAVASCRIPT

console.log('A')

setTimeout(() => console.log('B'), 0)

Promise.resolve().then(() => console.log('C'))

console.log('D')

// 输出:A → D → C → B

// 因为 Promise.then 是微任务,优先级高于 setTimeout 宏任务

⚠️ 一个超容易踩的坑

在循环里用 var + setTimeout,会输出一堆相同的值:原因:var 没有块级作用域,三个回调共享同一个 i,等它们执行时循环已结束,i 已经是 3。解决:把 var 换成 let,或者用闭包包起来。

JAVASCRIPT

for (var i = 0; i < 3; i++) {

  setTimeout(() => console.log(i), 0)

}

// 输出:3 3 3(不是 0 1 2!)

🔑 读异步代码的三个问题

每遇到一段异步代码,问自己:① 这段代码是同步还是异步?(看有没有 await / .then / 回调)② 异步的部分什么时候执行?(微任务还是宏任务?)③ 它执行时,依赖的变量还是当时的值吗?回答完这三个问题,90% 的异步代码你都能读懂。

04

PART FOUR

四、读懂框架的「魔法」

一个装饰器凭什么就绑定了路由?

你写 FastAPI 时,是不是觉得这行代码像魔法:

PYTHON

@app.get("/users")

def get_users():

    return [{"name": "张三"}]

一个 @app.get 加在函数上,它就变成了一个 HTTP 接口。

Python 装饰器:@ 的真面目

🔑 装饰器的本质

@decorator 加在函数上,等价于 func = decorator(func)。就是这么简单——它把函数当成参数传进去,返回一个新的函数替换原来的。

看一个最小的例子:

PYTHON

def log_time(func):

    def wrapper(*args, **kwargs):

        print(f"开始执行 {func.__name__}")

        result = func(*args, **kwargs)

        print(f"结束执行")

        return result

    return wrapper

# 下面两种写法完全等价

# 写法一:手动包装

def say_hi():

    print("你好")

say_hi = log_time(say_hi)

say_hi()

# 写法二:用 @ 语法糖

@log_time

def say_hi2():

    print("你好")

say_hi2()

现在回头看 @app.get

FastAPI 的 @app.get("/users") 做了三件事:

01接收路径字符串"/users"
02返回一个装饰器(这个装饰器记住了路径)
03装饰器把函数注册到一个「路由表」里相当于内部执行了:路由表["/users"] = get_users

所以真正的逻辑是:框架维护了一个路由表,装饰器帮你往表里加了一条记录。当请求进来时,框架查表找到对应的函数去执行。

✅ 以后看到装饰器,这样读

不要被 @ 迷惑。就想:「这个函数被包装了,可能加了额外行为,也可能被注册到某个地方了」。

Vue 组合式 API:为什么要有 ref 和 .value

你写 Vue 3 时,是不是经常忘了 .value 导致数据不更新?

JAVASCRIPT

import { ref } from 'vue'

const count = ref(0)

count.value++       // ✅ 正确:必须 .value

console.log(count.value)

// 在 template 里却不用写 .value

// <template>{{ count }}</template>

原因:JS 无法监听一个普通变量的变化

JAVASCRIPT

let count = 0

count = 1       // Vue 完全不知道这个变量变了

JS 里对一个普通变量赋值,没有任何机制能通知 Vue「该更新视图了」。所以 Vue 换了个思路:不让你直接改变量,而是让你改一个对象。

JAVASCRIPT

// ref(0) 实际上返回的是类似这样:

{

  get value() {

    追踪()        // 记录:有人用了这个值

    return 内部的值

  },

  set value(新值) {

    内部的值 = 新值

    触发更新()    // 通知:值变了,重新渲染

  }

}

当你写 count.value = 1 时,实际调用的是 set value,Vue 就能拦截到这次赋值。

类比

普通变量就像你家的电灯开关,拉一下灯亮,但没人知道。ref 就像装了个智能开关,你拉一下,手机 App 会收到通知。

为什么 template 里不用 .value?因为 Vue 的模板编译器会自动帮你加。

场景
写法
原因
<script setup>
 里
count.value
JS 里没有编译魔法,手动加
<template>
 里
count
编译器自动加 .value
reactive()
 对象
obj.count
对象本身就是响应式的

💡 reactive vs ref

• 基本类型(数字、字符串、布尔)→ 用 ref

• 对象/数组,希望直接改属性 → 用 reactive

• 团队约定:都用 ref 更统一

🔑 读框架代码的通用心法

所有「魔法」背后都是普通代码:遇到看不懂的语法,就去查它的等价普通写法。

• 装饰器 = 函数包装

• 响应式 = getter/setter 拦截

• 路由 = 一张映射表

• 依赖注入 = 一个全局容器

05

PART FIVE

五、HTTP 协议实战

前后端之间的「语言」,不学真的没法调试。

状态码:一眼看出问题出在哪边

范围
含义
常见状态码
2xx
成功
200
 成功 · 201 创建成功 · 204 无内容
3xx
重定向
301
 永久 · 302 临时 · 304 缓存有效
4xx客户端错400
 参数错 · 401 未登录 · 403 无权限 · 404 找不到 · 422 校验失败
5xx服务端错500
 后端崩溃 · 502 网关错 · 504 超时

✅ 快速定位法

看到 4xx → 先查前端:参数对不对、路径对不对、有没有带 token看到 5xx → 立刻看后端日志:一般是有异常没被捕获看到 200 但业务失败 → 是业务层错误,看返回的 message

Header 与 Cookie

Header
方向
作用
Content-Type
双向
请求/响应的数据格式
Authorization
请求
身份凭证,通常是 Bearer <token>
Cookie
请求
浏览器自动带上
Set-Cookie
响应
服务器让浏览器种 cookie
User-Agent
请求
客户端信息
Origin
请求
请求来自哪个域(CORS 用)
Access-Control-Allow-Origin
响应
允许哪些域访问(CORS 用)

Cookie / Session / Token 的区别

方式
存在哪
怎么工作
优缺点
Cookie
浏览器
服务器 Set-Cookie,浏览器自动带上
简单,有 CSRF 风险
Session
服务器
服务器存数据,给浏览器 sessionId
安全,但服务器要存储
Token / JWT
浏览器
服务器签发加密串,前端每次带上
无状态,但无法主动失效

类比

• Cookie = 你在咖啡店办的纸质卡,每次去都带着

• Session = 店里有个本子记录你,你只带一个会员号

• JWT = 一张加密的电子凭证,谁都能验证真伪

JWT:现代前后端最常用的方案

eyJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOjEyMywiZXhwIjoxNzE5fQ.abc123

   ├── Header ──┤ ├─────── Payload ──────┤ ├─ Signature ─┤

部分
内容
是否加密
Header
算法信息
Base64(可解)
Payload
用户信息、过期时间
Base64(可解,别放敏感信息)
Signature
签名,防篡改
用密钥加密,无法伪造

完整流程

前端登录 → 后端验证密码 → 生成 JWT 返回 → 前端存 localStorage

前端请求 → Header 带 Token → 后端验证签名 → 返回数据

前端统一加 token:

JAVASCRIPT

axios.interceptors.request.use(config => {

  const token = localStorage.getItem('token')

  if (token) {

    config.headers.Authorization = `Bearer ${token}`

  }

  return config

})

⚠️ Token 存哪里?

localStorage:简单,但 XSS 攻击能偷走。Cookie + HttpOnly:JS 读不到,更安全,但有 CSRF 风险。简单项目:localStorage 够用。涉及支付的:必须用 HttpOnly Cookie + CSRF Token。

CORS 跨域:为什么本地一直报错

Access to XMLHttpRequest at 'http://localhost:8000/api/users'

from origin 'http://localhost:5173' has been blocked by CORS policy

浏览器为什么要拦截

想象你在 bank.com 登录了,然后打开了一个恶意网站 evil.com。如果没有同源策略,evil.com 就能用你的登录态偷偷请求 bank.com/api/transfer。

所以浏览器规定:一个源的页面,不能随便请求另一个源的接口。这是浏览器的行为,不是服务器拒绝你。

同源的定义

协议 + 域名 + 端口,三者完全一样才算同源。

页面地址
请求地址
是否同源
原因
http://a.comhttp://a.com/api
✅
完全一样
http://a.comhttps://a.com/api
❌
协议不同
http://a.comhttp://b.com/api
❌
域名不同
http://a.comhttp://a.com:8000/api
❌
端口不同

解决方案:三种,按场景选

开发环境:用 Vite proxy

JAVASCRIPT

server: {

  proxy: {

    '/api': {

      target: 'http://localhost:8000',

      changeOrigin: true,

    }

  }

}

生产环境:用 Nginx 反向代理

NGINX

location /api/ {

  proxy_pass http://127.0.0.1:8000;

}

真正需要跨域时:后端设置 CORS 头

PYTHON

from fastapi.middleware.cors import CORSMiddleware

app.add_middleware(

    CORSMiddleware,

    allow_origins=["https://yourdomain.com"],

    allow_credentials=True,

    allow_methods=["*"],

    allow_headers=["*"],

)

❗ 别用 allow_origins=["*"] + allow_credentials=True

浏览器不允许这种组合。生产环境必须明确列出允许的域名。

🔑 记住这个本质

CORS 是浏览器的安全机制,不是服务器的问题。报跨域错时,不是「后端拒绝了你」,而是「浏览器不让浏览器发这个请求」。解决思路就是:让请求看起来同源——用 proxy 或 Nginx 反代。

06

PART SIX

六、Git:代码的时光机

不会 Git,等于没有版本控制的编程。

四个核心概念

工作区 → git add → 暂存区 → git commit → 本地仓库 → git push → 远程仓库

类比

Git 像一个「拍照存档系统」:

• 工作区 = 你正在改的稿子

• git add = 把想存档的页面挑出来

• git commit = 按下快门,拍一张照片存档

• git push = 把照片上传到云端

日常工作流(90% 时间只用这几条)

BASH

git status                # 查看状态(最常用!)

git diff                  # 看改了什么

git add .                 # 添加全部

git add src/main.js       # 添加单个

git commit -m "feat: 添加登录功能"

git push

git pull

读懂 git diff

diff --git a/src/main.js b/src/main.js

index 1234567..89abcde 100644

--- a/src/main.js

+++ b/src/main.js

@@ -10,7 +10,9 @@ function login() {

   const user = getCurrentUser()

-  if (user) {

-    return user.name

+  if (user && user.isActive) {

+    console.log('登录成功')

+    return user.name

   }

   return null

 }

符号
含义
---
 / +++
旧文件 / 新文件
@@ ... @@
这段改动的位置
-
 开头的行
被删除的内容
+
 开头的行
新增的内容
无符号的行
上下文,用来定位

分支:并行开发的利器

BASH

git branch                        # 看所有分支

git switch -c feature-login       # 创建并切换

git switch main                   # 切回主分支

git merge feature-login           # 合并分支到当前

git branch -d feature-login       # 删除已合并分支

出错了怎么回滚(救命操作)

场景
命令
改乱了某个文件,想还原
git checkout -- 文件名
add 错了文件,想撤销
git reset HEAD 文件名
刚 commit 完想改消息
git commit --amend -m "新消息"
撤销最近一次 commit(保留改动)
git reset HEAD~1
彻底删除最近一次 commit
git reset --hard HEAD~1
(危险)
已 push 了想回滚
git revert <commit-hash>

❗ --hard 很危险

git reset --hard 会彻底丢掉未提交的修改,找都找不回来。敲之前一定确认:git status 看看有没有未提交的重要内容。

冲突怎么办

<<<<<<< HEAD

// 你的版本

const API_URL = 'http://localhost:8000'

=======

// 别人的版本

const API_URL = 'https://api.example.com'

>>>>>>> feature-branch

处理方法:

01打开冲突文件,找到 <<<<<<< 到 >>>>>>> 之间的部分
02决定用哪个版本(或者改成两者结合)
03删掉那些 <<<<<<、======、>>>>>>> 标记行
04git add 冲突文件
05git commit 完成合并

✅ 提交信息规范

feat: 新功能 · fix: 修复 bug · docs: 文档style: 格式 · refactor: 重构 · chore: 杂项例子:fix: 修复登录页手机号校验错误

07

PART SEVEN

七、数据库与 SQL 基础

做网站一定会碰,绕不过去。

为什么需要数据库

你写的 Python 后端,重启一次数据就没了——因为内存不持久。数据库就是「把数据存到磁盘上」的工具。

类型
代表
适合
关系型(SQL)
MySQL、PostgreSQL、SQLite
结构化数据、需要事务
文档型(NoSQL)
MongoDB
结构不固定
键值型
Redis
缓存、会话、排行榜

Web 项目里,90% 的场景用关系型数据库。

SQL 核心语法(记住这 5 条就够用)

1. 查询:SELECT

SQL

SELECT * FROM users

SELECT id, name, email FROM users WHERE age > 18

SELECT * FROM users WHERE name LIKE '张%' ORDER BY created_at DESC LIMIT 10

2. 插入:INSERT

SQL

INSERT INTO users (name, email, age)

VALUES ('张三', 'zhang@example.com', 25)

3. 更新:UPDATE

SQL

UPDATE users SET age = 26 WHERE id = 1

❗ UPDATE / DELETE 一定要带 WHERE

UPDATE users SET age = 26(不带 WHERE)会把所有用户的年龄都改成 26。养成习惯:先写 WHERE,再补前面的部分。

4. 删除:DELETE

SQL

DELETE FROM users WHERE id = 1

5. 关联查询:JOIN

SQL

SELECT u.name, o.order_no, o.total

FROM users u

JOIN orders o ON o.user_id = u.id

WHERE u.id = 1

ORM:为什么后端代码里看不到 SQL

PYTHON

# SQLAlchemy(Python 最常见的 ORM)

users = db.query(User).filter(User.age > 18).all()

# 或新版本

from sqlalchemy import select

result = await db.execute(select(User).where(User.age > 18))

users = result.scalars().all()

类比

直接写 SQL 像用键盘敲中文拼音;用 ORM 像用输入法——你敲拼音它也翻译成中文,方便但不改变本质。

ORM 基本操作对照

操作
SQL
SQLAlchemy
查询全部
SELECT * FROM usersdb.query(User).all()
条件查询
SELECT * FROM users WHERE age > 18db.query(User).filter(User.age > 18).all()
查一条
SELECT * FROM users WHERE id = 1db.query(User).get(1)
新增
INSERT INTO users ...db.add(User(name="张三")); db.commit()
删除
DELETE FROM users WHERE id = 1db.delete(user); db.commit()

✅ 读懂 ORM 代码的关键

看到 db.query(User) 就翻译成 SELECT * FROM users,看到 .filter(...) 就翻译成 WHERE ...,看到 .all() 就是执行查询。ORM 只是语法糖,背后还是 SQL。

连接池

建立数据库连接很慢(几十毫秒)。所以后端启动时会预先创建一批连接放在池子里,用完不关,还回去。

这也解释了为什么 .env 里的 DATABASE_URL 那么重要——它决定后端连的是哪个数据库。

08

PART EIGHT

八、读懂报错信息

90% 的答案就在报错里,只是你不会读。

Python 报错(traceback):从下往上读

Traceback (most recent call last):

  File "app.py", line 25, in <module>

    main()

  File "app.py", line 18, in main

    result = calculate(data)

  File "app.py", line 8, in calculate

    return a / b

ZeroDivisionError: division by zero

部分
含义
第一行
固定格式
中间的 File "..."
调用链,从外层到内层
最后一行错误类型 + 描述
(最重要)

读 traceback 的正确姿势是从下往上:

01先看最后一行的错误类型(ZeroDivisionError)和描述(division by zero)
02往上看,找到最靠近底部的那一行你自己的代码(app.py line 8)
03再往上,看这行代码被谁调用的(app.py line 18)
04这样你就知道了:哪一行、因为什么、被谁调的

JS 报错:从第一行读

Uncaught TypeError: Cannot read properties of undefined (reading 'name')

    at login (main.js:25)

    at handleClick (main.js:42)

    at HTMLButtonElement.onclick (index.html:10)

部分
含义
第一行
错误类型 + 描述
at ...
调用栈,从上到下:第一个是出错位置

常见错误类型速查

Python

错误
含义
常见原因
SyntaxError
语法错误
少了括号、缩进不对
NameError
名字没定义
变量名拼错 / 忘记 import
TypeError
类型错误
字符串和数字相加、调用 None 的方法
KeyError
字典键不存在
用了不存在的 key
IndexError
索引越界
list[5] 但只有 3 个元素
AttributeError
属性不存在
对象没有这个属性 / 方法
ImportError
导入失败
没装包、路径不对
ValueError
值不合法
int("abc")

JavaScript

错误
含义
常见原因
TypeError
类型错误
undefined.name
、不是函数却调用
ReferenceError
引用未定义
用了不存在的变量
SyntaxError
语法错误
少了括号、引号没配
RangeError
范围错误
数组长度负数、递归太深
Uncaught (in promise)
Promise 错误没捕获
忘了 .catch 或 try/catch

排查报错的通用四步

01看最后一行 / 第一行:到底是什么错误类型?
02定位到文件行号:打开那一行,看上下文。
03加打印 / 断点:在出错位置之前打印关键变量。这一步是新手最容易跳过的,但恰恰最有效。
04看文档 / 搜索:错误信息 + 框架名,90% 有人遇到过。

🔑 一个思维转变

把报错信息当成「程序在向你求救」,而不是「失败的证据」。它告诉你:「我在这里,遇到了这个问题」。看懂它,你就多了一个永远在线的老师。

09

PART NINE

九、项目分层与数据流

为什么代码要分目录,怎么快速找到某段逻辑。

典型后端分层(以 FastAPI 为例)

backend/

├── main.py           ← 入口:创建 app,注册路由中间件

├── routers/          ← 路由层:定义 URL 和请求参数

│   └── user.py         @router.get("/users")

├── schemas/          ← 数据校验层:定义请求/响应的数据结构

│   └── user.py         class UserCreate(BaseModel)

├── services/         ← 业务逻辑层:真正干活的地方

│   └── user_service.py   def create_user(data): ...

├── models/           ← 数据模型层:对应数据库表

│   └── user.py         class User(Base)

├── db/               ← 数据库连接

└── utils/            ← 工具函数

一次「创建用户」的请求怎么走

HTTP 请求 → routers/user.py → services/user_service.py → models/user.py → 数据库

每层的职责

层
只做
不做
routers
接收请求、调用 service、返回响应
不写业务逻辑、不直接操作数据库
services
业务逻辑、协调多个 model
不关心 HTTP、不处理请求参数
models
定义数据库表结构
不写业务逻辑
schemas
定义请求/响应的数据结构、校验
不碰数据库

✅ 找代码的快速方法

• 想找「这个接口怎么处理请求」→ 看 routers

• 想找「这个业务逻辑在哪」→ 看 services

• 想找「这个字段在数据库是什么类型」→ 看 models

• 想找「这个参数怎么校验」→ 看 schemas

前端典型分层(Vue)

src/

├── main.js           ← 入口:挂载 app,注册路由和全局状态

├── App.vue

├── router/           ← 路由配置

├── stores/           ← 全局状态(Pinia)

├── api/              ← 接口封装:所有 HTTP 请求集中在这

│   └── user.js         export const login = (data) => ...

├── views/            ← 页面组件

├── components/       ← 可复用的 UI 组件

└── utils/            ← 工具函数

    └── request.js      axios 实例、拦截器

一次「登录」的前端数据流

Login.vue(用户点击)

  → api/user.js(调用 login)

  → utils/request.js(axios 发请求)

  → 后端接口

  → 返回 token

  → stores/user.js(存 token)

  → router.push(跳转首页)

🔑 读项目代码的正确顺序

自上而下(Top-down):不要从下往上读,会迷失在细节里。

• 先看入口(main.py / main.js),知道项目怎么启动

• 再看路由(routers / router),知道有哪些功能

• 找到你要改的功能对应的路由

• 顺着它调用的 service / api 往下读

• 最后看它怎么操作数据(models / stores)

10

PART TEN

十、Vue 响应式的坑

数据改了页面没更新?问题都在这。

坑一:忘了 .value

JAVASCRIPT

const count = ref(0)

// ❌ 忘了 .value,等于给 count 重新赋值

count = 1

// ✅ 正确

count.value = 1

坑二:解构 reactive 对象

JAVASCRIPT

const state = reactive({ count: 0, name: '张三' })

// ❌ 解构后,count 变成普通数字,丢失响应式

const { count } = state

count++      // 页面不会更新

// ✅ 方案 1:不解构

state.count++

// ✅ 方案 2:用 toRefs

import { toRefs } from 'vue'

const { count } = toRefs(state)

count.value++   // 现在可以了

坑三:给数组直接赋值索引或改长度

Vue 3 用了 Proxy,比 Vue 2 的 Object.defineProperty 覆盖更全面,所以 Vue 2 的老坑在 Vue 3 里基本不存在:

JAVASCRIPT

const list = ref([1, 2, 3])

list.value[0] = 99       // ✅ Vue 3 可以

list.value.length = 1    // ✅ 可以

list.value = [4, 5, 6]   // ✅ 也可以

坑四:异步更新,读的是旧值

JAVASCRIPT

const count = ref(0)

function increment() {

  count.value++

  console.log(count.value)   // 1,这个是对的

  console.log(document.querySelector('.count').textContent)

  // ❌ 还是旧值!因为 DOM 更新是异步的

}

// ✅ 需要等 DOM 更新后

import { nextTick } from 'vue'

async function increment() {

  count.value++

  await nextTick()

  console.log(document.querySelector('.count').textContent)  // ✅ 新值

}

💡 为什么 DOM 更新是异步的

如果你在同一个事件里改了 10 次数据,Vue 不想更新 DOM 10 次,而是攒起来只更新一次。所以 DOM 更新会被推迟到下一个微任务。

坑五:watch 的深层监听

JAVASCRIPT

const user = reactive({ profile: { name: '张三' } })

// ❌ 只监听 user.profile 这个引用

watch(() => user.profile, (newVal) => {

  console.log(newVal)

})

// ✅ 加 deep 监听内部

watch(() => user.profile, (newVal) => {

  console.log(newVal)

}, { deep: true })

// ✅ 或直接监听具体字段

watch(() => user.profile.name, (newName) => {

  console.log(newName)

})

坑六:v-if 和 v-for 一起用

HTML

<!-- ❌ 不推荐:同一元素上用 v-if 和 v-for -->

<li v-for="item in list" v-if="item.visible">{{ item.name }}</li>

<!-- ✅ 用 computed 过滤 -->

<script setup>

const visibleList = computed(() =>

  list.value.filter(item => item.visible)

)

</script>

<template>

  <li v-for="item in visibleList">{{ item.name }}</li>

</template>

原因:Vue 3 里 v-if 优先级高于 v-for,会导致 v-if 里访问不到 item。

🔑 响应式的核心思维

• 数据是「响应式对象」时,改它的属性/值会自动触发更新

• 「响应式」是对象/引用的性质,一旦被解构成普通值就丢失了

• DOM 更新是异步的,需要 nextTick 等它

• 遇到不更新,先问自己:我改的是不是响应式对象?

11

PART ELEVEN

十一、学习路线与速查

循序渐进的练习路线

第 1-2 周:调试能力

每次遇到 bug,强制自己先用 console.log 或断点定位,不准盲改。一周后你会发现调试速度翻倍。

第 3-4 周:读懂异步代码

找你自己项目里的 5 处 async/await 或 .then,画一张图:数据从哪来、经过哪些异步操作、最后去了哪。你会发现之前觉得「玄学」的代码,其实都是普通流程。

第 5-6 周:吃透 HTTP

打开一个你项目的 Network 面板,点开每一个请求:

• 状态码是什么

• 请求头有哪些字段

• 响应体长什么样

• 用了 token 吗

每看懂一个字段,你对 HTTP 的理解就深一分。

第 7-8 周:Git 实战给自己定规矩:

• 每个功能一个分支

• 每次提交写清楚 commit message

• 遇到问题学会用 git status / git diff / git log 分析

第 9-10 周:跑一个 SQL 环境本地装个 PostgreSQL 或 MySQL,手动建表、插入、查询。然后再用 ORM 做一遍,对照着理解 db.query(User) 背后是什么 SQL。

第 11-12 周:完整读一个项目挑一个你写过的 Vue + Python 项目,按照第⑨章的方法,完整画出:路由 → 业务 → 模型 的分层图和一次请求的完整链路。画完那一刻,你对项目的掌控感会完全不一样。

核心概念一句话速查

概念
一句话
debugger
浏览器里打断点,比 console.log 更强
breakpoint()
Python 内置断点,进入交互式调试
Network 面板
看所有请求的核心工具
Promise
表示「未来会有结果」的对象
async/await
让异步代码看起来像同步
事件循环
先跑同步,再清微任务,最后宏任务
微任务 / 宏任务
Promise.then 优先于 setTimeout
装饰器 @
等于 func = decorator(func)
@app.get
注册路由:把函数加进路由表
ref / .value
包装成响应式对象,JS 里必须 .value
reactive
让对象直接响应式,不用 .value
状态码 2xx
成功
状态码 4xx
客户端错(先查前端)
状态码 5xx
服务端错(看后端日志)
Cookie / Session
服务端记 / 客户端记
JWT
无状态 token,三段式字符串
CORS
浏览器的同源策略,用代理解决
git add / commit / push
挑 → 存档 → 上传
git status / diff
看状态 / 看改动,最常用
git reset
回滚,慎用 --hard
ORM
用对象操作数据库,本质还是 SQL
traceback
Python 报错,从下往上读
分层
routers / services / models / schemas
nextTick
等 Vue 的 DOM 更新完成

两期合起来的知识地图

第一层:操作系统与命令行(第一期)

├── 命令行基础 / cwd / 管道

├── PATH / which / node_modules/.bin

├── 环境变量 / .env

├── 路径 / 权限 / chmod

└── 进程 / 端口 / Nginx / 部署

第二层:代码阅读与调试(第二期)

├── console.log / debugger / DevTools

├── 异步 JS / Promise / 事件循环

├── 装饰器 / 响应式原理

├── HTTP / 状态码 / Token / CORS

├── Git / SQL / ORM

└── 项目分层 / 数据流 / 报错阅读

第三层:架构与设计(下一期)

├── 系统设计 / 高并发

├── 微服务 / 消息队列

├── 性能优化 / 缓存策略

└── 自动化测试 / CI/CD

🎯 一句话总结第二期

所有框架的「魔法」都能拆成普通代码;所有 bug 都能靠调试定位;所有协议都有它的设计理由。把这三个认知记在心里,你就从「用工具的人」变成了「理解工具的人」。下一期我们往更高层走:架构设计和系统思维。但别急,先把这一期动手练熟。

Vibe Coding 底层知识补全计划 · 第二期 · 从能跑就行到真正读懂代码保存到本地随时翻阅 · 每章动手做一遍胜过读十遍

法律AI技术研究应用

LAW × AI · RESEARCH & PRACTICE

相关学习资料