A · 地基:HTML + CSS
1. HTML 骨架与语义标签
干什么:决定页面上「有什么」。语义标签的作用是让代码可读、让手机和搜索引擎看得懂。
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>标题</title>
</head>
<body>
<header>页头</header>
<nav>导航</nav>
<main>
<section>
<h1>一级标题</h1>
<p>段落</p>
</section>
</main>
<footer>页脚</footer>
</body>
</html>
记住三条:meta viewport 没写,手机上就是缩小版网页;一个页面只有一个 h1;img 一定要有 alt。
2. CSS 选择器与盒模型
干什么:决定「长什么样」。盒模型是所有布局的地基:每个元素都是一个盒子,由内容 + 内边距 + 边框 + 外边距组成。
/* 类选择器最常用,id 少用 */
.card { }
.card .title { }
.card:hover { }
/* 让宽高含 padding 和 border —— 必写这行 */
* { box-sizing: border-box; }
.card {
width: 300px;
padding: 16px; /* 内边距 */
border: 1px solid #333;
margin: 12px auto; /* 上下 12,左右居中 */
}
优先级:行内样式 > id > 类 > 标签。写不动的样式大多是优先级被压住了,先查这个再用 !important。
3. Flex 布局(90% 的横向排列都用它)
干什么:一行里怎么摆、怎么对齐、怎么分配空间。
.row {
display: flex;
justify-content: space-between; /* 主轴:靠边分 */
align-items: center; /* 交叉轴:垂直居中 */
gap: 12px; /* 间距,比 margin 干净 */
flex-wrap: wrap; /* 放不下就换行 */
}
.row .grow { flex: 1; } /* 这个子元素吃掉剩余空间 */
口诀:justify 管主轴,align 管交叉轴。默认横向,加 flex-direction: column 就变纵向,两个轴的作用会互换。
4. Grid 布局(二维格子,做卡片墙和整页排版)
.grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
gap: 12px;
}
auto-fill + minmax 这一行能让卡片自动适配手机和电脑,不用写任何媒体查询。记住它,省很多事。
5. 响应式:媒体查询与相对单位
/* 小于 640px 时生效 */
@media (max-width: 640px) {
.row { flex-direction: column; }
}
/* 手机安全区(有刘海/小黑条的手机) */
body { padding-bottom: env(safe-area-inset-bottom); }
单位选择:字号用 rem 或 px,宽度用 % 或 fr,间距用 px 就行。别到处 vw,字号会随窗口乱缩。
6. CSS 变量(改一处,全站变)
:root {
--bg: #0b0f14;
--txt: #e6edf5;
--accent: #4ade80;
}
body { background: var(--bg); color: var(--txt); }
.btn { background: var(--accent); }
做主题色、做暗色模式都靠它。写第二次的颜色值就该变成变量,这是判断你有没有在写「能维护的代码」的第一条标准。
7. 过渡与动画
.btn {
transition: transform .15s, background .2s;
}
.btn:active { transform: scale(.96); }
@keyframes fadeUp {
from { opacity: 0; transform: translateY(8px); }
to { opacity: 1; transform: none; }
}
.card { animation: fadeUp .3s ease both; }
只动 transform 和 opacity,这两个由显卡处理,不卡。动 width/top/margin 会让浏览器重排,手机上会掉帧。
8. position 定位(弹窗、吸顶、角标)
.bar { position: sticky; top: 0; } /* 吸顶:滚到顶部就粘住 */
.modal {
position: fixed;
inset: 0; /* 上下左右全是 0 = 铺满 */
background: rgba(0,0,0,.6);
}
.badge { position: absolute; top: 4px; right: 4px; }
相对定位给父元素 position: relative,绝对定位的子元素才会以父元素为基准。
B · JavaScript:让它动起来
9. 变量、类型、模板字符串
const name = '小明'; // 不会重新赋值,默认用它
let count = 0; // 会变才用 let
var old = 1; // 别用
const text = `你好 ${name},你有 ${count} 条消息`;
const list = [1, 2, 3];
const obj = { a: 1, b: 2 };
规矩:默认 const,需要改再换 let。这一个小习惯能消掉你以后一半的低级 bug。
10. 函数与箭头函数
function add(a, b) { return a + b; }
const add2 = (a, b) => a + b; // 单行可省 return
const user = {
name: 'A',
say() { return this.name; } // 方法用简写
};
箭头函数没有自己的 this,所以对象方法和需要 this 的地方别用箭头函数。
11. 数组四件套:map / filter / reduce / find
这四个能解决 80% 的数据处理。看到 for 循环先想想能不能换成它们。
const nums = [1, 2, 3, 4, 5];
nums.map(n => n * 2); // [2,4,6,8,10] 逐个转换
nums.filter(n => n % 2 === 0); // [2,4] 筛出满足的
nums.find(n => n > 3); // 4 找第一个
nums.reduce((s, n) => s + n, 0); // 15 汇总成一个值
// 实战:把购物车算总价
const cart = [{p:10, n:2}, {p:5, n:3}];
const total = cart.reduce((s, i) => s + i.p * i.n, 0);
12. 对象、解构、展开
const user = { name: 'A', age: 20, city: '北京' };
const { name, age } = user; // 解构取值
const { city: cityName } = user; // 改名
const copy = { ...user, age: 21 }; // 复制并改一个字段
const merged = { ...user, ...extra }; // 合并
const [first, ...rest] = [1, 2, 3]; // 数组解构
{...obj, 字段: 新值} 是最常用的「不可变更新」写法,React 里天天用。先把它练成肌肉记忆。
13. 找元素与改元素(DOM)
const el = document.querySelector('.card');
const all = document.querySelectorAll('.card'); // 类数组
el.textContent = '新文字'; // 只写文字
el.innerHTML = '<b>加粗</b>'; // 会解析标签,小心 XSS
el.classList.add('on');
el.classList.toggle('on');
el.style.display = 'none';
el.setAttribute('data-id', '7');
原则:能改 class 就别直接改 style,样式留在 CSS 里。用户输入的内容永远用 textContent,不要 innerHTML。
14. 事件与事件委托
btn.addEventListener('click', (e) => {
e.preventDefault(); // 阻止默认行为(表单提交/跳转)
console.log('点了');
});
// 事件委托:给父元素挂一次,管所有子元素
list.addEventListener('click', (e) => {
const item = e.target.closest('[data-id]');
if (!item) return;
handle(item.dataset.id);
});
动态生成的元素用事件委托,否则每加一个元素就要挂一次监听,页面一多就卡。
15. 表单与简单校验
const form = document.querySelector('form');
form.addEventListener('submit', (e) => {
e.preventDefault();
const data = new FormData(form);
const name = String(data.get('name') || '').trim();
if (!name) { alert('名字不能空'); return; }
save({ name });
});
FormData 一次拿全部字段,比一个个 querySelector 干净得多。
16. localStorage:把数据留在浏览器里
// 存字符串
localStorage.setItem('key', JSON.stringify(data));
// 取,给默认值防止第一次是 null
const data = JSON.parse(localStorage.getItem('key') || '{}');
// 删
localStorage.removeItem('key');
只能存字符串,所以对象要 JSON.stringify。容量约 5MB,清缓存就没了——重要的数据要做「导出」按钮。
17. fetch 请求数据(含报错处理)
async function load() {
try {
const res = await fetch('https://api.example.com/list');
if (!res.ok) throw new Error('HTTP ' + res.status);
const data = await res.json();
render(data);
} catch (err) {
console.error(err);
showError('加载失败,请稍后重试');
}
}
必写 if (!res.ok)。fetch 只在网络断的时候 reject,404/500 它也算成功,不检查你会拿到一堆错误数据还以为没事。
18. 模块化:import / export
// math.js
export function add(a, b) { return a + b; }
export const PI = 3.14;
// main.js
import { add, PI } from './math.js';
import * as math from './math.js';
什么时候拆文件:一个文件超过 300 行,或者某段代码被复制了第二遍。别提前拆。
C · 工程能力:从「会写」到「能交付」
19. Git 只需要先会这 5 条
git init # 初始化
git add . # 加入暂存
git commit -m "改了什么" # 提交(一定要写清改了什么)
git log --oneline # 看历史
git checkout -b 分支名 # 开分支
提交信息写「改了什么」不是「更新」。三个月后你会感谢自己。
20. npm 与 package.json
npm init -y # 生成 package.json
npm install 包名 # 装到 dependencies
npm install -D 包名 # 装到 devDependencies(只在开发时用)
npm run 脚本名 # 跑 scripts 里的命令
node_modules 永远不要提交、不要复制给别人,别人 npm install 就能还原。
21. Vite:开发环境(热更新 + 打包)
npm create vite@latest my-app
cd my-app
npm install
npm run dev # 本地开发,改代码浏览器立刻更新
npm run build # 产出 dist 文件夹,这才是要上线的
先用 Vite,不要一上来学 Webpack。Vite 快、配置少,2026 年的默认选择。
22. 框架怎么选(别纠结,看你要做什么)
| 要做的 | 用什么 | 理由 |
|---|---|---|
| 一个页面、几个按钮 | 原生 JS | 引入框架纯属给自己加难度 |
| 多个页面、有登录和数据 | Vue 或 React | 组件化能撑住复杂度 |
| 想尽快找工作 | React | 岗位多 |
| 想上手快、中文资料多 | Vue | 文档友好 |
核心概念只有一个:数据变了,界面自动跟着变。你不再手动 querySelector 改文字,而是声明「界面长这样,数据是这些」。
23. 组件与状态(框架的骨架)
// Vue 3
<script setup>
import { ref, computed } from 'vue'
const count = ref(0)
const double = computed(() => count.value * 2)
</script>
<template>
<button @click="count++">{{ count }} / {{ double }}</button>
</template>
// React
const [count, setCount] = useState(0);
return <button onClick={() => setCount(count + 1)}>{count}</button>;
状态就是「会变的数据」。能算出来的不要存成状态(用 computed / 直接算),否则两个值早晚不同步。
24. 路由(多页面应用)
// Vue Router
const routes = [
{ path: '/', component: Home },
{ path: '/about', component: About },
];
单页应用的路由是用 JS 换内容,不刷新页面。所以服务器要做 fallback:所有路径都返回 index.html,否则用户刷新子页面会 404。
25. 最小后端:Node + Express
import express from 'express';
const app = express();
app.use(express.json());
app.get('/api/todos', (req, res) => {
res.json([{ id: 1, text: '买菜' }]);
});
app.post('/api/todos', (req, res) => {
const { text } = req.body;
if (!text) return res.status(400).json({ error: 'text 必填' });
res.status(201).json({ id: Date.now(), text });
});
app.listen(3000, () => console.log('http://localhost:3000'));
REST 就是「用 HTTP 动词表达动作」:GET 查、POST 建、PUT 改、DELETE 删。路径用名词,别用 /getUser。
26. 数据库:先会 SQL 这 5 条
-- 建表
CREATE TABLE todo (id INTEGER PRIMARY KEY, text TEXT, done INTEGER DEFAULT 0);
SELECT * FROM todo WHERE done = 0 ORDER BY id DESC;
INSERT INTO todo (text) VALUES ('买菜');
UPDATE todo SET done = 1 WHERE id = 3;
DELETE FROM todo WHERE id = 3;
永远用参数化查询(? 占位),不要拼字符串,否则就是 SQL 注入。
27. 部署:两条路
| 类型 | 放哪 | 要点 |
|---|---|---|
| 纯前端(HTML/CSS/JS) | Cloudflare Pages / GitHub Pages / Vercel | 免费,把这几个文件拖上去就行 |
| 有后端 | Railway / Render / 自己的服务器 | 要配环境变量,数据库单独托管 |
上线后必做的三件事:换 https、手机上真机打开一次、把报错日志能看到(不然出问题你只能猜)。
D · 怎么算「能独立开发」
- 给一个需求,你能拆成页面 + 数据 + 交互三部分,并说出先后顺序。
- 不查教程能写出一个带表单、存数据、能刷新的页面。
- 报错能自己定位:会看堆栈、会在关键位置打 log、会缩小范围。
- 能把东西部署到网上并发给别人一个链接。
新手最常踩的 8 个坑
| 现象 | 原因 | 解法 |
|---|---|---|
| 样式怎么都不生效 | 选择器优先级被压住 | 先查优先级,别急着 !important |
| 点按钮没反应 | 脚本在元素之前执行 | script 放 body 末尾或加 defer |
| 数据刷新就没了 | 只存在内存里 | 存 localStorage 或发到后端 |
| fetch 拿到 undefined | 忘了 await res.json() | 加 await,并检查 res.ok |
| 循环里改数组越改越乱 | 边遍历边删 | 用 filter 生成新数组 |
| 手机上排版崩了 | 写死宽度 | 用 %/fr,加 flex-wrap |
| 上线后 404 | 路径写成了绝对路径 | 用相对路径 ./ |
| 改了半天没变化 | 浏览器缓存 | 强刷 Ctrl+F5,或清 Service Worker |
第一个能「独立开发」的项目该做多大
一个待办清单,但要真的能用。包含:添加、删除、勾选完成、刷新后数据还在、能按「全部/未完成」筛选、手机上能看。这六条做到,你就跨过了「只会跟着教程敲」那条线。做完立刻部署,拿到那个链接。