JavaWeb 30 天入门:第二十一天 ——AJAX 异步交互技术
在前二十天的学习中,我们掌握了 JavaWeb 开发的核心技术,包括 Servlet、JSP、会话管理、过滤器、监听器、文件操作、数据库交互、连接池、分页与排序等。今天我们将学习一项彻底改变 Web 应用交互方式的技术 ——AJAX(Asynchronous JavaScript and XML)。
传统的 Web 应用中,每次数据交互都需要刷新整个页面,用户体验较差。AJAX 通过在后台与服务器进行异步数据交换,使网页可以在不重新加载整个页面的情况下,实现部分内容的更新。这项技术是现代 Web 应用(如 Gmail、Facebook、微博等)实现流畅用户体验的基础。
AJAX 概述
什么是 AJAX
AJAX 是一种在无需重新加载整个网页的情况下,能够更新部分网页的技术。
- Asynchronous(异步):指与服务器通信时,浏览器不需要暂停等待服务器响应,可以继续执行其他操作
- JavaScript:核心编程语言,用于发送请求、处理响应和更新页面
- And:连接词
- XML:早期主要用于数据交换的格式,现在 JSON 更常用
AJAX 的核心是XMLHttpRequest 对象(XHR),它允许浏览器与服务器进行异步通信。
AJAX 的工作原理
AJAX 的工作流程如下:
- 用户在网页上执行某个操作(如点击按钮、输入文本)
- JavaScript 捕获该事件,创建 XMLHttpRequest 对象
- XMLHttpRequest 对象向服务器发送异步请求
- 服务器处理请求,返回数据(通常是 JSON 或 XML 格式)
- JavaScript 接收服务器返回的数据
- JavaScript 更新网页的部分内容,而无需重新加载整个页面
AJAX 的优势
- 提升用户体验:无需刷新整个页面,减少等待时间和视觉干扰
- 减少数据传输:只传输需要更新的数据,节省带宽
- 提高交互性:可以实现实时验证、自动完成等高级交互功能
- 减轻服务器负担:部分数据处理可以在客户端完成
- 支持离线功能:结合现代 API 可以实现数据本地存储和离线操作
AJAX 的应用场景
- 表单实时验证(如用户名是否已存在)
- 动态加载数据(如下拉列表联动、滚动加载更多)
- 实时搜索建议(输入时自动提示匹配结果)
- 无刷新分页和排序
- 实时数据展示(如股票行情、在线聊天)
- 文件上传进度显示
XMLHttpRequest 对象
XMLHttpRequest(XHR)是 AJAX 的核心对象,用于在后台与服务器交换数据。
创建 XHR 对象
不同浏览器创建 XHR 对象的方式略有差异,标准写法如下:
// 创建XMLHttpRequest对象
function createXHR() {var xhr;if (window.XMLHttpRequest) {// 现代浏览器(IE7+、Firefox、Chrome、Safari等)xhr = new XMLHttpRequest();} else {// 兼容IE6及以下版本xhr = new ActiveXObject("Microsoft.XMLHTTP");}return xhr;
}
XHR 对象的常用属性
属性 | 描述 |
---|---|
readyState | 请求的状态码:0 - 未初始化,1 - 服务器连接已建立,2 - 请求已接收,3 - 请求处理中,4 - 请求已完成且响应已就绪 |
status | 服务器返回的 HTTP 状态码:200 - 成功,404 - 未找到,500 - 服务器内部错误等 |
statusText | 服务器返回的状态文本(如 "OK"、"Not Found") |
responseText | 服务器返回的文本数据 |
responseXML | 服务器返回的 XML 数据(可作为 DOM 对象处理) |
onreadystatechange | 每当readyState 改变时触发的事件处理函数 |
XHR 对象的常用方法
方法 | 描述 |
---|---|
open(method, url, async) | 初始化请求: - method:请求方法(GET、POST 等) - url:请求地址 - async:是否异步(true - 异步,false - 同步) |
send(data) | 发送请求: - data:POST 请求时的参数数据 |
setRequestHeader(header, value) | 设置请求头信息(需在open() 之后、send() 之前调用) |
abort() | 取消当前请求 |
getResponseHeader(header) | 获取指定响应头的值 |
getAllResponseHeaders() | 获取所有响应头信息 |
AJAX 的基本使用步骤
使用 AJAX 与服务器交互的基本步骤:
- 创建 XMLHttpRequest 对象
- 注册
onreadystatechange
事件处理函数 - 使用
open()
方法初始化请求 - (可选)设置请求头信息
- 使用
send()
方法发送请求 - 在事件处理函数中处理服务器响应
1. GET 请求示例
GET 请求通常用于从服务器获取数据,参数通过 URL 的查询字符串传递:
// 发送GET请求
function sendGetRequest() {// 1. 创建XHR对象var xhr = createXHR();// 2. 注册事件处理函数xhr.onreadystatechange = function() {// 当请求完成且响应就绪if (xhr.readyState === 4) {// 当HTTP状态码为200(成功)if (xhr.status === 200) {// 处理响应数据var response = xhr.responseText;console.log("服务器响应:", response);document.getElementById("result").innerHTML = response;} else {// 处理错误console.error("请求失败,状态码:", xhr.status);document.getElementById("result").innerHTML = "请求失败:" + xhr.statusText;}}};// 3. 初始化请求(带参数)var username = document.getElementById("username").value;// 对参数进行编码,防止特殊字符问题var url = "GetDataServlet?username=" + encodeURIComponent(username) + "&t=" + new Date().getTime();xhr.open("GET", url, true);// 4. 发送请求(GET请求参数在URL中,send()方法参数为null)xhr.send(null);
}
注意:
- GET 请求的参数会显示在 URL 中,安全性较低
- GET 请求有长度限制(不同浏览器限制不同,通常 2KB-8KB)
- 添加时间戳(
t=new Date().getTime()
)是为了避免浏览器缓存
2. POST 请求示例
POST 请求通常用于向服务器提交数据,参数在请求体中传递:
// 发送POST请求
function sendPostRequest() {// 1. 创建XHR对象var xhr = createXHR();// 2. 注册事件处理函数xhr.onreadystatechange = function() {if (xhr.readyState === 4) {if (xhr.status === 200) {var response = xhr.responseText;console.log("服务器响应:", response);document.getElementById("result").innerHTML = response;} else {console.error("请求失败,状态码:", xhr.status);document.getElementById("result").innerHTML = "请求失败:" + xhr.statusText;}}};// 3. 初始化请求var url = "PostDataServlet";xhr.open("POST", url, true);// 4. 设置请求头(POST请求需要设置Content-Type)xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");// 5. 准备请求参数var username = document.getElementById("username").value;var email = document.getElementById("email").value;// 对参数进行编码var data = "username=" + encodeURIComponent(username) + "&email=" + encodeURIComponent(email);// 6. 发送请求xhr.send(data);
}
POST vs GET:
特性 | GET | POST |
---|---|---|
参数位置 | URL 查询字符串 | 请求体 |
长度限制 | 有 | 无(由服务器配置决定) |
缓存 | 可被缓存 | 通常不被缓存 |
安全性 | 低(参数可见) | 较高(参数在请求体) |
用途 | 获取数据 | 提交数据 |
幂等性 | 是(多次请求结果相同) | 否(可能产生副作用) |
处理 JSON 数据
现代 Web 应用中,JSON(JavaScript Object Notation)已成为 AJAX 数据交换的首选格式,它比 XML 更轻量、更易解析。
1. 服务器返回 JSON 数据
在 Servlet 中返回 JSON 数据:
import com.fasterxml.jackson.databind.ObjectMapper;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;@WebServlet("/JsonDataServlet")
public class JsonDataServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {// 设置响应内容类型为JSONresponse.setContentType("application/json;charset=UTF-8");// 创建返回数据对象Map<String, Object> result = new HashMap<>();try {// 获取请求参数String username = request.getParameter("username");// 模拟数据库查询boolean exists = "admin".equals(username);// 构建响应数据result.put("success", true);result.put("message", exists ? "用户名已存在" : "用户名可用");result.put("exists", exists);} catch (Exception e) {result.put("success", false);result.put("message", "服务器错误:" + e.getMessage());}// 使用Jackson库将Java对象转换为JSON字符串ObjectMapper mapper = new ObjectMapper();String json = mapper.writeValueAsString(result);// 发送JSON响应response.getWriter().write(json);}
}
添加 Jackson 依赖(Maven):
<dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId><version>2.13.3</version>
</dependency>
2. 客户端解析 JSON 数据
客户端使用JSON.parse()
方法解析 JSON 字符串:
// 发送请求并处理JSON响应
function checkUsername() {var xhr = createXHR();xhr.onreadystatechange = function() {if (xhr.readyState === 4 && xhr.status === 200) {// 解析JSON响应try {var result = JSON.parse(xhr.responseText);var messageElement = document.getElementById("message");if (result.success) {// 处理成功响应messageElement.textContent = result.message;messageElement.style.color = result.exists ? "red" : "green";} else {// 处理错误响应messageElement.textContent = "错误:" + result.message;messageElement.style.color = "red";}} catch (e) {console.error("JSON解析错误:", e);document.getElementById("message").textContent = "数据格式错误";}}};var username = document.getElementById("username").value;var url = "JsonDataServlet?username=" + encodeURIComponent(username) + "&t=" + new Date().getTime();xhr.open("GET", url, true);xhr.send(null);
}
3. 用户名实时验证示例
结合上述代码,实现一个用户名实时验证功能:
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head><title>用户名实时验证</title><style>.container { width: 500px; margin: 100px auto; }.form-group { margin: 20px 0; }label { display: inline-block; width: 100px; }input { padding: 8px; width: 250px; }#message { margin-left: 105px; height: 20px; }</style>
</head>
<body><div class="container"><h2>注册</h2><div class="form-group"><label for="username">用户名:</label><input type="text" id="username" onblur="checkUsername()" onkeyup="debounceCheckUsername()"></div><div id="message"></div><div class="form-group"><label for="password">密码:</label><input type="password" id="password"></div><div class="form-group"><input type="button" value="注册" onclick="register()"></div></div><script>// 创建XHR对象的函数function createXHR() {var xhr;if (window.XMLHttpRequest) {xhr = new XMLHttpRequest();} else {xhr = new ActiveXObject("Microsoft.XMLHTTP");}return xhr;}// 防抖函数(避免输入时频繁请求)var timeout = null;function debounceCheckUsername() {clearTimeout(timeout);// 延迟500毫秒执行,避免输入过程中频繁请求timeout = setTimeout(checkUsername, 500);}// 检查用户名函数(前面已定义)function checkUsername() {// ... 实现代码同上 ...}// 注册函数function register() {// ... 实现注册逻辑 ...}</script>
</body>
</html>
AJAX 与表单提交
使用 AJAX 提交表单可以避免页面刷新,同时提供更灵活的错误处理和用户反馈。
1. 基本表单提交
// 使用AJAX提交表单
function submitForm() {// 获取表单数据var username = document.getElementById("username").value;var password = document.getElementById("password").value;var email = document.getElementById("email").value;// 简单验证if (!username || !password || !email) {alert("请填写完整信息");return;}// 创建XHR对象var xhr = createXHR();// 处理响应xhr.onreadystatechange = function() {if (xhr.readyState === 4) {if (xhr.status === 200) {try {var result = JSON.parse(xhr.responseText);if (result.success) {// 注册成功alert("注册成功!");// 可以跳转到登录页// window.location.href = "login.jsp";} else {// 注册失败alert("注册失败:" + result.message);}} catch (e) {alert("服务器响应格式错误");}} else {alert("请求失败,状态码:" + xhr.status);}}};// 发送POST请求xhr.open("POST", "RegisterServlet", true);xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");// 构建表单数据var data = "username=" + encodeURIComponent(username) +"&password=" + encodeURIComponent(password) +"&email=" + encodeURIComponent(email);xhr.send(data);
}
2. 处理文件上传
AJAX 也可以处理文件上传,需要使用FormData
对象:
// 使用AJAX上传文件
function uploadFile() {// 获取文件输入元素var fileInput = document.getElementById("file");var file = fileInput.files[0];// 检查文件是否选择if (!file) {alert("请选择要上传的文件");return;}// 检查文件类型var allowedTypes = ["image/jpeg", "image/png", "image/gif"];if (!allowedTypes.includes(file.type)) {alert("只允许上传JPG、PNG、GIF格式的图片");return;}// 检查文件大小(限制5MB)if (file.size > 5 * 1024 * 1024) {alert("文件大小不能超过5MB");return;}// 创建FormData对象var formData = new FormData();formData.append("file", file);formData.append("description", document.getElementById("description").value);// 创建XHR对象var xhr = createXHR();// 处理上传进度xhr.upload.onprogress = function(event) {if (event.lengthComputable) {var percent = (event.loaded / event.total) * 100;document.getElementById("progressBar").style.width = percent + "%";document.getElementById("progressText").textContent = Math.round(percent) + "%";}};// 处理响应xhr.onreadystatechange = function() {if (xhr.readyState === 4) {if (xhr.status === 200) {var result = JSON.parse(xhr.responseText);if (result.success) {alert("上传成功!");document.getElementById("result").innerHTML = "文件路径:" + result.filePath + "<br>" +"预览:<img src='" + result.filePath + "' style='max-width: 300px;'>";} else {alert("上传失败:" + result.message);}} else {alert("上传失败,状态码:" + xhr.status);}}};// 发送请求xhr.open("POST", "FileUploadServlet", true);// 上传文件时不要设置Content-Type,浏览器会自动处理xhr.send(formData);
}
对应的 JSP 页面:
<div class="form-group"><label for="file">选择文件:</label><input type="file" id="file" accept="image/*">
</div>
<div class="form-group"><label for="description">描述:</label><input type="text" id="description" placeholder="请输入文件描述">
</div>
<div class="progress" style="width: 360px; height: 20px; border: 1px solid #ccc; margin-left: 105px;"><div id="progressBar" style="width: 0%; height: 100%; background-color: #4CAF50;"></div>
</div>
<div id="progressText" style="margin-left: 105px; margin-top: 5px;">0%</div>
<div class="form-group"><input type="button" value="上传" onclick="uploadFile()">
</div>
<div id="result" style="margin-left: 105px; margin-top: 10px;"></div>
AJAX 异步分页案例
结合之前的分页技术,使用 AJAX 实现无刷新分页:
1. 分页 Servlet
@WebServlet("/AjaxUserPageServlet")
public class AjaxUserPageServlet extends HttpServlet {private UserDAO userDAO = new UserDAO();@Overrideprotected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {response.setContentType("application/json;charset=UTF-8");// 获取分页参数int currentPage = 1;int pageSize = 10;try {currentPage = Integer.parseInt(request.getParameter("currentPage"));pageSize = Integer.parseInt(request.getParameter("pageSize"));} catch (NumberFormatException e) {// 使用默认值}// 获取查询条件String username = request.getParameter("username");// 获取排序参数String sortField = request.getParameter("sortField");String sortOrder = request.getParameter("sortOrder");// 查询分页数据PageBean<User> pageBean = new PageBean<>(pageSize, currentPage);pageBean.setSortField(sortField);pageBean.setSortOrder(sortOrder);if (username != null && !username.trim().isEmpty()) {pageBean = userDAO.getUsersByConditionSortAndPage(username.trim(), pageBean);} else {pageBean = userDAO.getUsersByConditionSortAndPage(null, pageBean);}// 转换为JSON并响应ObjectMapper mapper = new ObjectMapper();// 处理日期格式mapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));String json = mapper.writeValueAsString(pageBean);response.getWriter().write(json);}
}
2. 客户端分页实现
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head><title>AJAX分页示例</title><style>/* 样式省略,参考之前的分页页面 */</style>
</head>
<body><div class="container"><h2>用户列表(AJAX分页)</h2><!-- 搜索栏 --><div class="search-bar"><input type="text" id="username" placeholder="请输入用户名搜索"><input type="button" value="搜索" onclick="loadPage(1)"><select id="pageSize" onchange="loadPage(1)"><option value="5">5条/页</option><option value="10" selected>10条/页</option><option value="20">20条/页</option></select></div><!-- 数据表格 --><table><thead><tr><th>ID</th><th><a href="javascript:sortBy('username')">用户名</a></th><th><a href="javascript:sortBy('email')">邮箱</a></th><th><a href="javascript:sortBy('createTime')">注册时间</a></th><th><a href="javascript:sortBy('status')">状态</a></th></tr></thead><tbody id="userTableBody"><!-- 数据将通过AJAX动态加载 --><tr><td colspan="5" style="text-align: center;">加载中...</td></tr></tbody></table><!-- 分页导航 --><div id="pagination" class="page-nav"><!-- 分页导航将通过AJAX动态生成 --></div><!-- 加载状态提示 --><div id="loading" style="display: none; text-align: center; padding: 20px;">加载中...</div></div><script>// 当前页码和分页参数var currentPage = 1;var pageSize = 10;var sortField = "createTime";var sortOrder = "DESC";// 页面加载完成后加载第一页数据window.onload = function() {loadPage(1);};// 加载指定页数据function loadPage(pageNum) {// 显示加载状态document.getElementById("loading").style.display = "block";// 更新当前页码currentPage = pageNum;// 获取查询条件var username = document.getElementById("username").value.trim();pageSize = document.getElementById("pageSize").value;// 创建XHR对象var xhr = createXHR();// 处理响应xhr.onreadystatechange = function() {if (xhr.readyState === 4) {// 隐藏加载状态document.getElementById("loading").style.display = "none";if (xhr.status === 200) {try {var pageBean = JSON.parse(xhr.responseText);// 更新表格数据updateTable(pageBean.dataList);// 更新分页导航updatePagination(pageBean);} catch (e) {console.error("解析JSON失败:", e);document.getElementById("userTableBody").innerHTML = "<tr><td colspan='5' style='text-align: center; color: red;'>数据格式错误</td></tr>";}} else {document.getElementById("userTableBody").innerHTML = "<tr><td colspan='5' style='text-align: center; color: red;'>加载失败,状态码:" + xhr.status + "</td></tr>";}}};// 构建请求URLvar url = "AjaxUserPageServlet?" +"currentPage=" + pageNum +"&pageSize=" + pageSize +"&username=" + encodeURIComponent(username) +"&sortField=" + sortField +"&sortOrder=" + sortOrder +"&t=" + new Date().getTime();// 发送请求xhr.open("GET", url, true);xhr.send(null);}// 更新表格数据function updateTable(userList) {var tableBody = document.getElementById("userTableBody");if (userList.length === 0) {tableBody.innerHTML = "<tr><td colspan='5' style='text-align: center;'>暂无数据</td></tr>";return;}var html = "";for (var i = 0; i < userList.length; i++) {var user = userList[i];html += "<tr>";html += "<td>" + user.id + "</td>";html += "<td>" + user.username + "</td>";html += "<td>" + user.email + "</td>";html += "<td>" + user.createdTime + "</td>";html += "<td>" + (user.status === 1 ? "<span style='color: green;'>正常</span>" : "<span style='color: red;'>禁用</span>") + "</td>";html += "</tr>";}tableBody.innerHTML = html;}// 更新分页导航function updatePagination(pageBean) {var pagination = document.getElementById("pagination");var html = "";// 首页html += "<a href='javascript:loadPage(1)' " + (pageBean.currentPage === 1 ? "style='pointer-events: none; opacity: 0.5;'" : "") + ">首页</a>";// 上一页html += "<a href='javascript:loadPage(" + pageBean.prevPage + ")' " + (!pageBean.hasPrevPage ? "style='pointer-events: none; opacity: 0.5;'" : "") + ">上一页</a>";// 页码var startPage = Math.max(1, pageBean.currentPage - 3);var endPage = Math.min(pageBean.totalPage, pageBean.currentPage + 3);// 调整页码范围if (endPage - startPage < 6 && pageBean.totalPage > 6) {if (startPage === 1) {endPage = 7;} else if (endPage === pageBean.totalPage) {startPage = pageBean.totalPage - 6;}}for (var i = startPage; i <= endPage; i++) {if (i === pageBean.currentPage) {html += "<span class='active'>" + i + "</span>";} else {html += "<a href='javascript:loadPage(" + i + ")'>" + i + "</a>";}}// 下一页html += "<a href='javascript:loadPage(" + pageBean.nextPage + ")' " + (!pageBean.hasNextPage ? "style='pointer-events: none; opacity: 0.5;'" : "") + ">下一页</a>";// 末页html += "<a href='javascript:loadPage(" + pageBean.totalPage + ")' " + (pageBean.currentPage === pageBean.totalPage ? "style='pointer-events: none; opacity: 0.5;'" : "") + ">末页</a>";// 分页信息html += "<span>共 " + pageBean.totalCount + " 条记录,共 " + pageBean.totalPage + " 页,当前第 " + pageBean.currentPage + " 页</span>";pagination.innerHTML = html;}// 排序功能function sortBy(field) {if (sortField === field) {// 切换排序方向sortOrder = sortOrder === "ASC" ? "DESC" : "ASC";} else {// 新的排序字段,默认降序sortField = field;sortOrder = "DESC";}// 重新加载第一页loadPage(1);}// 创建XHR对象的函数function createXHR() {var xhr;if (window.XMLHttpRequest) {xhr = new XMLHttpRequest();} else {xhr = new ActiveXObject("Microsoft.XMLHTTP");}return xhr;}</script>
</body>
</html>
AJAX 最佳实践
1. 错误处理
完善的错误处理是 AJAX 应用的重要组成部分:
// 健壮的AJAX错误处理
function safeAjaxRequest(url, method, data, successCallback, errorCallback) {// 参数验证if (!url || !method) {if (errorCallback) errorCallback(new Error("URL和请求方法不能为空"));return;}var xhr = createXHR();// 超时设置(5秒)xhr.timeout = 5000;xhr.onreadystatechange = function() {if (xhr.readyState === 4) {if (xhr.status === 200) {try {// 尝试解析JSONvar response = JSON.parse(xhr.responseText);if (successCallback) successCallback(response);} catch (e) {if (errorCallback) {errorCallback(new Error("响应数据格式错误: " + e.message));} else {console.error("响应数据格式错误: ", e);}}} else {var errorMsg = "请求失败,状态码: " + xhr.status;if (xhr.status === 404) errorMsg = "请求的资源不存在";if (xhr.status === 500) errorMsg = "服务器内部错误";if (errorCallback) {errorCallback(new Error(errorMsg));} else {console.error(errorMsg);}}}};// 网络错误处理xhr.onerror = function() {var error = new Error("网络错误,无法连接到服务器");if (errorCallback) errorCallback(error);else console.error(error.message);};// 超时处理xhr.ontimeout = function() {var error = new Error("请求超时,请稍后重试");if (errorCallback) errorCallback(error);else console.error(error.message);};// 发送请求xhr.open(method, url, true);if (method.toUpperCase() === "POST" && !(data instanceof FormData)) {xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");}xhr.send(data || null);// 返回xhr对象,允许调用abort()return xhr;
}
2. 性能优化
- 请求合并:将多个小请求合并为一个大请求,减少 HTTP 请求次数
- 请求防抖:对于频繁触发的事件(如输入、滚动),延迟发送请求
- 缓存响应:对不常变化的数据进行本地缓存,减少重复请求
- 压缩数据:使用 gzip 压缩服务器响应,减少传输数据量
- 使用 HTTP/2:支持多路复用,提高并发请求效率
- 预加载:在空闲时预加载可能需要的数据
3. 安全性考虑
防止 XSS 攻击:
- 服务器对输出进行 HTML 转义
- 客户端使用
textContent
而非innerHTML
插入不可信内容
防止 CSRF 攻击:
- 使用 CSRF 令牌验证请求来源
- 检查 Referer 请求头
数据验证:
- 客户端验证仅作为辅助,必须在服务器端进行严格验证
- 对所有用户输入进行过滤和转义
限制请求频率:
- 服务器端实现限流机制,防止恶意请求
- 客户端添加请求间隔限制
4. 用户体验优化
加载状态反馈:
- 显示加载动画或进度条
- 提供取消请求的选项
错误提示友好:
- 使用用户易懂的语言描述错误
- 提供解决问题的建议
离线支持:
- 使用 Service Worker 缓存静态资源
- 实现离线操作和数据同步
进度指示:
- 对于耗时操作(如下载、上传),显示进度信息
- 预估完成时间
现代 AJAX 替代方案
虽然原生 XMLHttpRequest 功能强大,但使用起来比较繁琐。现代前端开发中,有更便捷的替代方案:
1. Fetch API
Fetch API 是现代浏览器提供的用于替代 XMLHttpRequest 的 API,基于 Promise,语法更简洁:
// 使用Fetch API发送请求
fetch('JsonDataServlet?username=' + encodeURIComponent(username)).then(response => {if (!response.ok) {throw new Error('HTTP error, status = ' + response.status);}return response.json();}).then(data => {console.log('成功:', data);// 处理数据}).catch(error => {console.error('错误:', error);});
2. Axios
Axios 是一个流行的第三方 AJAX 库,支持 Promise API,提供了更多功能:
// 使用Axios发送请求
axios.get('JsonDataServlet', {params: {username: username}
})
.then(response => {console.log('成功:', response.data);// 处理数据
})
.catch(error => {console.error('错误:', error);
});
在 JavaWeb 项目中使用 Axios,只需引入 CDN:
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
总结与实践
知识点回顾
AJAX 基础:
- AJAX 允许在不刷新页面的情况下与服务器交换数据
- 核心是 XMLHttpRequest 对象,负责异步通信
- 支持 GET 和 POST 等 HTTP 方法
数据交互:
- 服务器通常返回 JSON 格式数据
- 客户端使用 JSON.parse () 解析响应
- 可以提交表单数据和上传文件
高级应用:
- 实时验证:如用户名唯一性检查
- 异步分页:无刷新加载分页数据
- 文件上传:带进度显示的文件上传
最佳实践:
- 完善的错误处理和超时控制
- 性能优化:请求合并、防抖、缓存
- 安全性考虑:防止 XSS、CSRF 攻击
- 良好的用户体验:加载状态、友好提示
实践任务
实时聊天系统:
- 使用 AJAX 实现简单的实时聊天功能
- 定期轮询服务器获取新消息
- 支持发送消息和显示消息历史
动态数据仪表盘:
- 实现数据的实时刷新
- 添加图表展示(使用 Chart.js)
- 支持数据筛选和时间范围选择
无刷新购物车:
- 实现商品的添加、删除、数量修改
- 实时计算总价和优惠信息
- 支持本地存储购物车数据