前言
我是一名刚入门的程序员,最近写了一个"用户聊天"小项目(user-chat)。项目虽小,但把前后端模块化分离的完整流程走了一遍。
一、项目总览:前后端各管各的
先看整个项目的目录结构:
go
user-chat/
├── fe/ ← 前端只碰这个目录
│ ├── index.html ← 页面结构 + 样式引入
│ └── common.js ← 页面交互逻辑
├── backend/ ← 后端只碰这个目录
│ ├── db.json ← 数据源
│ ├── package.json ← 后端项目描述文件
│ └── package-lock.json
└── readme.md
核心理念:每个文件夹有明确的职责划分,每个文件只做一件事。
fe/目录:专注前端,只管页面长什么样、用户怎么交互backend/目录:专注后端,只管数据存哪里、接口怎么提供
前后端各司其职,互不干扰,通过 HTTP 接口沟通。这就像一个餐厅------前端是大堂服务员(接待客人、展示菜单),后端是后厨(准备食材、做菜),中间通过"订单小票"(HTTP 请求)来传递信息。
为什么不能把所有代码塞进一个文件?
如果代码、功能全堆在一起,会出现三个致命问题:
- 不好维护:改一个样式要找半天,牵一发而动全身
- 不好扩展:加一个新功能时,生怕把旧功能改坏了
- 不好优化:代码像一团乱麻,根本不知道从哪下手
解决方案就是模块化拆分------职责分离之后,每个文件只做一件事,就像乐高积木,可以随意组合、替换、扩展。
二、前端内部也做了"再分离":三件套各司其职
打开 fe/index.html,前端本身也把结构、样式、行为三者做了分离:
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<!-- CSS 在头部引入:负责样式 -->
<link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<!-- HTML 标签:负责结构 -->
<header>1111</header>
<main class="container">
...
</main>
<footer>222</footer>
<!-- JS 在底部引入:负责行为 -->
<script src="./common.js"></script>
</body>
</html>
CSS 放在 <head>、JS 放在 <body> 底部------这不是随意放的,而是有讲究的:
| 技术 | 作用 | 类比 | 引入位置 | 原因 |
|---|---|---|---|---|
| HTML | 负责结构 | 房子的钢筋骨架 | 整个文件 | 所有内容的载体 |
| CSS | 负责样式 | 房子的装修 | <head> 里 |
尽早加载,让用户第一眼就看到好看的页面,体验更好 |
| JS | 负责行为 | 房子的水电系统 | <body> 底部 |
等页面结构渲染完了再加载,不阻塞页面显示 |
CSS 框架:Bootstrap
你可能注意到了,这个项目没有手写一行 CSS------全靠引入的 Bootstrap 框架:
html
<link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet">
Bootstrap 是 Twitter 开源的 CSS 框架,帮我们准备好了大量开箱即用的样式。对于新手来说,先用框架快速出效果,后面再深入学习 CSS 原理,是高效的学习路径。
三、CSS 布局的三个核心概念
1. Box(盒子模型)------"先写盒子,再写内容"
在 CSS 的世界里,所有可见元素都是矩形盒子。比如代码中的:
html
<div class="row col-md-6 col-md-offset-3">
<table class="table table-striped" id="user-table">
...
</table>
</div>
外层 div 是一个大盒子(容器),里面的 table 是一个小盒子(内容)。布局的思维就是:先规划盒子套盒子的层级关系,再往盒子里填充具体内容。
2. Container(容器)------"中间固定,左右留白"
html
<main class="container">
container 是 Bootstrap 提供的布局类,作用是:内容区域的宽度固定,左右两侧自动留白。这样无论用户用的是大屏幕台式机还是小屏幕笔记本,内容始终居中显示------这是从 PC 时代就积累下来的经典布局方案。
3. Row 和 Col(行列布局)------栅格系统
ini
row = 一行
col = 一列
Bootstrap 把一行划分为 12 个等份 ,通过 col-md-数字 来控制元素占据的宽度:
html
<div class="row col-md-6 col-md-offset-3">
col-md-6:占 6 份(即一行的一半宽度)col-md-offset-3:向右偏移 3 份(从而实现居中效果)
这就是栅格系统------实现响应式布局的基石。
四、HTML 世界的两种标签类型
HTML 默认只有两大类标签,理解这个是写出好页面的第一步:
| 类型 | 核心特点 | 用来干什么 | 例子 |
|---|---|---|---|
| 块级元素 | 默认独占一整行 | 做"盒子",搭建页面骨架 | div、header、footer、main、aside、table |
| 行内元素 | 兄弟之间可以排在同一行,互不干扰 | 装"内容"(文字、链接、图片) | span、a、img、em |
代码中就能看到这个规律:
header、main、footer、aside、div、table→ 都是块级元素,用来搭骨架- 这些大盒子里再嵌套内容(文字、表格数据)
语义化标签:拒绝 div 满天飞
新手很容易写成这样:
html
<div class="header">头部</div>
<div class="main">内容</div>
<div class="footer">底部</div>
全是 div,虽然也能跑,但就像一本书没有目录、没有章节标题------机器难以理解页面结构,搜索引擎爬虫和屏幕阅读器(视障用户依赖的工具)更是无从下手。
这个项目用了更规范的做法------语义化标签:
html
<header>头部</header> ← 比 <div class="header"> 更有语义
<main>主体内容</main> ← 比 <div class="main"> 更有语义
<footer>底部</footer> ← 比 <div class="footer"> 更有语义
<aside>侧边栏</aside> ← 比 <div class="sidebar"> 更有语义
这些标签和 div 同属块级元素,但名字本身就表达了含义 ------header 就是页头,footer 就是页脚,对人、对机器都更友好。大厂面试经常会问语义化标签,因为这是 Web 标准理解深度的直接体现。
Table 的语义化关键:thead + tbody
html
<table class="table table-striped" id="user-table">
<tbody>
<tr>
<td>ID</td>
<td>姓名</td>
<td>昵称</td>
<td>家乡</td>
</tr>
</tbody>
</table>
thead(表头)和 tbody(表体)是表格语义化的关键结构。它们告诉浏览器哪些行是表头、哪些行是数据,这对表格的排序、筛选、打印、无障碍访问都至关重要。千万不要省略。
五、<!DOCTYPE html> 和 HTML 文档的本质
两种文本类型
浏览器处理两种文本:
| 类型 | 说明 |
|---|---|
text/plain |
纯文本,记事本打开什么样就显示什么样 |
text/html |
HTML 标签文本,通过 HTTP 协议传输,由浏览器解析成可视化页面 |
你的 HTML 文件属于 text/html 类型,浏览器收到后会解析标签、渲染出可视化的网页。
<!DOCTYPE html> 是干嘛的?
html
<!DOCTYPE html>
这行的作用是告诉浏览器:请用最新的 HTML5 标准渲染这个页面。如果不写,浏览器会进入"怪异模式"(向后兼容老版本 HTML4 的渲染方式),可能导致样式错乱、布局异常。每个正经的 HTML 文档,第一行一定长这样。
六、DOM 编程:JS 如何动态操控页面
这是前端最核心的底层能力,也是大厂面试的重点考察方向。
DOM 是什么?
DOM = Document Object Model(文档对象模型)
浏览器加载 HTML 后,会把每个 HTML 标签变成 JS 内存中的对象 ,并组织成一棵树状结构:
xml
document ← 文档对象(整个页面在 JS 中的入口)
└── document.documentElement ← 根节点(对应 <html> 标签)
└── document.body ← 页面主体(对应 <body> 标签)
├── <header>
├── <main>
│ ├── <aside>
│ └── <div class="row">
│ └── <table id="user-table">
│ └── <tbody>
│ └── <tr> x N
└── <footer>
你可以通过 document.querySelector() 在这棵树上旅行,找到任意节点,然后查看它的子节点、兄弟节点,甚至把它挂载到另一个节点上。
实际代码解读
打开 fe/common.js:
js
// 1. 准备一个空数组,用来存放从后端拿到的数据
let users = [];
// 2. 向后端发起 HTTP 请求
fetch('http://localhost:3000/users')
.then(data => data.json()) // 3. 把响应体转成 JSON 格式
.then(data => {
console.log(data); // 4. 打印看看拿到了什么
users = data; // 5. 存到 users 数组中
// 6. 在 DOM 树上找到表格的 tbody 节点
const oBody = document.querySelector("#user-table tbody");
// 7. 遍历数据,动态插入 HTML
for (let user of users) {
oBody.innerHTML += `
<tr>
<td>1</td>
<td>${user.name}</td>
<td>${user.nickname}</td>
<td>${user.hometown}</td>
</tr>
`;
}
});
这段代码展示了完整的数据驱动页面流程:
- 后端 JSON 数据 →
- 前端
fetch请求 → document.querySelector()找到挂载点 →.innerHTML动态修改 DOM 内容 →- 浏览器自动重新渲染 →
- 用户看到最新数据
传统的静态页面是"后端返回什么,页面就显示什么",而这种动态方式让前端自己掌控渲染------拿到数据后,JS 操控 DOM 来更新页面,不需要刷新整个页面,这就是现代 Web 应用的基础模式。
补充:for...of 语法
代码中遍历用户数组用的是 ES6 的新循环:
js
for (let user of users) {
// user 就是当前遍历到的那个用户对象
}
老式写法需要手动计数:
js
for (let i = 0; i < users.length; i++) {
let user = users[i];
}
for...of 不需要计数器,可读性更好,是现代 JS 的推荐写法。
七、后端:用 json-server 快速搭建数据接口
从零初始化一个后端项目
进入 backend/ 目录,第一条命令:
bash
npm init -y
这会生成 package.json,它是后端项目的身份证:
json
{
"name": "backend",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"dev": "json-server --watch db.json"
},
"type": "commonjs",
"dependencies": {
"json-server": "^1.0.0-beta.15"
}
}
name/version:项目名和版本号scripts:定义启动命令,npm run dev就是执行json-server --watch db.jsondependencies:项目依赖了哪些第三方包,这里只有json-servertype: "commonjs":使用 CommonJS 模块规范(Node.js 的传统方式)
安装 json-server
bash
npm i json-server
json-server 是一个神器------它可以把一个 JSON 文件直接变成 HTTP 接口服务。对于学习阶段来说,不用学数据库、不用写 SQL、不用搭 Express,一个 JSON 文件就能跑起来。
数据文件 db.json
json
{
"users": [
{
"id": 1,
"name": "张三",
"hometown": "华夏",
"nickname": "龙傲天"
},
{
"id": 2,
"name": "曹老板",
"hometown": "梵蒂冈",
"nickname": "梨花"
},
{
"id": 3,
"name": "niko",
"hometown": "波黑尼亚",
"nickname": "donk"
}
]
}
一个普通的 JSON 对象字面量,users 数组里存放了三条用户记录,每条记录包含 id、name、hometown、nickname 四个字段。
json-server 会自动把这个结构映射为 RESTful 接口:
| 接口地址 | 方法 | 作用 |
|---|---|---|
http://localhost:3000/users |
GET | 获取所有用户 |
http://localhost:3000/users/1 |
GET | 获取 id=1 的用户 |
http://localhost:3000/users |
POST | 新增一个用户 |
http://localhost:3000/users/1 |
DELETE | 删除 id=1 的用户 |
零配置,直接能用------这就是 json-server 的价值。
启动后端服务
bash
npm run dev
执行后,json-server 会监听 localhost:3000,前端就能通过 fetch 请求拿到数据了。--watch 参数表示它会监听 db.json 的文件变化,数据改了接口自动同步,开发体验极佳。
八、前后端联通:完整数据流一览
把整个过程串起来,数据是这样流动的:
bash
┌─────────────────────────────────────────────────────────┐
│ backend/db.json │
│ { "users": [...] } │
│ │ │
│ ▼ │
│ json-server --watch db.json │
│ 启动 HTTP 服务 → http://localhost:3000/users │
│ │ │
│ │ HTTP 请求(前端 fetch) │
│ ▼ │
│ fe/common.js │
│ fetch('http://localhost:3000/users') │
│ │ │
│ ▼ │
│ data.json() → 遍历数据 → querySelector 找挂载点 │
│ │ │
│ ▼ │
│ innerHTML += 动态拼接 <tr> 标签 │
│ │ │
│ ▼ │
│ 浏览器重新渲染 → 用户看到表格中的用户数据 │
└─────────────────────────────────────────────────────────┘
前后端通过 HTTP 接口 沟通,各管各的------前端不关心数据怎么存的,后端不关心页面怎么渲染。这就是前后端分离的核心思想。
九、总结
这个小项目虽然简单,但它涵盖了现代 Web 开发最重要的几个基础思维:
| 思想 | 具体体现 |
|---|---|
| 目录级分离 | fe/ 管前端,backend/ 管后端,职责清晰 |
| 前端再分离 | HTML 管结构、CSS 管样式、JS 管行为,三者各司其职 |
| 盒子布局思维 | 先规划盒子套盒子的层级关系,再填充内容 |
| 语义化思维 | 用 header/main/footer/aside 代替 div 满天飞 |
| DOM 编程思维 | 理解 JS 如何在内存中操控页面元素树 |
| 接口通信思维 | 前端通过 fetch 请求后端 HTTP 接口,数据驱动页面 |
对于我来说,理解这些思想比记住具体代码重要得多。