JavaWeb 学习笔记 8:AJAX
AJAX(Asynchronous JavaScript And XML,异步 js 和 XML)是一种用 js 代码异步(或同步)的方式请求服务端数据,并在页面显示或加载的技术。
1.快速入门
先看如何用纯 js 的方式使用 AJAX:
定义一个用于响应 AJAX 请求的 Servlet:
@WebServlet("/hello")
public class HelloServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().println("Hello World!");
}
}
定义一个 html 页面:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<button onclick="doAjaxRequest()">send</button>
</body>
<script>
function doAjaxRequest(){
// 创建 XMLHttpRequest 对象
const xhttp = new XMLHttpRequest();
// 定义回调函数
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
let result = this.responseText;
console.log(result);
}
};
// 发送请求
xhttp.open("GET", "http://localhost:8080/ajax-demo/hello", true);
xhttp.send();
}
</script>
</html>
js 方法doAjaxRequest
负责创建一个 XMLHttpRequest 对象,并用其发送 AJAX 请求,然后将返回的内容打印在控制台中。
关于原生 AJAX 的详细说明,可以阅读这里。
点击按钮,可以看到浏览器发送了一个类型为 XHR(XMLHttpRequest) 的请求到服务端:
并且可以看到控制台输出。
2.案例:验证用户是否存在
服务端:
@WebServlet("/user/exist")
public class ExistController extends HttpServlet {
private UserService userService = new UserServiceImpl();
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String username = req.getParameter("username");
boolean exists = userService.checkUsernameExists(username);
if (exists){
resp.getWriter().print("true");
}
else{
resp.getWriter().print("false");
}
}
}
在页面表单中输入用户名的元素上设置光标焦点事件:
<input name="username" type="text" id="username" onblur="checkUsernameExists()">
对应的 js:
// 检查用户名是否存在
function checkUsernameExists() {
let username = $("input#username").val();
const xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
let result = this.responseText;
console.log(result)
if (result == "true"){
console.log("用户名已存在")
$("span#username_err").html("用户名已存在");
$("span#username_err").show();
}
else{
$("span#username_err").hide();
}
}
};
xhttp.open("POST", "http://localhost:8080/login-demo/user/exist?username="+username, true);
xhttp.send();
}
3.Axios
Axios 是封装好的一个异步调用框架,可以运行于浏览器或 Node.js 服务器上。
使用 Axios 可以让我们之前的 AJAX 调用的代码更为简单。
在 html 中添加对 Axios 的引用:
<head>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
</head>
修改 AJAX 部分代码,使用 Axios 进行调用:
// 检查用户名是否存在
function checkUsernameExists() {
let username = $("input#username").val();
axios({
method: 'post',
url: '/login-demo/user/exist?username=' + username
})
.then(function (response) {
let result = response.data;
if (result) {
$("span#username_err").html("用户名已存在");
$("span#username_err").show();
} else {
$("span#username_err").hide();
}
});
}
axios
对象可以接收一个 js 对象,其method
属性指定了 HTTP Method,url
指定请求的 url。
需要注意的是,这里 Axios 将返回的响应报文体进行了解析,所以这里
response.data
不是一个字符串形式的true
或false
,而是 bool 类型。
除了上边的方式,还可以使用一种简化的方式:
// 检查用户名是否存在
function checkUsernameExists() {
let username = $("input#username").val();
axios.post('/login-demo/user/exist?username=' + username)
.then(function (response) {
let result = response.data;
if (result) {
$("span#username_err").html("用户名已存在");
$("span#username_err").show();
} else {
$("span#username_err").hide();
}
});
}
4.JSON
通常浏览器和服务端通过异步调用方式传输的数据结构都很复杂,所以会使用 JSON 格式的字符串进行传输。
浏览器端:
// 检查用户名是否存在
function checkUsernameExists() {
let username = $("input#username").val();
axios.post('/login-demo/user/exist', {"username": username})
.then(function (response) {
let result = response.data;
if (result.exist) {
$("span#username_err").html("用户名已存在");
$("span#username_err").show();
} else {
$("span#username_err").hide();
}
});
}
服务端:
@WebServlet("/user/exist")
public class ExistController extends HttpServlet {
private UserService userService = new UserServiceImpl();
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
BufferedReader reader = req.getReader();
StringBuilder sb = new StringBuilder();
do{
String line = reader.readLine();
if (line == null){
break;
}
sb.append(line);
}
while (true);
String content = sb.toString();
System.out.println(content);
JSONObject jsonObject = JSON.parseObject(content);
String username = (String) jsonObject.get("username");
boolean exists = userService.checkUsernameExists(username);
JSONObject resultJO = new JSONObject();
if (exists){
resultJO.put("exist", true);
}
else{
resultJO.put("exist", false);
}
resp.setContentType("application/json;charset=UTF-8");
resp.getWriter().print(resultJO.toJSONString());
}
}
这里使用了一个中间件 FastJSON,用于在服务端解析和编码 JSON 字符串:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.75</version>
</dependency>
5.案例:异步加载品牌列表
可以用 Axios+JSON 异步加载品牌列表。
后端:
@WebServlet("/brand/list")
public class ListController extends HttpServlet {
// ...
/**
* 获取品牌列表
* @param request
* @param response
* @throws ServletException
* @throws IOException
*/
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/json;charset=utf-8");
List<Brand> brands = brandService.getAllBrands();
response.getWriter().print(JSON.toJSONString(brands));
}
}
前端:
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script type="text/javascript" src="/login-demo/js/jquery-3.7.1.min.js"></script>
<script type="text/javascript" src="/login-demo/js/axios-0.18.0.js"></script>
</head>
<body>
<h1>${username},欢迎您</h1>
<a href="/login-demo/brand/add"><input type="button" value="新增"></a><br>
<hr>
<table id="brandsTable" border="1" cellspacing="0" width="800">
<tr>
<th>序号</th>
<th>品牌名称</th>
<th>企业名称</th>
<th>排序</th>
<th>品牌介绍</th>
<th>状态</th>
<th>操作</th>
</tr>
<tr align="center" class="brandsRow" hidden="hidden">
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td><a href="/login-demo/brand/edit?id=#" class="edit">修改</a>
<a href="/login-demo/brand/delete?id=#" class="delete">删除</a></td>
</tr>
</table>
</body>
<script type="text/javascript">
$(document).ready(function () {
loadBrandsTable();
});
function loadBrandsTable() {
let table = $("table#brandsTable");
let demoRow = table.find("tr.brandsRow:first");
// 获取品牌列表信息
axios({
"method": "POST",
"url": "/login-demo/brand/list"
})
.then((resp)=>{
let brands = resp.data;
brands.map((brand)=>{
let newRow = demoRow.clone();
console.log(newRow);
fillRowData(newRow, brand);
table.find("tr:last").after(newRow);
});
});
}
function fillRowData(newRow, data){
newRow.children("td:eq(0)").html(data.id);
newRow.children("td:eq(1)").html(data.brandName);
newRow.children("td:eq(2)").html(data.companyName);
newRow.children("td:eq(3)").html(data.ordered);
newRow.children("td:eq(4)").html(data.description);
let status = "禁用";
if (data.status == 0){
status = "启用";
}
newRow.children("td:eq(5)").html(status);
newRow.children("td:eq(6)").children("a.edit").attr("href","/login-demo/brand/edit?id="+data.id);
newRow.children("td:eq(6)").children("a.delete").attr("href","/login-demo/brand/delete?id="+data.id);
newRow.removeAttr("hidden");
}
</script>
</html>
这里为表格添加了一个用于示例数据的行,并使用 JQuery 选择器获取这个行的 DOM 对象,并进行拷贝,填充数据后依次添加到表格的末尾。
用类似的方式可以将新增品牌也修改为前端异步提交而非表单提交,这里不再赘述。
6.Template
在前边的示例中,使用一个表单中的“隐藏行”作为模板,用于遍历品牌信息并填充数据,然后添加到表格 DOM 树中,并最终称为一个有数据的表格。
这样做是可行的,但是“隐藏行”只是用户看不见,实际上依然是存在的,并且会影响页面的渲染速度。对此,有一个专门用于此类问题的 Html 标签 template,可以用它作为模板 Html 代码存放的位置。这样做的好处在于,<template>
标签中的内容是一个 DocumentFragment(文档片段),它不是当前Document
(文档)的一部分,不是任何DOM
对象的子节点,所以它不会参与页面渲染,就不影响性能。但同时我们依然可以通过 js 代码获取其内部 DOM 结构,作为模板进行使用。
使用 template 标签改写之前的示例:
<table id="brandsTable" border="1" cellspacing="0" width="800">
<tr>
<th>序号</th>
<th>品牌名称</th>
<th>企业名称</th>
<th>排序</th>
<th>品牌介绍</th>
<th>状态</th>
<th>操作</th>
</tr>
</table>
<template id="rowTemplate">
<tr align="center">
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td><a href="/login-demo/brand/edit?id=#" class="edit">修改</a>
<a href="/login-demo/brand/delete?id=#" class="delete">删除</a></td>
</tr>
</template>
对应的 js 代码:
function loadBrandsTable() {
let table = $("table#brandsTable");
let demoRow = $("#rowTemplate").get(0).content.querySelector("tr");
// 获取品牌列表信息
axios({
"method": "POST",
"url": "/login-demo/brand/list"
})
.then((resp)=>{
let brands = resp.data;
brands.map((brand)=>{
let newRow = $(demoRow).clone();
// console.log(newRow);
fillRowData(newRow, brand);
table.find("tr:last").after(newRow);
});
});
}
需要注意的是,template 标签的内容是文档片段,而文档片段并不是当前文档的一部分。如果用开发者工具观察文档结构:
template 标签本身是当前文档的一部分,是可以通过文档操作的方式获取的,比如用 JQuery:$("#rowTemplate").get(0)
。但是,其内部的内容是一个文档片段(#document-fragment
标记的部分),这部分内容并不属于当前文档,所以你是没办法用类似$("#rowTemplate").children()
的方式获取到的,这个筛选项的长度只能是0,没有任何内容。
除了上边示例中使用template
的content
属性获取 DOM 对象外,还可以用 JQuery 的方式:
let demoRow = $($("#rowTemplate").html()).get(0);
这里是先获取template
标签内的 html 内容,然后用 JQuery 解析以生成 DOM 对象。
两者效果是相同的,只是写法不同。
本文的完整示例可以从这里获取。
7.参考资料
- 黑马程序员JavaWeb基础教程
- AJAX 简介 (w3school.com.cn)
- jQuery 教程 (w3school.com.cn)
- DocumentFragment - Web API 接口参考 | MDN (mozilla.org)
- DOMParser - Web API 接口参考 | MDN (mozilla.org)
- javascript中html字符串转化为jquery dom对象的方法