Ajax(Asynchronous JavaScript and XML)通過 XMLHttpRequest 實現瀏覽器與服務器異步通信,避免整頁刷新,提升用戶體驗。本文通過一個完整的用戶註冊案例,演示用戶名異步校驗、服務條款異步加載、驗證碼生成三大核心場景的前後端實現。
| 模式 | 特點 | 用戶體驗 |
|---|---|---|
| 同步 | 提交請求 → 等待響應 → 頁面跳轉/重繪 | 阻塞,無法操作 |
| 異步 | 事件觸發請求 → 後台處理 → 回調更新局部頁面 | 非阻塞,可繼續操作 |
readyState:0(未初始化) 1(已open) 2(已send) 3(接收中) 4(完成)status:HTTP 狀態碼(200 成功)responseText:文本響應responseXML:XML 響應<%@ page contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>
<html>
<head>
<title>用戶註冊</title>
<link href="css/regist.css" rel="stylesheet" type="text/css" />
<script src="js/regist_ajax.js" type="text/javascript"></script>
</head>
<body onload="getRegistRuleTxt(), getCheckcode()">
<form action="regist.do" onsubmit="return check(this);">
<table>
<tr>
<td>用戶名:</td>
<td><input type="text" name="username" id="username" onblur="postValidate()" />
<span id="checkMsg">* 用戶名由字母、數字、下劃線組成</span></td>
</tr>
<tr>
<td>密碼:</td>
<td><input type="password" name="password" id="password" /></td>
</tr>
<tr>
<td>確認密碼:</td>
<td><input type="password" name="repassword" id="repassword" /></td>
</tr>
<tr>
<td>郵箱:</td>
<td><input type="text" name="email" id="email" /></td>
</tr>
<tr>
<td>驗證碼:</td>
<td>
<input type="text" id="checkcode" />
<img id="ckcodeimage" src="imgsrc" />
<a href="javascript:;" onclick="getCheckcode()">看不清,換一張</a>
<span id="ckcodeMsg"></span>
</td>
</tr>
<tr>
<td>服務條款:</td>
<td><textarea rows="5" cols="48" readonly id="item"></textarea></td>
</tr>
<tr><td><input type="submit" value="同意條款並註冊" /></td></tr>
</table>
</form>
</body>
</html>
var xmlHttpRequest = getXmlHttpRequest();
// 跨瀏覽器創建 XHR
function getXmlHttpRequest() {
var xhr = null;
if (typeof XMLHttpRequest != 'undefined') {
xhr = new XMLHttpRequest(); // 現代瀏覽器
} else {
xhr = new ActiveXObject('Microsoft.XMLHTTP'); // IE6-
}
return xhr;
}
// 防緩存時間戳
function addTimestamp(url) {
return url + (url.indexOf('?') != -1 ? '&' : '?') + 'timestamp=' + new Date().valueOf();
}
// 1. 異步加載服務條款(頁面加載時觸發)
function getRegistRuleTxt() {
var item = document.getElementById("item");
var url = "rulesText.do";
xmlHttpRequest.open("POST", encodeURI(url), true);
xmlHttpRequest.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xmlHttpRequest.onreadystatechange = function() {
if (xmlHttpRequest.readyState == 4) {
if (xmlHttpRequest.status == 200) {
item.value = xmlHttpRequest.responseText;
} else {
item.value = "系統錯誤";
}
} else {
item.value = "加載中...";
}
};
xmlHttpRequest.send(null);
}
// 2. POST 方式驗證用戶名(失焦觸發)
function postValidate() {
var username = document.getElementById("username");
var url = "validatename.do";
xmlHttpRequest.open('POST', url, true);
xmlHttpRequest.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xmlHttpRequest.onreadystatechange = callback;
xmlHttpRequest.send('username=' + escape(username.value));
}
// 3. 統一回調處理
function callback() {
if (xmlHttpRequest.readyState == 4) {
if (xmlHttpRequest.status == 200) {
innerHtmlMsg(xmlHttpRequest.responseText);
} else {
innerHtmlMsg("error");
}
} else {
innerHtmlMsg("checking");
}
}
function innerHtmlMsg(message) {
var checkMsg = document.getElementById("checkMsg");
switch (message) {
case 'exists': checkMsg.innerHTML = "<font color='red'>* 對不起,該用戶名已存在</font>"; break;
case 'noexists': checkMsg.innerHTML = "<font color='green'>* 恭喜,該用戶名可用</font>"; break;
case 'checking': checkMsg.innerHTML = "<font color='#0099aa'>* 系統正在檢查...</font>"; break;
default: checkMsg.innerHTML = "<font color='red'>系統錯誤</font>";
}
}
// 4. 驗證碼刷新
function getCheckcode() {
var codeimage = document.getElementById("ckcodeimage");
codeimage.src = addTimestamp("checkcode.do");
}
table { margin-top:70px; width:780px; border-collapse:collapse; font-size:14px; border:1px solid gray; }
.left { font-weight:500; color:#708899; padding-right:20px; background-color:#D6ECF5; }
.inpt { border:solid #92CEDB 1px; width:210px; height:22px; margin-left:10px; }
.rghts { margin-top:20px; color:#708899; font-size:12px; }
<web-app>
<servlet>
<servlet-name>UserServlet</servlet-name>
<servlet-class>com.webapp.servlet.UserServlet</servlet-class>
<init-param>
<param-name>rulesfilepath</param-name>
<param-value>/txt/item.txt</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>UserServlet</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>regist.jsp</welcome-file>
</welcome-file-list>
</web-app>
package com.webapp.util;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
public class CheckCodeImageUtil {
private static final String[] CHARS = {"0","1","2","3","4","5","6","7","8","9","A","B","C","D","E","F","G","H","I"};
private static final int SIZE = 4, LINES = 4, WIDTH = 110, HEIGHT = 40, FONT_SIZE = 21;
public static Map creatCheckImage() {
StringBuffer buffer = new StringBuffer();
BufferedImage image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
Map map = new HashMap<>();
Graphics g = image.getGraphics();
g.setColor(Color.WHITE);
g.fillRect(0, 0, WIDTH, HEIGHT);
Random random = new Random();
// 繪製隨機字符
for (int i = 0; i < SIZE; i++) {
int rand = random.nextInt(CHARS.length);
g.setColor(randomColor());
g.setFont(new Font(null, Font.BOLD | Font.ITALIC, FONT_SIZE));
g.drawString(CHARS[rand], i * WIDTH / SIZE + 8, HEIGHT / 2 + 10);
buffer.append(CHARS[rand]);
}
// 繪製干擾線
for (int i = 1; i <= LINES; i++) {
g.setColor(randomColor());
g.drawLine(random.nextInt(WIDTH), random.nextInt(HEIGHT), random.nextInt(WIDTH), random.nextInt(HEIGHT));
if (i == LINES) {
g.setFont(new Font(null, Font.ITALIC, 13));
g.setColor(Color.GRAY);
g.drawString("工作室", 5, 15);
}
}
map.put(buffer.toString(), image);
return map;
}
private static Color randomColor() {
Random r = new Random();
return new Color(r.nextInt(256), r.nextInt(256), r.nextInt(256));
}
}
package com.webapp.servlet;
import com.webapp.util.CheckCodeImageUtil;
import javax.imageio.ImageIO;
import javax.servlet.*;
import javax.servlet.http.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.util.*;
public class UserServlet extends HttpServlet {
private List userList; // 模擬用戶庫
private String txtFilePath;
@Override
public void init() throws ServletException {
txtFilePath = getInitParameter("rulesfilepath");
userList = new Vector<>(Arrays.asList("zhangsan", "lisi", "wangwu", "zhaoliu"));
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String path = req.getRequestURI().substring(req.getRequestURI().lastIndexOf("/"), req.getRequestURI().lastIndexOf("."));
req.setCharacterEncoding("utf-8");
if ("/validatename".equals(path)) {
resp.setContentType("text/html;charset=utf-8");
try { Thread.sleep(3000); } catch (InterruptedException e) {} // 模擬延遲
String username = req.getParameter("username");
resp.getWriter().print(userList.contains(username) ? "exists" : "noexists");
}
else if ("/rulesText".equals(path)) {
resp.setContentType("text/html;charset=utf-8");
String filePath = getServletContext().getRealPath(txtFilePath);
StringBuilder sb = new StringBuilder();
try (BufferedReader br = new BufferedReader(new FileReader(filePath))) {
String line;
while ((line = br.readLine()) != null) {
sb.append(new String(line.getBytes("gbk"), "utf-8")).append("\n");
}
}
resp.getWriter().print(sb.toString());
}
else if ("/checkcode".equals(path)) {
resp.setContentType("image/jpeg");
Map map = CheckCodeImageUtil.creatCheckImage();
String code = map.keySet().iterator().next();
req.getSession().setAttribute("code", code); // 存入 Session
ImageIO.write(map.get(code), "jpeg", resp.getOutputStream());
}
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req, resp);
}
}
| 場景 | 關鍵技術 | 注意事項 |
|---|---|---|
| 用戶名驗證 | POST + escape 編碼 + 異步回調 | 防緩存時間戳、狀態碼判斷、提示用戶體驗 |
| 條款加載 | 頁面 onload 觸發 + 文件讀取編碼轉換 | GBK→UTF-8 轉碼、大文本流式輸出 |
| 驗證碼生成 | BufferedImage + Graphics 繪圖 + Session 存儲 | 干擾線/字符、隨機顏色、點擊刷新機制 |
fetch API / axios 替代原生 XHR,配合 async/await 簡化回調本案例完整展示了 Ajax 在表單驗證、內容異步加載、圖形驗證碼三大經典場景的落地實現。雖然技術棧偏傳統(JSP/Servlet/原生 JS),但核心思想——異步非阻塞、局部刷新、前後端協議約定——在現代前後端分離架構中依然通用。建議讀者以此為基礎,遷移至 Vue/React + Spring Boot + RESTful API 的現代棧中實踐。