在前二十天的学习中,我们掌握了 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 {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 设置响应内容类型为JSON
response.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();
@Override
protected 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>";
}
}
};
// 构建请求URL
var 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 {
// 尝试解析JSON
var 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)
- 支持数据筛选和时间范围选择
-
无刷新购物车:
- 实现商品的添加、删除、数量修改
- 实时计算总价和优惠信息
- 支持本地存储购物车数据