????????在前二十天的學習中,我們掌握了 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)
- 支持數據篩選和時間范圍選擇
無刷新購物車:
- 實現商品的添加、刪除、數量修改
- 實時計算總價和優惠信息
- 支持本地存儲購物車數據