《前端基础实战:从零搭建用户列表,掌握前后端分离核心思想》

0 阅读9分钟

从零搭建用户列表:一个完整的前后端分离项目实战

本文将带你从零开始,使用原生HTML/CSS/JS配合json-server,完成一个用户列表展示功能。涵盖语义化HTML、DOM编程、模块化思想、后端Mock数据等核心知识点。

写在前面

最近在整理前端基础知识时,发现很多同学虽然能用框架快速搭建页面,但对底层的HTML语义、DOM操作、模块化设计等概念却掌握不牢。这篇文章从一个简单的用户列表需求出发,完整记录了我的开发思路和技术选型,希望能帮助大家夯实基础。

项目结构

project/
├── fe/                    # 前端目录
│   ├── index.html        # 页面结构
│   ├── common.js         # JS逻辑
│   └── style.css         # 样式(可选)
├── backend/              # 后端目录
│   ├── package.json      # 项目配置
│   └── db.json          # 模拟数据库
└── README.md

📚 知识点:为什么这样组织目录?

  • 前后端分离febackend可以分别部署在不同的服务器
  • 职责单一:每个文件只做一件事,符合软件工程原则
  • 便于协作:前端和后端可以并行开发,互不干扰

一、HTML 结构设计

语义化标签优先

很多同学写页面喜欢 div 一把梭,但语义化标签对SEO和代码可读性都更有好:

<header>页面头部</header>
<main class="container">
    <aside>侧边栏</aside>
    <div class="row">
        <div class="col-md-6 col-md-offset-3">
            <!-- 主要内容 -->
        </div>
    </div>
    <aside>右侧边栏</aside>
</main>
<footer>页面底部</footer>

📚 知识点详解:语义化标签

标签语义对SEO的影响替代的div方案
<header>页眉/区块头部告诉搜索引擎这是导航区域<div class="header">
<main>页面主体内容标识核心内容,一个页面只有一个<div class="main">
<aside>侧边栏/辅助信息表示与主内容相关的辅助信息<div class="sidebar">
<footer>页脚/版权信息标识页面底部信息区域<div class="footer">
<nav>导航链接标识网站导航区域<div class="nav">

为什么语义化很重要?

  1. 搜索引擎优化(SEO):谷歌爬虫给语义化标签更高的权重
  2. 无障碍访问(a11y):屏幕阅读器可以快速定位页面结构
  3. 代码可维护性:其他开发者接手时能快速理解页面结构
  4. 浏览器默认样式:部分标签自带合理的默认样式

Table 的正确写法

表格一定要区分 theadtbody,这不仅让结构清晰,也方便后续JS操作:

<table class="table" id="user-table">
    <thead>
        <tr>
            <th>ID</th>
            <th>姓名</th>
            <th>昵称</th>
            <th>家乡</th>
        </tr>
    </thead>
    <tbody></tbody>  <!-- JS动态填充内容 -->
</table>

📚 知识点详解:表格标签

table(表格容器)
├── thead(表头区域,01个)
│   └── tr(表格行)
│       └── th(表头单元格,自动加粗居中)
├── tbody(表格主体,0或多个)
│   └── tr
│       └── td(普通单元格)
└── tfoot(表尾区域,01个,可选)
    └── tr
        └── td

th 与 td 的区别:

  • <th>(Table Header):表头单元格,默认字体加粗、居中,表示该列的含义
  • <td>(Table Data):普通数据单元格,默认左对齐、普通字重

为什么JS要操作tbody而不是table?

  • 只刷新数据区域,表头保持不变
  • 避免意外破坏表格结构
  • 性能更好(只重新渲染tbody部分)

盒模型思维

写HTML时先搭盒子框架,再填充内容。container 固定宽度左右留白,row 表示一行,这是经典的PC端布局思路。

📚 知识点详解:CSS盒模型

┌─────────────────────────────────────┐
│            margin(外边距)           │
│  ┌───────────────────────────────┐  │
│  │         border(边框)          │  │
│  │  ┌─────────────────────────┐  │  │
│  │  │     padding(内边距)     │  │  │
│  │  │  ┌───────────────────┐  │  │  │
│  │  │  │    content(内容)   │  │  │  │
│  │  │  └───────────────────┘  │  │  │
│  │  └─────────────────────────┘  │  │
│  └───────────────────────────────┘  │
└─────────────────────────────────────┘

块级元素 vs 行内元素:

类型特点常见标签宽度
块级元素独占一行,可设宽高<div><p><header><main>默认100%
行内元素与其他元素同行,不可设宽高<span><a><strong>内容撑开

二、CSS 与 Bootstrap

直接引入 Bootstrap 3 快速获得样式支持,省去手写大量CSS的时间:

<link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet">

Bootstrap的栅格系统非常实用:col-md-6 col-md-offset-3 表示在中等屏幕上占据一半宽度并居中。

