网页表单提交时数据不刷新 jQuery AJAX异步请求的实际用法与常见报错排查
说实话,刚接触前端开发的时候,我最头疼的就是表单提交那档子事。传统的表单提交,点一下按钮,页面”唰”地刷新,数据是传过去了,但用户辛辛苦苦填了一大堆,页面一刷新全没了,体验差得要命。后来我学了AJAX,才发现世界原来可以这么美好——数据悄悄传过去,页面纹丝不动,连个刷新都没有。
但话说回来,AJAX也不是万金油,刚开始用的时候,各种报错能把你烦死。今天就把我踩过的坑、总结的经验,老老实实跟你唠唠。
一、先搞明白:为什么表单提交会让页面刷新?
这个问题其实特别基础,但很多新手根本懒得去搞清楚。
传统的HTML表单,你写个 <form>,里面塞几个输入框,再放一个提交按钮。默认情况下,你点提交,浏览器会做这么一件事:把表单里的数据打包,然后 向 action 属性指定的地址发起一个同步请求,收到响应之后,浏览器直接 把当前页面整个刷新掉,用新页面的内容替换掉原来的。
这就是为什么你表单填得好好的,一点提交,页面闪一下,数据全没了。页面刷新,DOM 重建,你之前填的内容自然也就没了。
但 AJAX 不一样。AJAX 的全称是 Asynchronous JavaScript and XML,意思就是用 JavaScript 在后台悄悄发请求,不用刷新页面。你点提交,数据悄悄传到服务器,服务器返回结果,JavaScript 拿到结果之后,随便你想怎么更新页面都行。用户完全无感,体验丝滑。
二、jQuery AJAX 的基本用法
2.1 最简示例:一个登录表单
我们先从一个最简单的场景开始——用户填用户名和密码,点登录,页面上显示”登录成功”或者”用户名或密码错误”,但页面不刷新。
HTML 部分:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>AJAX 表单提交示例</title>
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
</head>
<body>
<div class="login-box">
<h2>用户登录</h2>
<form id="loginForm">
<div>
<label>用户名:</label>
<input type="text" name="username" id="username" placeholder="请输入用户名">
</div>
<div>
<label>密 码:</label>
<input type="password" name="password" id="password" placeholder="请输入密码">
</div>
<div>
<button type="submit">登 录</button>
</div>
</form>
<div id="result"></div>
</div>
<script>
$(function() {
$('#loginForm').on('submit', function(e) {
// 关键:阻止表单默认的提交行为(就是那个会让页面刷新的行为)
e.preventDefault();
// 获取表单数据
var username = $('#username').val();
var password = $('#password').val();
// 简单的非空校验
if (!username || !password) {
$('#result').html('<p style="color:red;">用户名和密码不能为空!</p>');
return;
}
// 发起 AJAX 请求
$.ajax({
url: '/api/login', // 提交地址
type: 'POST', // 请求方式
data: { // 提交的数据
username: username,
password: password
},
dataType: 'json', // 期望服务器返回 JSON 格式
success: function(res) {
// 请求成功
if (res.code === 200) {
$('#result').html('<p style="color:green;">' + res.message + '</p>');
} else {
$('#result').html('<p style="color:red;">' + res.message + '</p>');
}
},
error: function(xhr, status, error) {
// 请求失败
$('#result').html('<p style="color:red;">请求失败:' + error + '</p>');
}
});
});
});
</script>
</body>
</html>
这段代码的核心就一句话:e.preventDefault()。
你看,我们在 submit 事件里首先调用了 e.preventDefault(),这一步就是告诉浏览器:”别用默认方式提交表单了,我自己来处理。” 没有这一行,你的 AJAX 请求照样会发出去,但页面照样会刷新,AJAX 的结果还没来得及展示,页面就已经重置了。
2.2 用 serialize() 自动收集表单数据
上面的例子里,用户名和密码是我一个个手写的 $('#username').val()。但如果你的表单有二十个字段呢?总不能一个一个去取吧?
jQuery 提供了一个超好用的方法:serialize(),它能把整个表单的数据自动序列化成 username=zhangsan&password=123456 这样的字符串,直接交给 AJAX 使用。
$('#myForm').on('submit', function(e) {
e.preventDefault();
// 一行代码搞定所有表单数据的收集
var formData = $(this).serialize();
$.ajax({
url: '/api/submit',
type: 'POST',
data: formData,
success: function(res) {
$('#result').text(res.message);
},
error: function(xhr, status, error) {
$('#result').text('提交失败:' + error);
}
});
});
是不是简单多了?$(this) 指的就是当前触发 submit 事件的表单元素,.serialize() 自动帮你把里面所有的 input、select、textarea 的值都拼成查询字符串。
注意: serialize() 只会收集有 name 属性的表单元素。所以你的 <input> 标签一定要加 name 属性,不然数据传不过去。这是一个很常见的新手坑。
2.3 提交文件上传的表单
serialize() 虽然方便,但它有个硬伤:不支持文件上传。因为文件上传需要用 FormData 对象,这是 HTML5 才有的东西。
$('#uploadForm').on('submit', function(e) {
e.preventDefault();
// 用 FormData 收集表单数据,包含文件
var formData = new FormData(this);
$.ajax({
url: '/api/upload',
type: 'POST',
data: formData,
// 这两行是文件上传必须的,不加会报错
processData: false, // 告诉 jQuery 不要处理数据
contentType: false, // 告诉 jQuery 不要设置 Content-Type
success: function(res) {
$('#result').html('<p style="color:green;">上传成功!</p>');
},
error: function(xhr, status, error) {
$('#result').html('<p style="color:red;">上传失败:' + error + '</p>');
}
});
});
这里有两个参数特别关键:
processData: false:默认情况下,jQuery 会尝试把 data 转成查询字符串。但 FormData 对象不能这样转,必须关掉这个功能。contentType: false:文件上传时,浏览器会自动设置正确的Content-Type(带上 boundary 信息)。如果手动设置,反而会破坏请求格式。
这两个参数是文件上传 AJA× 的标配,记不住的话就写个注释在旁边。
三、常见报错排查指南
AJAX 调起来容易,排错起来真的能让人崩溃。下面我把最常见的错误类型和排查方法一一列出来。
3.1 错误一:页面还是刷新了
现象: AJAX 代码写好了,但点提交之后页面照样刷新,表单数据没了。
原因: 十有八九是漏掉了 e.preventDefault()。
排查:
- 打开浏览器开发者工具(F12),看 Console 里有没有报错。
- 检查你的 submit 事件处理函数里,第一行是不是
e.preventDefault()。 - 如果你用的是
<button type="submit">,浏览器会默认触发表单提交。如果你用的是<button type="button">,就不会触发,也就不会刷新。
正确写法:
$('#myForm').on('submit', function(e) {
e.preventDefault(); // 这一行绝对不能少!
// 接下来的 AJAX 代码...
});
3.2 错误二:请求发出去了,但服务器返回 404
现象: Console 里看到请求发了,但状态码是 404 Not Found。
原因: URL 写错了,或者后端接口地址没部署好。
排查:
- 检查
url参数是不是写对了。 - 确认后端接口是否已经启动并部署。
- 用浏览器的 Network 面板看请求的完整 URL,复制出来在浏览器地址栏直接访问,看能不能通。
$.ajax({
url: '/api/login', // 确认这个路径是否正确
type: 'POST',
data: { username: 'test', password: '123' },
// ...
});
如果后端接口在另一个域名下(比如 http://localhost:8080/api/login),你就需要写完整 URL,或者配置代理。
3.3 错误三:CORS 跨域错误
现象: Console 里出现类似的报错:
Access to XMLHttpRequest at 'http://localhost:8080/api/login' from origin
'http://localhost:3000' has been blocked by CORS policy:
No 'Access-Control-Allow-Origin' header is present on the requested resource.
原因: 你的前端和后端不在同一个域名(或端口)下,浏览器出于安全考虑,拦截了跨域请求。
解决方案:
- 方案一(推荐): 在后端配置 CORS 响应头。比如 Node.js 后端可以用
cors中间件:
const cors = require('cors');
app.use(cors()); // 允许所有跨域请求
- 方案二: 开发环境配置代理。比如在 Vue/React 项目中配置
vue.config.js或vite.config.js:
// vite.config.js
export default {
server: {
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true
}
}
}
}
这样前端请求 /api/login 会被代理到 http://localhost:8080/api/login,规避跨域问题。
3.4 错误四:数据提交到服务器,但服务器收不到
现象: 请求状态码是 200,但后端打印出来的数据是空的。
原因: 最常见的原因是 contentType 设置不对。
详细分析:
jQuery AJAX 默认的 contentType 是 application/x-www-form-urlencoded; charset=UTF-8,这时候 data 会被序列化成 key=value&key2=value2 的格式,后端用 request.getParameter() 就能收到。
但如果你手动设置了 contentType: 'application/json',数据就会以 JSON 字符串的形式发送,后端用 request.getParameter() 就拿不到了,需要解析请求体。
排查步骤:
- 打开 Network 面板,看请求的 Payload(有效载荷)长什么样。
- 如果 Payload 是
username=test&password=123,说明是 form 格式,后端用普通方式接收即可。 - 如果 Payload 是
{"username":"test","password":"123"},说明是 JSON 格式,后端需要解析 JSON。
推荐写法(保持默认即可):
$.ajax({
url: '/api/login',
type: 'POST',
data: { username: 'test', password: '123' },
// 不设置 contentType,让 jQuery 用默认的 application/x-www-form-urlencoded
success: function(res) {
console.log(res);
}
});
3.5 错误五:success 回调没执行,走到了 error
现象: 请求明明成功了(Network 面板显示 200),但 success 没执行,反而走了 error。
原因: 服务器返回的内容不是合法的 JSON,但你的 dataType 设成了 'json'。jQuery 尝试解析响应体为 JSON,解析失败就会触发 error 回调。
排查:
- 在 Network 面板找到这个请求,看 Response(响应体)里到底返回了什么。
- 可能返回的是 HTML 错误页面(比如后端报错了,返回了 500 的 HTML)。
- 也可能是后端返回的 JSON 格式有误,比如少了个引号、多了个逗号。
解决方案:
$.ajax({
url: '/api/login',
type: 'POST',
data: { username: 'test', password: '123' },
dataType: 'json',
success: function(res) {
console.log('成功:', res);
},
error: function(xhr, status, error) {
// 打印详细信息,方便排查
console.log('状态:', status);
console.log('错误:', error);
console.log('响应内容:', xhr.responseText);
}
});
在 error 回调里打印 xhr.responseText,你就能看到服务器实际返回了什么内容。很多时候,问题就出在这里。
3.6 错误六:POST 请求变成了 GET 请求
现象: 代码里写的是 type: 'POST',但 Network 面板里显示的是 GET 请求。
原因: 这种情况比较少见,但有可能发生。最常见的原因是:
- URL 里带了
?后面的查询参数,且 type 写错了。 检查代码中type是否确实写成了'POST'。 - 使用了
$.get()或$.getJSON()方法,这两个方法默认是 GET 请求。 - 后端配置了重定向,某些服务器在收到 POST 请求后,会返回 301⁄302 重定向,浏览器会改用 GET 请求访问新 URL。这时候在 Network 面板里会看到两条请求。
排查:
打开 Network 面板,仔细查看请求的 Method 列,确认实际发出的请求方式。同时看 Status Code,如果是 301/302,说明是被重定向了。
3.7 错误七:表单提交后按钮重复点击,发送了多次请求
现象: 用户点了一次提交按钮,但服务器收到了多个请求,导致数据重复插入。
原因: 用户手抖多点了几下,AJAX 请求没有防重复机制。
解决方案:
提交按钮后,立即禁用按钮,等请求完成再启用:
$('#myForm').on('submit', function(e) {
e.preventDefault();
var $btn = $(this).find('button[type="submit"]');
$btn.prop('disabled', true).text('提交中...'); // 禁用按钮
$.ajax({
url: '/api/submit',
type: 'POST',
data: $(this).serialize(),
success: function(res) {
$('#result').text(res.message);
},
error: function(xhr, status, error) {
$('#result').text('提交失败:' + error);
},
complete: function() {
// 无论成功失败,都恢复按钮
$btn.prop('disabled', false).text('提 交');
}
});
});
complete 回调不管成功还是失败都会执行,是最适合恢复按钮状态的地方。
四、更优雅的写法:封装通用 AJAX 方法
项目中多个页面都要用 AJAX,每次都写一长串代码太繁琐了。我们可以封装一个通用的方法:
/**
* 通用 AJAX 封装
* @param {string} url - 请求地址
* @param {string} method - 请求方式 GET/POST/PUT/DELETE
* @param {object} data - 请求数据
* @param {function} successCallback - 成功回调
* @param {function} errorCallback - 失败回调
*/
function ajaxRequest(url, method, data, successCallback, errorCallback) {
$.ajax({
url: url,
type: method,
data: data,
dataType: 'json',
timeout: 10000, // 10秒超时
success: function(res) {
if (res.code === 200) {
successCallback && successCallback(res);
} else {
showError(res.message || '请求失败');
errorCallback && errorCallback(res);
}
},
error: function(xhr, status, error) {
showError('网络异常,请稍后重试');
errorCallback && errorCallback(xhr);
}
});
}
// 简单的错误提示
function showError(msg) {
$('#result').html('<p style="color:red;">' + msg + '</p>');
}
// 使用示例
$('#loginForm').on('submit', function(e) {
e.preventDefault();
var formData = $(this).serialize();
ajaxRequest(
'/api/login',
'POST',
formData,
function(res) {
$('#result').html('<p style="color:green;">' + res.message + '</p>');
},
function() {
// 失败时的额外处理
}
);
});
这样封装之后,每次调用都简洁很多。而且统一了错误处理逻辑,不用每个地方都写一遍 showError。
五、几个容易被忽视的细节
5.1 表单重置
AJAX 提交成功后,表单不会自动清空。如果你希望提交成功后清空表单,需要手动处理:
success: function(res) {
if (res.code === 200) {
$('#result').html('<p style="color:green;">提交成功!</p>');
$('#myForm')[0].reset(); // 清空表单
// 或者用 jQuery:$('#myForm').trigger('reset');
}
}
注意要用 $('#myForm')[0].reset() 而不是 $('#myForm').reset(),因为 reset() 是 DOM 方法,不是 jQuery 方法。
5.2 同步请求(不推荐)
jQuery AJAX 默认是异步的(async: true)。如果你把它改成同步,页面会卡死,用户体验极差:
// 千万不要这样写!
$.ajax({
url: '/api/login',
type: 'POST',
async: false, // 同步请求,会让页面卡死
data: { username: 'test', password: '123' }
});
除非你有非常特殊的理由,否则永远不要设 async: false。
5.3 CSRF 防护
如果你的项目有 CSRF(跨站请求伪造)防护,AJAX 请求需要携带 CSRF Token。一般在 HTML 的 meta 标签里存一个 token:
<meta name="csrf-token" content="abc123xyz">
然后在 AJAX 请求时带上:
$.ajax({
url: '/api/submit',
type: 'POST',
data: $('#myForm').serialize(),
headers: {
'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
},
success: function(res) {
console.log(res);
}
});
六、总结
AJAX 表单提交说到底就三件事:阻止默认提交、收集数据、发送请求。
- 阻止默认提交:
e.preventDefault(),这一行不能少。 - 收集数据:简单表单用
.serialize(),文件上传用FormData。 - 发送请求:配置好 URL、type、data,写好 success 和 error 回调。
报错的时候,别慌。先看 Network 面板,看请求到底发没发出去,状态码是什么,响应体里有什么。大部分问题,一眼就能看出来。
希望这篇文章能帮你少走弯路。AJAX 这东西,练多了就熟了,没什么好怕的。
