之前一直觉得前后端分离是个特别高大上的工程化概念,总以为得学一堆框架、接口规范、部署流程才能上手。
直到昨天我没用Vue、没用React,纯靠原生JS+HTML+CSS+json-server,手写了一套最朴素的前后端分离小案例,瞬间把底层逻辑彻底打通了。
没有花里胡哨的封装,所有流程都是最原始的写法,反而让我搞懂了框架背后到底在帮我们做什么。今天把我踩坑、试错、顿悟的全过程分享出来。
先说说我想解决的问题
以前写前端页面,所有数据都是写死在HTML里的。
展示一个用户表格,就得手动写一堆<tr>,新增、修改、删除数据都要改页面代码,特别笨拙,完全不符合实际开发逻辑。
我就想实现一个最基础的效果:数据单独存、前端动态拉取、自动渲染页面,真正做到数据和页面结构分离。
不想搭复杂的 Java、Python 后端,偶然发现 json-server 这个神器,零代码就能搭本地API接口,新手练手前后端分离再合适不过。
5分钟搭建极简模拟后端
说实话,以前总觉得后端搭建很麻烦,这次实操完才知道,本地练手的后端居然这么简单。
全程就三个文件,分工超级清晰,这也是我第一次真切体会到模块化拆分的意义。
1. 初始化项目配置
新建一个 backend 文件夹,执行 npm init -y 初始化项目,生成 package.json。这个文件就是项目的"身份证",记录所有依赖和启动脚本。
然后安装核心依赖 npm i json-server,最后配置启动命令,完整配置如下:
json
{
"name": "backend",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"dev":"json-server --watch db.json" // 启动本地模拟接口服务
},
"keywords": [],
"author": "",
"license": "ISC",
"type": "commonjs",
"dependencies": {
"json-server": "^1.0.0-beta.15"
}
}
2. 搭建模拟数据库
新建 db.json,直接写入模拟用户数据,json-server 会自动把这个文件当成数据库,对外提供接口。
json
{
"users":[
{
"id": 1,
"name": "张三",
"hometown": "杭州",
"nikename": "小三"
},
{
"id": 2,
"name": "李四",
"hometown": "南昌",
"nikename": "小四"
},
{
"id": 3,
"name": "王五",
"hometown": "上海",
"nikename": "小五"
}
]
}
终端执行 npm run dev,直接启动服务,访问 http://localhost:3000/users 就能拿到所有用户数据。一行后端代码没写,接口直接可用,太适合新手练手了。

用Bootstrap重构页面,改掉Div满天飞的坏习惯
之前写页面,我清一色全用 div,堆得乱七八糟,自己回头看都看不懂结构。这次刻意用了 HTML5 语义化标签 + Bootstrap 栅格布局,彻底改掉了烂习惯。
语义化标签的真香之处
这次页面我用了 header、main、aside、footer,表格也严格区分 thead 和 tbody。
以前觉得这些标签没用,和 div 没区别,后来才懂大厂为啥特别看重语义化:
- 结构一目了然,哪个是头部、主体、侧边栏清晰分明
- 搜索引擎抓取页面更友好,利于SEO
- 表格拆分表头和表体,是规范开发的基础,后续DOM操作更精准
Bootstrap栅格布局快速适配页面
手动写CSS居中、自适应布局太麻烦,Bootstrap的栅格系统直接开箱即用。
我用了 container 做页面居中留白,row 定义行,col-md-6 col-md-offset-3 实现表格居中展示,几行类名搞定布局,不用写一行自定义CSS。
完整HTML页面代码:
xml
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>动态数据表格</title>
<!-- 引入Bootstrap样式,快速实现页面布局 -->
<link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<header>111</header>
<!-- 核心主体区域 -->
<main class="container">
<aside>333</aside>
<!-- 栅格布局:居中6列表格 -->
<div class="row col-md-6 col-md-offset-3">
<table class="table table-striped" id="user-table">
<thead class="container">
<tr>
<td>ID</td>
<td>姓名</td>
<td>家乡</td>
<td>昵称</td>
</tr>
</thead>
<tbody>
<!-- JS动态渲染数据,这里留白 -->
</tbody>
</table>
</div>
<aside>444</aside>
</main>
<footer>222</footer>
<!-- 引入自定义JS逻辑 -->
<script src="./common.js"></script>
</body>
</html>
踩坑半小时!终于弄懂JS异步执行逻辑
这是本次学习最大的卡点,也是新手最容易犯的错!
我第一版代码写完,控制台能打印出接口数据,但页面空白、无任何渲染内容,当时直接懵了。
错误写法(千万别这么写)
我当时把数据遍历渲染的代码,写在了 fetch 请求的外面:
bash
let users=[];
// 异步请求数据
fetch('http://localhost:3000/users')
.then(data => data.json())
.then(data => {
console.log(data); // 控制台能正常打印数据
users=data;
})
// 同步遍历渲染(坑死我了!)
const oBody = document.querySelector('.table tbody');
for(let user of users){
oBody.innerHTML += `
<tr>
<td>${user.id}</td>
<td>${user.name}</td>
<td>${user.nikename}</td>
<td>${user.hometown}</td>
</tr>
`
}
我百思不得其解:数据明明拿到了,为啥渲染不出来?
后来翻了执行逻辑才恍然大悟:JS是单线程,同步代码优先执行,异步代码后置执行。
页面加载后,会先执行外面的 for 循环,这时候 fetch 接口还在请求中,users 还是空数组。等接口请求成功、赋值完数据,页面早就渲染结束了。
执行步骤(按时间顺序)
- 定义空数组
users = [] - 发起
fetch请求 → 丢到异步队列,不等待,继续走后面代码 - 立刻执行同步的 for 循环 →
users是空的,页面啥也不渲染 - 过了一会儿,网络请求成功 → 执行
.then→ 给users赋值 - 但渲染代码早就跑完了! 页面永远不会更新
结果
- 控制台能打印数据(赋值成功了)
- 页面无任何数据(渲染时数据还没回来)
正确写法(所有依赖接口数据的操作,放进回调)
bash
let users=[];
const oBody = document.querySelector('.table tbody');
// 异步获取数据 + 动态渲染,全部放在回调内部
fetch('http://localhost:3000/users')
.then(data => data.json())
.then(data => {
console.log(data);
users=data;
// 数据回来后,再执行DOM渲染
for(let user of users){
oBody.innerHTML += `
<tr>
<td>${user.id}</td>
<td>${user.name}</td>
<td>${user.nikename}</td>
<td>${user.hometown}</td>
</tr>
`
}
})
执行步骤
- 定义变量、获取 DOM
- 发起
fetch请求 → 丢到异步队列 - 等待网络请求......(期间不执行任何代码)
- 数据成功返回 → 执行
.then - 给
users赋值 → 再执行 for 循环渲染 - 页面正常显示数据
结果
- 控制台打印数据
- 页面正常渲染所有用户信息