📚 知识点详解:Bootstrap栅格系统

Bootstrap把一行(row)分成12等份:

类名含义宽度计算
col-md-4中等屏幕占4格4/12 = 33.33%
col-md-6中等屏幕占6格6/12 = 50%
col-md-12中等屏幕占12格100%
col-md-offset-3向右偏移3格产生左边距

响应式断点:

  • col-xs-*:超小屏幕(手机,<768px)
  • col-sm-*:小屏幕(平板,≥768px)
  • col-md-*:中等屏幕(桌面,≥992px)
  • col-lg-*:大屏幕(宽屏,≥1200px)

CDN工作原理:

  1. 浏览器解析到<link>标签
  2. 向CDN服务器发起HTTP请求
  3. 下载bootstrap.min.css文件
  4. 解析CSS并应用到页面

为什么用CDN?

  • 速度快:CDN就近分配节点
  • 缓存共享:访问其他使用同一CDN的网站无需重新下载
  • 节省带宽:不占用自己服务器流量
  • 并发加载:不同域名可并行下载

三、JavaScript DOM 编程

模块化思维

不要把代码都塞在一个文件里。common.js 专门处理前端逻辑,与HTML结构分离,便于维护和扩展。

📚 知识点详解:模块化设计

设计原则说明本例体现
单一职责一个模块只做一件事HTML管结构,CSS管样式,JS管逻辑
高内聚模块内部紧密相关common.js只处理用户列表相关逻辑
低耦合模块之间依赖少通过ID/class连接,不直接依赖

动态渲染表格

核心步骤:找到挂载点 → 遍历数据 → 动态生成HTML

// 获取tbody元素作为挂载点
const oBody = document.querySelector('.table tbody');

let i = 1;
for (let user of users) {
    oBody.innerHTML += `
        <tr>
            <td>${i}</td>
            <td>${user.name}</td>
            <td>${user.nickname}</td>
            <td>${user.hometown}</td>
        </tr>
    `;
}

📚 知识点详解:DOM操作

DOM树结构:

document(根节点)
  └─ html(<html>)
      └─ body(<body>)
          └─ main.container
              └─ div.row
                  └─ div.col-md-6
                      └─ table
                          ├─ thead
                          └─ tbody ← querySelector找到这里

querySelector vs 其他选择器:

// 返回第一个匹配的元素
document.querySelector('.table tbody')

// 返回所有匹配的元素(NodeList)
document.querySelectorAll('td')

// 通过ID获取(最快)
document.getElementById('user-table')

// 通过类名获取
document.getElementsByClassName('table')

innerHTML 的工作原理:

  1. 浏览器解析传入的HTML字符串
  2. 构建新的DOM节点
  3. 替换原有的子节点

⚠️ 性能注意事项:

// ❌ 性能差:每次循环都重新解析
for (let user of users) {
    oBody.innerHTML += '<tr>...</tr>';
}

// ✅ 性能好:一次构建,一次插入
let html = '';
for (let user of users) {
    html += '<tr>...</tr>';
}
oBody.innerHTML = html;

ES6 语法亮点

  • for...of 循环:比传统 for (let i=0; i<arr.length; i++) 更简洁
  • 模板字符串:用反引号 + ${变量} 替代字符串拼接,可读性暴增

📚 知识点详解:ES6语法

数组遍历方法对比:

// 传统for循环(需要索引时使用)
for (let i = 0; i < users.length; i++) {
    console.log(users[i]);
}

// for...of(只需要值,不需要索引)
for (let user of users) {
    console.log(user.name);
}

// forEach(函数式编程)
users.forEach(user => {
    console.log(user.name);
});

// map(返回新数组)
const names = users.map(user => user.name);

模板字符串特性:

// 传统拼接(难写、难读)
const html = '<tr><td>' + user.id + '</td><td>' + user.name + '</td></tr>';

// 模板字符串(优雅、支持换行)
const html = `
    <tr>
        <td>${user.id}</td>
        <td>${user.name}</td>
    </tr>
`;

四、后端模拟:json-server

前端开发经常遇到后端接口还没好的情况,json-server 可以快速Mock一个REST API。

初始化项目

cd backend
npm init -y
npm install json-server

📚 知识点详解:npm包管理

package.json的作用:

{
  "name": "backend",           // 项目名称
  "version": "1.0.0",          // 版本号(语义化版本)
  "description": "",           // 项目描述
  "main": "index.js",          // 入口文件
  "scripts": {                 // 自定义命令
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],              // 关键词
  "author": "",                // 作者
  "license": "ISC",            // 开源协议
  "dependencies": {            // 生产环境依赖
    "json-server": "^0.17.0"   // ^表示兼容版本
  }
}

npm install 做了什么?

  1. 读取package.json中的dependencies
  2. 下载依赖包到node_modules目录
  3. 生成package-lock.json锁定版本

