前言
作为前端学习者,掌握 HTML、CSS、JavaScript 是必经之路。本文将通过一个实际的"用户表格展示项目",带你从零开始理解前端开发的核心理念和知识点。
📚 本文项目源码:user-chat 项目仓库
项目效果
我们最终要实现的是一个动态加载用户数据的表格页面,数据来源于后端 API,最终渲染效果如下:
┌─────────────────────────────────────────┐
│ ID │ 姓名 │ 家乡 │ 昵称 │
├─────────────────────────────────────────┤
│ 1 │ 贤康 │ 南 │ 罗 │
│ 2 │ 道轩 │ 南 │ 鸡蛋仙 │
│ 3 │ 文瑾 │ 中国 │ 娃娃机 │
│ 4 │ 佳明 │ 安 │ 炸酱面 │
└─────────────────────────────────────────┘
一、项目结构
bash
user-chat/
├── backend/ # 后端目录
│ ├── db.json # JSON 数据库
│ └── package.json # npm 配置
├── fe/ # 前端目录
│ ├── index.html # 主页面
│ ├── common.js # JavaScript 逻辑
│ └── common.css # 样式文件
└── readme.md # 学习笔记
为什么要前后端分离?
- 前端(fe):专注用户界面和交互
- 后端(backend):专注数据处理和存储
- 模块化:每个模块职责清晰,便于维护和扩展
二、HTML 基础
1. 语义化标签
HTML5 引入了语义化标签,让代码更具可读性:
html
<!-- 语义化写法 -->
<header>顶部导航</header>
<main>主内容区域</main>
<aside>侧边栏</aside>
<footer>底部信息</footer>
<!-- 不推荐:全部用 div -->
<div>顶部导航</div>
<div>主内容区域</div>
<div>侧边栏</div>
语义化的好处:
- 代码可读性更高
- 有利于 SEO 搜索引擎优化
- 屏幕阅读器能更好地识别页面结构
2. 表格结构
表格是展示结构化数据的重要方式:
html
<table class="table">
<!-- 表头区域 -->
<thead>
<tr>
<th>ID</th>
<th>姓名</th>
<th>家乡</th>
<th>昵称</th>
</tr>
</thead>
<!-- 表体区域 -->
<tbody>
<tr>
<td>1</td>
<td>贤康</td>
<td>南</td>
<td>罗</td>
</tr>
</tbody>
</table>
标签说明:
| 标签 | 全称 | 作用 |
|---|---|---|
<table> |
Table | 表格容器 |
<thead> |
Table Head | 表头区域 |
<tbody> |
Table Body | 表体区域 |
<tr> |
Table Row | 行 |
<th> |
Table Header | 表头单元格(自动加粗居中) |
<td> |
Table Data | 数据单元格 |
💡 重要提示 :
<thead>和<tbody>不是可选的!它们对于 CSS 样式控制和无障碍访问至关重要。
三、CSS 基础
1. 盒子模型
每个 HTML 元素都可以看作一个"盒子":
css
┌──────────────────────────────────┐
│ margin(外边距) │
│ ┌────────────────────────────┐ │
│ │ border(边框) │ │
│ │ ┌────────────────────────┐ │ │
│ │ │ padding(内边距) │ │ │
│ │ │ ┌──────────────────┐ │ │ │
│ │ │ │ content │ │ │ │
│ │ │ │ (内容区域) │ │ │ │
│ │ │ └──────────────────┘ │ │ │
│ │ └────────────────────────┘ │ │
│ └────────────────────────────┘ │
└──────────────────────────────────┘

2. Bootstrap 栅格系统
Bootstrap 将页面分为 12 列,使用时只需添加相应的类名:
html
<!-- Bootstrap CDN 引入 -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.4.1/dist/css/bootstrap.min.css">
<!-- 占满整行 -->
<div class="col-md-12">100% 宽度</div>
<!-- 占一半宽度,可并排两个 -->
<div class="col-md-6">50%</div>
<div class="col-md-6">50%</div>
<!-- 占 1/3 宽度,可并排三个 -->
<div class="col-md-4">33.3%</div>
<div class="col-md-4">33.3%</div>
<div class="col-md-4">33.3%</div>
<!-- 居中显示:占 6 列,偏移 3 列 -->
<div class="col-md-6 col-md-offset-3">
居中的内容
</div>
栅格布局图解:
markdown
12 列布局:
|___|___|___|___|___|___|___|___|___|___|___|___|
1 2 3 4 5 6 7 8 9 10 11 12
col-md-6 col-md-offset-3:
| | | |___|___|___|___|___|___| | | |
1 2 3 4 5 6 7 8 9 10 11 12
↑ 偏移 3 列 → 从这里开始占据 6 列