一句话总结
- 第一段:不等数据回来,直接渲染 → 白跑一趟,没数据
- 第二段:等数据回来,再渲染 → 逻辑正确,页面生效
这就是 JavaScript 异步编程最基础、最容易踩的坑!所有依赖异步数据的操作 (渲染 DOM、计算、判断等),必须写在异步回调(then/async-await)里面。
新手核心避坑点:只要是接口请求、定时器这类异步操作,后续所有依赖返回结果的逻辑,必须写在异步回调里面!
原生DOM编程,吃透框架底层原理
现在的框架都帮我们封装好了DOM渲染,很多人只会用框架,根本不懂底层怎么实现的。这次纯原生手写,我彻底搞懂了动态渲染的本质。
简单说下我的通俗理解:
DOM 就是浏览器把所有HTML标签,转换成JS内存里的一棵树状对象 。我们通过 querySelector 精准选中页面节点,再用 innerHTML动态拼接HTML结构,把接口数据批量插入页面。
对比老式的索引for循环,ES6的 for...of 真的太香了,不用管下标、不用赋值,直接遍历数组元素,代码简洁可读性拉满,这也是现在主流的遍历写法。
最后聊聊模块化开发的感悟
以前写代码,所有结构、样式、逻辑全堆在一个文件里,看着简洁,实则一坨乱麻,后续根本没法维护扩展。
这次我严格做了拆分:
- HTML:只负责页面结构,只管长什么样
- CSS/外部Bootstrap:只负责页面样式,只管好不好看
- 独立JS文件:只负责逻辑、数据请求、DOM渲染
- json文件:只负责存储数据
这就是最基础的模块化思想:每个文件只做一件事,各司其职,解耦分离。所有大厂的工程化项目,底层都是这个逻辑。
收尾:本次学习3个核心收获
折腾完这个小demo,比我看十篇理论文章收获都大,总结三个最实用的知识点:
1.前后端分离的本质超简单:前端专注视图渲染,后端专注提供数据接口,数据和页面彻底解耦,这就是所有前后端项目的核心逻辑。
-
JS异步执行是重中之重:同步先行、异步后置,所有依赖接口返回值的逻辑,绝对不能写在异步代码外部。
-
基础远比框架重要:Vue/React只是封装了DOM操作和异步逻辑,吃透原生DOM、异步机制、语义化开发,才算真正懂前端。
最后提一句:json-server 只适合新手练手 、本地调试,没有权限校验、数据持久化简陋,千万别用到线上正式项目。
其实前端很多看似难懂的概念,亲手撸一遍demo就彻底通透了。你们初学前端的时候,有没有踩过JS异步、DOM渲染的坑?评论区聊聊~