2026年,前端圈子依然热闹,大家聊的都是组件库、微前端、AI辅助编码这些新东西。但说实话,真正让我花时间最多的,反而是那些看起来不那么新的场景:Java Web + JSP项目里怎么用JS和jQuery把审批流做得顺手,项目本地跑起来突然报network unavailable到底怎么查,以及隔三差五被问到的“UI和web前端开发哪个好学”。这篇是《web前端知识点总结2026(五)》的第五期,我不想再列一堆零散语法,那对实际工作帮助不大。我打算把这次的高频需求串成一条线:先用一个典型的审批流项目实例带你走一遍前端实现,再记录network unavailable的排查过程,最后把UI转前端和面试题里的高频考点一起收掉。
如果你正在维护老系统,或者准备从UI方向转向前端开发,又或者只是想在面试前快速过一轮知识点,这篇应该能让你少走一点弯路。第五期我特意把篇幅向“真实项目”倾斜,因为前端知识落到页面里,需要的不只是API背得熟,更重要的是知道每一步为什么要这样做。
1. 为什么一篇2026年的前端总结,要先聊JSP和jQuery
1.1 存量系统的前端知识并不“过时”
前端技术迭代很快,但翻看很多公司的内部系统,尤其是一些业务逻辑很重的后台项目,Java Web + JSP + jQuery依然存在,而且短时间内不会消失。原因不复杂:这类系统核心是流程和数据,不是酷炫的视觉交互,重新用Vue或React全部改写成本太高,收益又不明显。于是在这种环境里工作的前端,实际上每天接触的还是script标签、服务端渲染出来的JSP片段、以及一串串jQuery链式调用。
我有段时间也以为jQuery已经退出历史舞台了,直到接手一个带审批流的项目,发现里面大量代码还在用 $(document).ready 配合 $.ajax 做异步提交。从那以后我的态度变了:框架会换,但浏览器原生DOM操作、事件机制、异步请求这些底子不会过时。jQuery只是封装了它们,真正难的是对业务状态的理解和对交互边界的设计。
1.2 审批流是前端知识点的天然载体
如果单纯总结“CSS选择器优先级”或“JS数组方法”,读者看过就忘。审批流不一样,它几乎把前端日常该用的知识点全带上了:动态渲染按钮、状态判断、事件委托、表单校验、异步请求、成功后刷新、失败错误提示、历史记录回显。更关键的是,审批流对“角色”和“状态”的敏感度非常高,一点小疏漏就会造成越权操作或重复提交,这对前端工程素养是很好的锻炼。
所以这次我把应用场景锁定为“Java Web + JSP项目中用JS + jQuery实现审批流”。你学会了这个,再去看Vue项目里的审批流设计,会发现核心思路是完全一致的,只是渲染和状态管理方式不同。这也是我推荐前端新手多接触业务系统的原因——页面不只是展示,它还承载规则。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 审批流的页面状态设计与动作按钮拆解
2.1 先梳理清角色、状态和动作,再写代码
审批流最容易踩的坑就是上来就写按钮,结果状态一多,代码里全是if else嵌套,最后自己都看不懂。正确的做法是先梳理出一张三栏表:当前流程状态、当前登录人角色、页面允许出现哪些操作按钮。
以一个标准业务审批为例,流程角色我先简化为两类:申请人和审批人。流程状态我习惯用一串英文字段表示,比如:
- draft:草稿
- submitted:审批中
- approved:已通过
- rejected:已驳回
- withdrawn:已撤回
那么对应到页面动作,大致是这样:
| 流程状态 | 申请人可选动作 | 当前审批人可选动作 | 说明 |
|---|---|---|---|
| draft | 提交审批 | 无操作权限 | 草稿只有发起人能看到提交按钮 |
| submitted | 撤回 | 同意、驳回 | 审批人必须是当前节点处理人 |
| approved | 查看 | 无操作权限 | 流程结束 |
| rejected | 重新编辑、再次提交 | 无操作权限 | 申请人看审批意见后修改 |
| withdrawn | 重新编辑、再次提交 | 无操作权限 | 撤回后可重走流程 |
这个表格看起来简单,但把它画清楚之后,前端代码基本就是一张映射表的事。我不建议把判断逻辑散落在十几个if里,而是统一根据“后端返回的可执行动作列表”来渲染按钮。
2.2 前端不要硬编码按钮权限,让后端传actionList
在我过去做的项目里,早期版本是这样写的:
javascript复制if (status === "submitted" && role === "approver") {
$("#approveBtn").show();
}
这样写有两个问题:第一,每次新增角色或状态,前端要跟着改;第二,前端控制权限并不安全,用户改一下DOM就能把隐藏按钮调出来,发起一个本不该发起的请求。后来我调整了方案,后端接口在返回详情数据时会附带一个actionList数组,明确告诉前端“当前登录人在这条单子上可以执行哪些动作”,前端只负责把动作渲染成按钮。
这里有个很重要的观念转变:前端是交互层,不是权限层,权限校验必须在后端做。前端依据后端下发的动作列表干活,就相当于门锁在后端手里,前端只负责画一把用户看得见的钥匙,能不能开锁由后端说了算。
2.3 按钮的文案与样式映射
动作列表通常是英文字符串,比如submit、approve、reject、withdraw,前端要维护一个映射关系:
javascript复制var ACTION_MAP = {
submit: { text: "提交审批", cls: "btn-primary" },
approve: { text: "同意", cls: "btn-success" },
reject: { text: "驳回", cls: "btn-warning" },
withdraw: { text: "撤回", cls: "btn-default" }
};
function renderActionBar(containerId, actionList) {
var $box = $("#" + containerId);
$box.empty();
if (!Array.isArray(actionList)) {
return;
}
actionList.forEach(function (action) {
var cfg = ACTION_MAP[action];
if (!cfg) {
return;
}
$("<button>")
.attr("type", "button")
.attr("data-action", action)
.addClass("btn " + cfg.cls)
.text(cfg.text)
.appendTo($box);
});
}
动态创建按钮时,我习惯把动作名放到data-action里,后面事件委托和处理函数都围绕它做。这样页面上新增一个按钮,只要后端返回动作,前端映射表里有配置,就能自动出现,不需要逐个人肉补DOM。
2.4 用事件委托绑定动态按钮
JSP老项目里经常有人踩这个坑:页面一开始没有按钮,等接口数据返回后动态append上去,然后给按钮绑定的click事件不生效。原因很简单,事件绑定发生在元素创建之前。一个立竿见影的做法是事件委托,把监听挂到父容器,让父容器代理处理子元素后续触发的事件:
javascript复制$("#approveActionBar").on("click", "button", function () {
var action = $(this).data("action");
handlerApproveAction(action, $(this));
});
这样不管按钮是什么时候生成的,都能被父容器捕获到。而且动态按钮如果需要销毁重建,也不会造成事件叠加和重复触发。$(document).on(...)虽然也能用,但范围越大,性能损耗和误触概率越高,尽量把委托范围限制在按钮实际所在的容器内。
3. 审批流前端核心操作的完整实现
3.1 项目结构先搭起来
聊完思路,我给一个最小可复现的JSP项目结构。后端以Java Web为例,但重点看前端部分:
text复制src/main/java/com/example/apply/
controller/ApplyController.java
service/ApplyService.java
dao/ApplyDao.java
webapp/
WEB-INF/jsp/apply/
detail.jsp
static/
js/
jquery.min.js
apply-detail.js
detail.jsp负责服务端渲染基础页面,apply-detail.js负责页面加载后的所有前端交互。
3.2 发起提交与流程动作分发
页面加载时,详情接口会返回这条申请单的基本数据、审批历史和actionList。拿到数据后,前端做三件事:渲染基础信息、渲染审批历史、渲染动作按钮。这里我把动作处理统一交给一个函数:
javascript复制function handlerApproveAction(action, $btn) {
var opinion = $("#opinion").val();
if (action === "reject") {
if (!$.trim(opinion)) {
alert("驳回时必须填写审批意见");
return;
}
}
if (!confirm("确认执行该操作吗?")) {
return;
}
$btn.prop("disabled", true);
$.ajax({
url: ctx + "/apply/doApprove",
type: "POST",
data: {
applyNo: $("#applyNo").val(),
action: action,
opinion: opinion
},
dataType: "json",
timeout: 10000,
success: function (res) {
if (res && res.code === 0) {
alert(res.msg || "操作成功");
location.reload();
} else {
alert((res && res.msg) || "操作失败");
$btn.prop("disabled", false);
}
},
error: function (xhr, status) {
if (status === "timeout") {
alert("请求超时,请检查网络后重试,注意不要重复提交");
} else {
alert("请求失败:" + status);
}
// 接口超时无法确定后端是否收到请求,不能盲目恢复按钮
$btn.prop("disabled", false);
}
});
}
有一个细节需要注意:处理成功后我直接location.reload(),把页面状态彻底刷新。这样做的好处是保证操作结果是最新的,避免前端把按钮状态改到一半,和后端实际状态不一致。很多老系统页面卡在“已经提交成功但按钮还显示可以提交”的状态,就是因为只做了局部DOM修改,没有刷新全页。
3.3 驳回、撤回、重新编辑的前端交互差异
审批流的动作虽然都走同一个AJAX接口,但交互侧应该有所区分:
- 驳回:必须写意见,通常还会带一个确认框,告知申请人后续修改机会;
- 撤回:最好要求填写撤回原因,虽然有些系统不强制,但保留原因能减少扯皮;
- 提交:如果有必填的业务字段,应该先做一次整体表单校验,再发起AJAX;
- 再次提交:往往需要把草稿里的数据重新提交到流程引擎,同时清掉上一次的审批记录提示。
实现上,这些差异并不需要写多个接口,动作名不同,后端根据动作名走不同的业务分支即可。前端要做的只是在校验规则上区别对待,然后用同一个submitApproveAction发送请求。
3.4 审批历史回显时的时间处理
审批历史一般是后端返回的JSON数组。页面回显时我写了一个简版渲染函数:
javascript复制function loadHistory(applyNo) {
$.getJSON(ctx + "/apply/history", { applyNo: applyNo }, function (res) {
if (!res || res.code !== 0) {
return;
}
var rows = "";
$.each(res.data || [], function (i, item) {
rows += "<tr>" +
"<td>" + escapeHtml(item.nodeName) + "</td>" +
"<td>" + escapeHtml(item.operatorName) + "</td>" +
"<td>" + escapeHtml(item.actionName) + "</td>" +
"<td>" + escapeHtml(item.opinion || "") + "</td>" +
"<td>" + formatDateTime(item.createTime) + "</td>" +
"</tr>";
});
$("#historyTable tbody").html(rows);
});
}
function formatDateTime(value) {
if (!value) {
return "";
}
return String(value).replace("T", " ").substring(0, 19);
}
function escapeHtml(value) {
if (value === null || value === undefined) {
return "";
}
return String(value)
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
}
escapeHtml这一步不能省。审批意见是用户输入内容,直接拼进HTML会把后端返回的内容当成代码执行,存在脚本注入风险。JSP项目里常见的做法是只在后端用JSTL的<c:out>输出,但前端动态生成的HTML很容易忘掉转义,所以我在所有拼接模板的场景里都套一层escapeHtml,这个习惯是从一次被XSS攻击中长出来的教训。
时间字段处理也值得一提。如果后端返回的是2026-01-01T10:00:00这种ISO格式,直接用没问题;最怕的是返回2026-01-01 10:00:00,在某些浏览器内核里new Date()解析会失败。我在老项目里习惯直接字符串替换再加截断,不做复杂转换,简单可靠。
3.5 防止重复提交的常见手段
审批操作不比普通查询,重复提交会造成流程节点异常,严重时会产生多笔在途单。前端我一般做三层拦截:
- 点击后立刻
prop("disabled", true),让按钮在请求期间不可点击; - 用模块级变量加锁,比如
isSubmitting,避免某些情况下按钮状态被异步逻辑改回去后用户连点; - 提交前二次确认,用
confirm弹窗给用户一个主动判断的机会。
注意第三层不是为了干扰用户,而是审批动作往往不可逆,尤其是“同意”和“驳回”,给一个确认弹窗能有效减少误触造成的事故。等到请求超时或明确失败后再恢复按钮。
4. 项目运行提示network unavailable的排查实录
4.1 先看页面和接口各自的报错范围
很多人一看到network unavailable就以为是外网断了,但JSP项目本地开发时遇到这个问题,绝大多数和外网没有关系。我建议先分清楚:是整个页面都打不开,还是页面能打开但某个AJAX请求失败。
如果是整站打不开,优先检查后端服务是否启动。Tomcat或者内嵌容器没起来,浏览器访问自然失败。此时地址栏输入http://localhost:8080看看有没有默认页或报错信息,如果连默认页都没有,基本是服务层的问题。如果服务启动了但还是打不开,再看端口是否被占用,用netstat -ano | findstr 8080查一下端口归属,或者用lsof -i:8080。
如果页面能打开,F12控制台里看到某个接口报network error,那问题就聚焦在接口请求本身了。常见原因是请求地址写错、跨域被拦截、接口路径404或500,这几种情况的报错形式不同,需要逐一分清。
4.2 一个容易被忽略的开关:浏览器的离线模式
排查前端网络问题时,我第一步永远是打开开发者工具,切到Network面板,看左上角有没有一个Offline选项被勾选。有些人可能是不小心点到了,也可能是早期调试离线缓存时留下来的。一旦勾选Offline,浏览器会模拟断网环境,所有请求都会显示失败,但页面静态资源因为缓存可能还能打开一部分,这时候很容易误判成后端问题。
我之前就碰到过一次,同事信誓旦旦说后端接口挂了,结果我过去一看,Network面板的Offline开关还开着。这个开关太隐蔽了,每次排查看一眼,可以省掉很多无效沟通。
4.3 混合内容导致的静默拦截
还有一个高频场景:页面是通过HTTPS打开的,但接口地址写的是HTTP。浏览器为了安全会把这类“混合内容”请求直接拦截,尤其在Chrome里,经常表现为请求pending很久然后失败,控制台提示Mixed Content或者直接显示网络错误。
如果你的项目部署在线上是HTTPS,本地联调却用HTTP,或者后端接口地址写死成http://,就会触发这个问题。解决思路是让页面和请求协议保持一致,要么都用HTTPS,要么都用HTTP。开发环境推荐用相对路径,比如ctx + "/apply/doApprove",不要写死http://开头的绝对地址,这样环境切换时少踩坑。
4.4 从“先刷新”到“看请求头”的排查顺序
我把这个场景的排查步骤整理成了一个最常用顺序,照着走能覆盖八成问题:
| 排查步骤 | 操作 | 常见结果 |
|---|---|---|
| 1. 刷新页面 | F5或Ctrl+Shift+R强制刷新 | 临时缓存导致的白屏消失 |
| 2. 看后端服务 | 确认Tomcat或应用服务是否启动 | 服务没启动导致所有接口失败 |
| 3. 看控制台Network | 检查请求是pending、404还是CORS | 定位是请求没发出还是接口报错 |
| 4. 看请求地址 | 打开请求预览,核对域名、端口、路径 | 路径多一个斜杠或少一个参数 |
| 5. 检查浏览器DevTools离线开关 | Network面板查看Offline状态 | 误开离线导致所有请求失败 |
| 6. 用隐身窗口测试 | 排除浏览器插件和缓存干扰 | 插件拦截导致请求异常 |
| 7. 重启服务并清缓存 | 停掉服务重新以debug模式启动 | JSP文件修改后未编译生效 |
我印象很深的一个case:JSP文件改了以后浏览器怎么刷新都是旧页面,控制台还偶发network unavailable。后来发现是IDE修改文件后没有触发热部署,Tomcat里跑的仍然是旧的class和页面。重启服务再强刷一次,问题立刻消失。遇到疑难网络报错,重启依然是必须尝试的手段,不要在代码里猜太久。
4.5 联调时的局域网和跨域问题
和同事联调时,如果前端页面跑在http://localhost:8080,后端接口跑在别人的机器http://192.168.x.x:8080,跨域问题几乎是躲不开的。浏览器跨域失败时,有些版本的报错会被网络层吞掉,显示成笼统的network unavailable。这时候在Network面板看请求状态,如果出现CORS policy字样,就要针对性处理,比如后端接口加@CrossOrigin,或者在开发环境配一层转发。
还有一个坑是后端应用只监听了localhost或127.0.0.1,没有监听0.0.0.0,同事那边访问你的接口自然不通。这个跟前端代码没关系,但排查时经常被拉去背锅。如果发现同一局域网内别人访问不了你的本机接口,先让对方换个端口试试,再检查你本机的防火墙和服务监听范围。
5. UI设计和前端开发哪个好学:我的建议
5.1 两个岗位产出的东西完全不同
“UI和web前端开发哪个好学”是搜索热词里出现频率很高的问题,看得出很多人正处于职业选择或者转型的十字路口。我给不出“哪个更好”的答案,因为这两个岗位的底层能力不一样。UI设计师的核心产出是设计方案,包含布局、配色、字体、交互细节;Web前端开发的核心产出是可运行的页面代码,把方案变成真实产品。
如果你想做前端,至少要接受每天和逻辑打交道:数据怎么来、状态怎么变、接口返回后展示什么。这不是说你必须成为算法高手,但你至少要能理解if、数组、对象、异步这些基础概念。如果你更享受把视觉做到极致,UI方向会让你更舒服。
5.2 从学习门槛上看差异
前端入门的JavaScript语法其实不复杂,HTML和CSS更是几天就能写出静态页。但前端越学越杂:浏览器兼容、性能优化、工程化、框架原理、网络协议都要懂。UI入门则需要审美基础和软件操作能力,短期内做出高保真图需要反复临摹和训练,但一旦形成设计规范思维,越往后越靠感觉和经验吃饭。
我用一个不那么严谨但很直观的说法:UI的入门曲线像爬台阶,前期陡,后面平;前端的入门曲线像走山路,入口平缓,但越走岔路越多,需要持续学习。如果你理工科背景、享受解决逻辑问题,可以考虑前端;如果你艺术背景、对细节敏感,UI更合适。关键不是哪个好学,而是哪个你愿意长期投入。
5.3 从UI转前端的一条建议路线
如果你已经在做UI,想转前端,我之前陪几个同事走过这条路,比较顺的路线是先别急着学Vue或React,而是打牢三块底子:
- HTML结构:准确使用语义化标签,理解块级和行内的区别;
- CSS布局:重点搞定Flex和Grid,这两个能解决绝大多数页面布局;
- JavaScript基础:变量、函数、数组方法、事件、Ajax、DOM操作。
这三块熟练后,再做几个带交互的小页面,比如一个可以新增删除表格行的后台页面、一个带筛选条件的列表页。然后才进入框架阶段。如果一开始就冲进组件库,你会连数据流和状态都理不清楚,很快就放弃。
前端程序员不需要会像素级设计,但至少要能看懂设计稿的间距、字号、图层命名。反过来,UI转前端的人有一个天然优势——对还原设计稿的追求高于普通开发,这在前端岗位里很受欢迎。
6. 2026前端面试高频考点速查与实践总结
6.1 基础考点:闭包、事件循环、事件委托
不管项目多老,前端面试基础题绕不开这几个点。闭包会追问到内存泄漏场景,事件循环会问到异步执行顺序,事件委托会问到动态列表的实现。我给一个精简的复习角度:
- 闭包:函数执行后返回内部函数,内部函数继续持有外层函数变量。常见用途是封装私有变量和防抖节流;坑在于不经意间长生命周期引用导致内存不释放。
- 事件循环:JS是单线程,宏任务和微任务执行顺序可以按“同步代码 → 微任务 → 宏任务”快速判断。Promise的then属于微任务,setTimeout属于宏任务。
- 事件委托:事件冒泡到父元素后,由父元素统一处理。使用
$(parent).on("click", ".child", handler)要能给面试官讲清楚为什么适合动态列表。
这三个考点我在审批流项目里几乎都用到了。动态按钮列表就是事件委托的活例子,请求成功后的location.reload()则涉及同步和异步位置的区别,理解透了,面试时能结合业务讲,比背概念有说服力得多。
6.2 项目面试题:状态管理和权限控制
现在面试官越来越喜欢问项目细节,审批流就是一个很好的切入点。相关问题通常包括:
- “你如何控制页面上按钮的显示和隐藏?”不要只答CSS的display,而是说后端返回可执行动作列表,前端做映射渲染。
- “如何防止用户直接绕过按钮发起请求?”诚实回答前端无法真正防止,权限判断必须在后端,前端限制只是体验层面的笨办法。
- “如果审批提交超时怎么办?”考察点不只是alert一下,而是要考虑接口可能实际已经成功,需要做状态确认或设计幂等。
对于这套题目,有JSP和jQuery项目经验的人其实不比写Vue的人差。关键是要能总结出项目的业务闭环,从审批单展示、按钮权限、意见填写到历史回显,每一环都有明确的思考和取舍。
6.3 给面试前一天的速记卡片
总结几个高频知识点,建议花一晚上过掉:
| 高频问题 | 回答要点 |
|---|---|
| 数组去重有哪些办法 | Set、filter+index、双循环,提到时间和空间复杂度 |
| css怎么实现水平垂直居中 | flex布局的align-items+justify-content,或绝对定位加transform |
| 防抖和节流的区别 | 防抖是等停止触发后再执行,节流是固定时间间隔内执行一次 |
| jQuery事件委托和普通绑定区别 | 普通绑定只作用于已存在元素,事件委托可以处理动态新增元素 |
| 前端如何做性能优化 | 减少HTTP请求、图片懒加载、静态资源压缩、合并脚本,渲染层减少DOM操作 |
| JSP页面提交和Ajax提交区别 | 同步刷新整页与异步局部刷新,Ajax需要处理好响应状态和错误提示 |
| 多个Tab页如何共享登录状态 | 可以提localStorage、cookie、SSO方案,再根据项目说具体实践 |
这些题目没有标准答案,面试官主要看你能不能自圆其说,并且能不能引到自己做过的项目里去。我复习的时候习惯把每个考点都对应到一个曾经写过的页面功能上,比如防抖对应搜索框实时查询,图片懒加载对应列表页的大图,事件委托对应审批动作按钮的渲染。这样面试回答起来不会干巴巴,很像真的做过事情。
6.4 老项目维护者容易忽略的得分点
如果你面试时提到自己维护过Java Web + JSP + jQuery项目,不要觉得拿不出手。面试官反而会关注你在这种环境里表现出的工程素养。你可以主动讲自己怎么解决跨模块公共方法重复定义的问题,怎么把散落各页面的公共AJAX封装成统一的common.js,怎么用data-action驱动动作分发。
我习惯在旧项目里把公共请求封装成一个方法,统一处理登录过期跳转和错误提示,而不是每个页面各写一遍$.ajax。面试中讲这种细节,会让对方觉得你不是只会调用API,而是具备代码组织意识。这个点在2026年依然重要,因为AI能帮你快速生成代码片段,但代码怎么和业务结合,还是需要人来判断。
7. 关于这套知识总结,我的个人整理心得
每次做前端知识点总结,我都不会去追求覆盖所有API,而是挑一类真实项目场景,把场景里用到的知识点掰开揉碎。审批流这个主题能串起状态管理、事件机制、异步请求、权限设计和页面渲染,是很划算的学习载体。
如果你正处于学习阶段,我建议别只阅读,而是自己开一个最小项目,用JSP、jQuery,或者用纯HTML静态页模拟后端接口都行,把审批流的按钮渲染和动作提交完整写一遍。这个过程里遇到的“动态按钮不生效”“重复提交”“时间格式兼容”这些坑,比刷一百道面试题都管用。我这几年的体会是:前端学习没有终点,但每吃透一个类业务场景,下一次遇到同类问题,手感和判断都会完全不一样。