3. 布局容器
html
<!-- container:左右留白,居中显示 -->
<div class="container">
<div class="row">
<!-- 内容 -->
</div>
</div>
<!-- container-fluid:100% 宽度 -->
<div class="container-fluid">
<!-- 全宽内容 -->
</div>
四、JavaScript 基础
1. DOM 编程

DOM(Document Object Model)是将 HTML 文档转换为对象的技术:
css
HTML 文档:
<html>
<body>
<div class="container">
<table>
<tbody>...</tbody>
</table>
</div>
</body>
</html>
DOM 树结构:
document
└── html
└── body
└── div.container
└── table
└── tbody
JavaScript 获取元素:
javascript
// 通过 ID 获取
const table = document.getElementById('user-table');
// 通过选择器获取(推荐)
const tbody = document.querySelector('.table tbody');
// ↑ class 选择器 ↑ 标签选择器
// ↓ 空格表示"后代选择器",查找 .table 内部的 tbody
2. 动态修改页面内容
javascript
// 获取元素
const oBody = document.querySelector('.table tbody');
// 修改内容
oBody.innerHTML = '<tr><td>新内容</td></tr>';
// 追加内容
oBody.innerHTML += '<tr><td>追加的内容</td></tr>';
⚠️ 注意 :在实际企业开发中,如果数据来自不可信的用户输入,直接使用
innerHTML可能会导致安全问题(XSS 攻击)。本文为了演示方便采用此方法,后续可以了解document.createElement等更安全的做法:
javascript// 更安全的做法 const tr = document.createElement('tr'); const td = document.createElement('td'); td.textContent = '安全的内容'; tr.appendChild(td); oBody.appendChild(tr);
3. 模板字符串
ES6 引入的模板字符串让字符串拼接更优雅:
javascript
const user = { id: 1, name: '罗贤康', hometown: '南昌' };
// 传统方式(繁琐)
'<tr><td>' + user.id + '</td><td>' + user.name + '</td></tr>'
// 模板字符串(简洁)
`<tr>
<td>${user.id}</td>
<td>${user.name}</td>
<td>${user.hometown}</td>
</tr>`
💡 模板字符串使用反引号 ````` 包裹,
${}用于插入变量。
4. fetch API 与网络请求
什么是 fetch?
fetch 是现代浏览器提供的 API,用于发送网络请求获取数据:
javascript
// 基本语法
fetch('请求地址')
.then(响应处理函数)
.then(数据处理函数)
.catch(错误处理函数);
处理响应数据
javascript
fetch('http://localhost:3000/users')
.then(response => response.json()) // 解析 JSON
.then(data => { // 处理数据
console.log(data);
})
流程解析:
| 步骤 | 数据类型 | 说明 |
|---|---|---|
fetch() 返回 |
Response 对象 |
HTTP 响应原始对象 |
response.json() |
Promise<JSON> |
解析为 JSON 的 Promise |
第二个 .then() |
Array/Object |
真正的数据,可直接使用 |
为什么需要 .then()?
fetch 是异步操作,必须等待服务器返回数据后才能处理:
scss
同步执行(阻塞):
代码1 → 代码2 → 代码3
异步执行(非阻塞):
fetch() 开始请求 ──────────────────►
↓ 等待...
Promise.then() 处理结果 ✓
⏱️ fetch 发出请求后,不会等待结果,继续执行后面的代码
⚠️ 注意 :fetch 的错误处理有个特别之处------它只在网络故障时 才会进入
.catch()。如果后端返回 404(找不到资源)或 500(服务器错误)等状态码,fetch 依然会进入.then()。因此,建议在解析 JSON 之前检查response.ok:
javascriptfetch('http://localhost:3000/users') .then(response => { if (!response.ok) { throw new Error('请求失败: ' + response.status); } return response.json(); }) .then(data => console.log(data)) .catch(err => console.error(err));
5. 通俗理解:前后端连接就像去餐厅吃饭
很多新手不理解前后端是怎么连接的,下面用一个通俗的餐厅例子来解释:

🍽️ 餐厅类比
scss
┌─────────────────────────────────────────────────────────────┐
│ │
│ 🍽️ 餐厅例子 │
│ │
│ ┌──────────┐ ┌──────────┐ │
│ │ 前台 │ ← 传菜单 → │ 厨房 │ │
│ │ (前端) │ │ (后端) │ │
│ └──────────┘ ← 端菜 → └──────────┘ │
│ │
│ 你坐的位置 服务员传话 厨师做菜 │
│ (浏览器) (网络请求) (服务器处理) │
│ │
└─────────────────────────────────────────────────────────────┘
1. 你去餐厅吃饭(打开网页)
markdown
👤 你(浏览器)
↓ 走进餐厅
┌──────────────────┐
│ 前台(网页界面) │
│ 桌号、菜单、座位 │
└──────────────────┘
2. 你点菜(前端发送请求)
sql
👤 你对服务员说:"我要一份宫保鸡丁!"
↓ 传递菜单
┌──────────────────┐
│ 服务员(fetch) │ 拿着菜单去厨房
└──────────────────┘
对应代码:
javascript
fetch('http://localhost:3000/users')
// ↑
// 就像服务员去找厨师
3. 厨房做菜(后端处理)
ini
┌──────────────────┐
│ 厨房(后端) │
│ │
│ db.json = 菜谱 │
│ 厨师按照菜谱做菜 │
└──────────────────┘
4. 端菜上桌(返回数据)
arduino
┌──────────────────┐
│ 厨房(后端) │
└────────┬─────────┘
↓
┌────────────────────────────────────────────┐
│ 你点的菜做好了! │
│ "宫保鸡丁一份,请慢用!" │
└────────────────────────────────────────────┘
对应代码:
javascript
fetch('...')
.then(data => data.json()) // 厨师把菜装盘
.then(data => {
// 菜端到你面前了,可以吃了!
console.log(data);
})
完整流程图
javascript
┌─────────────────────────────────────────────────────────────────────┐
│ │
│ 后端(backend) 前端(fe) │
│ │
│ ┌─────────────┐ ┌─────────────────────┐ │
│ │ db.json │ │ index.html │ │
│ │ │ │ (页面界面) │ │
│ │ 存放数据 │ │ │ │
│ └──────┬──────┘ └──────────┬──────────┘ │
│ │ │ │
│ │ json-server 在 3000 端口监听 │ │
│ │ │ │
│ ┌──────┴──────┐ │ │
│ │ 厨师 │ │ │
│ │ 随时待命 │◄──────────────────────────────┤ │
│ │ │ fetch('localhost:3000/users') │
│ └─────────────┘ "我要 users 数据!" │
│ │ │
│ │ 返回 JSON 数据 │
│ │ "[{id:1, name:'罗贤康'...}, ...]" │
│ │ │
│ └──────────────────────────────────────────────────────────►│
│ common.js │
│ ┌───────────────┐ │
│ │ 处理数据 │ │
│ │ 渲染到表格 │ │
│ └───────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
关键点总结
| 概念 | 餐厅类比 | 代码 |
|---|---|---|
| 后端 | 厨房+厨师 | db.json + json-server |
| 前端 | 餐厅前台+座位 | index.html + common.js |
| 请求 | 你点菜 | fetch('http://localhost:3000/users') |
| 响应 | 菜端上来 | .then(data => ...) |
| 端口 3000 | 厨房的窗口 | 服务器监听的"门牌号" |
💡 最重要的理解:
fetch= 打电话给后端要数据localhost:3000= 后端家的地址/users= 要的具体菜品.then()= 收到数据后的处理
6. 循环遍历
for...of 循环(ES6)
javascript
const users = [
{ name: '罗贤康', hometown: '南昌' },
{ name: '金道轩', hometown: '南昌' },
{ name: '王文瑾', hometown: '中国' }
];
// for...of:直接遍历元素,更简洁
for (let user of users) {
console.log(user.name); // 罗贤康、金道轩、王文瑾
}
// 传统 for 循环:需要索引
for (let i = 0; i < users.length; i++) {
console.log(users[i].name);
}
forEach 方法
javascript
users.forEach(user => {
console.log(user.name);
});
7. 变量声明:const vs let
| 声明方式 | 特点 | 使用场景 |
|---|---|---|
const |
常量,不能重新赋值 | 声明后不再改变的变量 |
let |
变量,可以重新赋值 | 需要改变值的变量 |
var |
函数作用域(不推荐) | 避免使用 |
javascript
const PI = 3.14159; // 常量,不应改变
let count = 0; // 变量,可能改变
count = count + 1; // ✓ let 可以
PI = 3.14; // ❌ TypeError: Assignment to constant variable
五、综合示例:动态渲染用户表格
javascript
// 1. 定义空数组
let users = [];
// 2. 从 API 获取数据
fetch('http://localhost:3000/users')
// 3. 解析 JSON 响应
.then(data => data.json())
// 4. 处理数据
.then(data => {
console.log('获取到的数据:', data);
// 赋值给变量
users = data;
// 获取表格 tbody 元素
const oBody = document.querySelector('.table tbody');
// 遍历用户数据,生成 HTML
for (let user of users) {
oBody.innerHTML += `
<tr>
<td>${user.id}</td>
<td>${user.name}</td>
<td>${user.hometown}</td>
<td>${user.nickname}</td>
</tr>
`;
}
})
// 5. 错误处理
.catch(err => console.error('请求失败:', err));
执行流程:
css
1. 定义 users = []
↓
2. fetch 发起请求(异步)
↓ 等待...
3. 服务器返回 JSON 数据
↓
4. response.json() 解析数据
↓
5. 遍历数组,拼接 HTML
↓
6. 插入到 tbody 中
↓
7. 页面显示完整表格 ✓
六、后端模拟:json-server
什么是 json-server?
json-server 是一个工具,可以快速搭建一个完整的 RESTful API 服务,只需一个 JSON 文件。
db.json 数据文件
json
{
"users": [
{
"id": 1,
"name": "罗贤康",
"hometown": "南昌",
"nickname": "罗"
},
{
"id": 2,
"name": "金道轩",
"hometown": "南昌",
"nickname": "鸡蛋仙"
}
]
}
启动服务
bash
cd backend
npm install
npm run dev
自动生成的 API
| 方法 | 地址 | 说明 |
|---|---|---|
| GET | /users | 获取所有用户 |
| GET | /users/1 | 获取 ID 为 1 的用户 |
| POST | /users | 添加新用户 |
| PUT | /users/1 | 更新 ID 为 1 的用户 |
| DELETE | /users/1 | 删除 ID 为 1 的用户 |
七、端口的概念
端口 = 服务的"门牌号"
yaml
计算机(IP 地址)
├── 端口 80 → HTTP 网页服务
├── 端口 443 → HTTPS 加密网页
├── 端口 3000 → json-server(我们的项目)
├── 端口 3306 → MySQL 数据库
└── 端口 22 → SSH 远程登录
访问方式:
arduino
http://localhost:3000
↑ ↑
IP 地址 端口号
localhost= 本机 IP 地址(127.0.0.1)3000= json-server 监听的端口
八、大厂关注的底层能力
通过这个项目,我们学习了大厂特别注重的底层能力:
1. HTML 语义化
html
<!-- 不推荐 -->
<div class="header">导航</div>
<div class="main">内容</div>
<!-- 推荐 -->
<header>导航</header>
<main>内容</main>
2. DOM 编程
javascript
// 选择器查找
document.querySelector()
// 操作 DOM
element.innerHTML
element.appendChild()
element.remove()
3. 模块化思想
markdown
├── backend/ → 后端模块
│ └── db.json
└── fe/ → 前端模块
├── index.html
├── common.js
└── common.css
4. 树状数据结构
理解 document 对象的树状结构,是 DOM 编程的基础:
css
document
├── html
│ ├── head
│ └── body
│ └── div.container
│ └── table
│ └── tbody
九、常见错误及解决方案
1. favicon.ico 404 错误
问题:浏览器自动请求网站图标失败
解决:在 HTML 中添加内联图标
html
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>💬</text></svg>" />
2. Bootstrap 类名不生效
问题:使用了 Bootstrap 的类名但没有引入 Bootstrap CSS
解决 :确保在 <head> 中引入 Bootstrap CDN
html
<head>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.4.1/dist/css/bootstrap.min.css">
</head>
3. fetch 数据为 undefined
问题 :没有调用 .json() 方法
错误写法:
javascript
fetch('url').then(data => {
console.log(data); // ❌ Response 对象,不是数据
})
正确写法:
javascript
fetch('url')
.then(res => res.json()) // ✓ 解析 JSON
.then(data => {
console.log(data); // ✓ 真正的数据
})
十、总结
通过这个简单的项目,我们学习了:
| 知识点 | 掌握程度 |
|---|---|
| HTML 语义化标签 | ✅ |
| 表格结构(thead/tbody/tr/th/td) | ✅ |
| CSS 盒子模型 | ✅ |
| Bootstrap 栅格系统 | ✅ |
| DOM 树状结构 | ✅ |
| JavaScript 选择器 | ✅ |
| 模板字符串 | ✅ |
| fetch API 异步请求 | ✅ |
| for...of 循环 | ✅ |
| 前后端分离概念 | ✅ |
🎯 学习建议:不要只看不练,亲手敲代码才能真正掌握!
十一、下一步学习
- 添加用户功能:使用 POST 请求添加新用户
- 删除用户功能:使用 DELETE 请求删除用户
- 搜索功能:在前端筛选数据
- 表单验证:验证用户输入
- 错误处理:更好的错误提示
📬 如果有任何问题,欢迎在评论区留言!
💻 项目源码:gitee.com/dongmeng-yi...
标签:前端入门 / HTML / CSS / JavaScript / 新手教程