创建 db.json

{
  "users": [
    {
      "id": 1,
      "name": "昌哥",
      "hometown": "南昌",
      "nickname": "昌哥nb666"
    },
    {
      "id": 2,
      "name": "胡航",
      "hometown": "南昌",
      "nickname": "航哥"
    }
  ]
}

📚 知识点详解:JSON格式

JSON语法规则:

  • 键名必须用双引号包裹
  • 字符串值必须用双引号
  • 数字、布尔值、null不需要引号
  • 不能有注释
  • 最后一个属性后面不能有逗号

数据类型:

类型示例说明
字符串"昌哥"双引号包裹
数字25整数或浮点数
布尔值truetrue或false
数组[1, 2, 3]方括号
对象{"name": "昌哥"}花括号
nullnull空值

启动服务

npx json-server db.json --port 3000

访问 http://localhost:3000/users 就能拿到JSON数据。

📚 知识点详解:REST API

HTTP方法端点功能请求体
GET/users获取所有用户
GET/users/1获取id=1的用户
POST/users新增用户{"name": "新用户"}
PUT/users/1完整更新id=1全量数据
PATCH/users/1部分更新id=1{"nickname": "新昵称"}
DELETE/users/1删除id=1

npx是什么?

  • npm 5.2+ 自带的工具
  • 直接运行node_modules/.bin下的命令
  • 不需要全局安装json-server

五、前后端联调

fetch 发起网络请求,拿到数据后渲染到页面:

fetch('http://localhost:3000/users')
    .then(response => response.json())
    .then(data => {
        users = data;
        renderTable();  // 重新执行渲染逻辑
    });

📚 知识点详解:fetch API和异步编程

fetch执行流程:

fetch() 发起请求
    ↓(异步,不阻塞后续代码)
浏览器发送HTTP请求
    ↓
服务器返回响应
    ↓
第一个.then() 拿到Response对象
    ↓
调用.json() 解析JSON
    ↓
第二个.then() 拿到实际数据
    ↓
执行渲染逻辑

Response对象常用属性和方法:

fetch(url).then(response => {
    response.status      // HTTP状态码,200表示成功
    response.ok          // 布尔值,status在200-299之间为true
    response.headers     // 响应头信息
    response.json()      // 解析为JSON对象
    response.text()      // 解析为字符串
    response.blob()      // 解析为二进制数据
})

Promise的三种状态:

状态含义触发时机
pending进行中初始状态
fulfilled成功调用resolve()
rejected失败调用reject()

async/await语法糖:

// 等价于上面的fetch写法,但更直观
async function loadUsers() {
    const response = await fetch('http://localhost:3000/users');
    const data = await response.json();
    users = data;
    renderTable();
}

六、完整数据流图解

1. 用户打开页面
   ↓
2. 浏览器加载index.html
   ↓
3. 解析HTML,构建DOM树
   ↓
4. 遇到<link>加载Bootstrap CSS
   ↓
5. 遇到<script>加载并执行common.js
   ↓
6. common.js执行fetch请求
   ↓
7. 请求到达json-server
   ↓
8. json-server读取db.json
   ↓
9. 返回JSON数据给前端
   ↓
10. JS遍历数据,生成HTML字符串
    ↓
11. 设置tbody.innerHTML
    ↓
12. 浏览器重新渲染表格
    ↓
13. 用户看到用户列表

知识小结

知识点要点代码体现
HTML语义化用语义标签替代div<header><main><aside><footer>
表格语义化区分表头和主体<thead> + <tbody>
DOM查询用选择器查找元素document.querySelector('.table tbody')
动态渲染用innerHTML插入内容oBody.innerHTML += ...
模块化代码按职责拆分HTML结构 + JS逻辑 + CSS样式
后端模拟用json-servernpx json-server db.json --port 3000
网络请求用fetch获取数据fetch(url).then(res => res.json())
异步处理Promise处理异步.then() 链式调用

常见问题排查指南

问题可能原因解决方法
表格不显示找不到tbody检查选择器是否正确
数据不更新fetch是异步的确认渲染在.then回调中
中文乱码缺少meta标签添加<meta charset="UTF-8">
跨域报错不同端口访问json-server默认支持CORS
页面空白JS语法错误打开F12控制台查看
连接失败json-server未启动运行npx json-server db.json --port 3000

写在最后

这个项目虽然简单,但涵盖了Web开发的核心流程。打好HTML语义、DOM操作、模块化设计这些基础,后续学习React/Vue等框架会事半功倍。

学习路径建议:

  1. 手敲一遍代码,不要复制粘贴
  2. 修改参数观察变化,理解每一行
  3. 尝试添加新功能(搜索、分页、添加用户)
  4. 理解原理后再学框架

如果觉得有收获,欢迎点赞收藏!


项目源码已整理,需要的小伙伴可以私信我~