AJAX
1. 概述
传统请求及缺点
传统的请求有哪些?
- 直接在浏览器地址栏输入 URL
- 点击超链接
- 提交 form 表单
- 使用 JS 代码发送请求
- window.open(url)
- document.location.href = url
- window.location.href = url
- …
存在的问题:
- 页面会全部刷新,导致用户体验差
- 传统请求会使用户体验不连贯
关于 AJAX
Asynchronous JavaScript And XML(异步的 JavaScript 和 XML)
AJAX 代码属于 JS 代码
AJAX 不是新的编程语言,而是一种使用现有标准的新方法,解决了传统请求存在的问题
AJAX 最大的优点:可以在不重新加载页面的情况下,与服务器交换数据并更新部分内容(局部刷新)
异步和同步
- 异步
客户端不需要等待服务器端的响应。在服务器处理请求的过程中,客户端可以进行其他的操作
ajax 请求1和 ajax 请求2,同时并发,谁也不用等谁 - 同步
客户端必须等待服务器端的响应。在等待的期间客户端不能做其他操作
ajax 请求1在发送时,需要等待 ajax 请求2结束之后才能发送
2. 核心:XMLHttpRequest 对象
XMLHttpRequest 对象是 AJAX 的核心对象,负责发送请求和接收服务器返回的数据
目前的浏览器都内置了该对象
使用
创建 XMLHttpRequest 对象:
1 | var xhr = new XMLHttpRequest(); |
相关方法
方法 | 描述 |
---|---|
abort() | 取消当前请求 |
getAllResponseHeaders() | 返回头部信息 |
getResponseHeader() | 返回特定的头部信息 |
open(method, url, async, user, psw) | 规定请求。 method: 请求方式 url: 文件位置 async: 异步或同步 user: 可选的用户名称 psw: 可选的密码 |
send() | 将请求发送到服务器,用于 GET 请求 |
send(string) | 将请求发送到服务器,用于 POST 请求 |
setRequestHeader() | 向要发送的报头添加标签/值对 |
相关属性
属性 | 描述 |
---|---|
onreadystatechange | 定义当 readyState 属性发生变化时被调用的函数 |
readyState | 保存 XMLHttpReqeust 对象的状态。 0:请求未初始化 1:服务器连接已建立 2:请求已收到 3:正在处理请求 4:请求已完成且响应已就绪 |
responseText | 以字符串返回响应数据 |
responseXML | 以 XML 数据返回响应数据 |
status | 返回请求的状态号。 200:”OK” 403:”Forbidden” 404:”Not Found” |
statusText | 返回状态文本(如:”OK” 或 “Not Found”) |
3.1 GET 请求
示例代码
ajax.html:
1 |
|
AjaxRequestServlet:
1 | /** |
缓存问题
- AJAX 请求缓存问题
- HTTP 协议中规定:get 请求会被缓存起来
- 发送 AJAX GET 请求时,对于低版本的浏览器来说,第二次的请求会走缓存,不走服务器
- POST 请求不会被浏览器缓存起来
- GET 请求缓存的优缺点
- 优点:直接从浏览器缓存中获取资源,不需要从服务器上重新加载,速度快,用户体验好
- 缺点:无法实时获取最新的服务器资源
- 浏览器什么时候走缓存?
- GET 请求
- 请求路径被浏览器缓存了,第二次发送请求时,路径没有变化
- 低版本的浏览器怎么解决 AJAX GET 请求的缓存问题?
- 在请求路径 url 后边添加时间戳或随机数,这样每次的路径都会发生改变
- “url?t=” + new Date().now()
- “url?t=” + Math.random()
3.2 POST 请求
与 GET 请求的区别:
- 只有前端代码有区别
- post 请求提交的数据在 send 方法中,而 get 请求提交的数据在 open 中
- post 请求需要设置请求头的内容类型,get 请求不需要
示例代码
1 |
|
案例
使用 AJAX POST 请求实现验证用户名
步骤:
- 前端:用户输入用户名后,失去焦点事件 blur 发生,然后发送 AJAX POST 请求,提交用户名
- 后端:接收用户名后,连接数据库,查询是否可用
ajax.html:
1 |
|
AjaxRequestServlet:
1 | /** |
4. 数据交换
基于 JSON 的数据交换
在 WEB 前端中,将一个 json 格式的字符串转换为 json 对象的方法:
- eval 函数
1
2var fromJava = "{\"name\": \"tom\", \"gender\": \"man\"}";
window.eval("var jsonObj = " + fromJava); - 内置对象 JSON 的 parse 方法
1
2var jsonStr = "{\"username\" : \"xxx\"}";
var jsonObj = JSON.parse(jsonStr);
fastjson
使用阿里巴巴的 fastjson 组件可以非常便利地将 java 对象转换成 json 格式的字符串
注意:需要导入 fastjson 的 jar 包
1 | String jsonStr = JSON.toJSONString(); |
基于 XML 的数据交换
如果服务器端响应 XML 的话,需要修改响应的内容类型
1 | response.setContentType("text/xml;charset=UTF-8"); |
补充:AJAX 乱码问题
Tomcat 9以及之前版本,需要设置字符集,否则会出现中文乱码问题
解决方案:
- 响应时乱码
1
response.setContentType("text/html;charset=UTF-8");
- 发送 AJAX post 请求时,服务器接收乱码
1
request.setCharacterEncoding("UTF-8");
5. AJAX 代码封装到 jQuery 库
jQuery 是一个轻量级的 JavaScript 库,可以简化 JS 代码的编写。这里我们手动封装一个 jQuery 库
手动开发 jQuery 源码:
1 | function jQuery(selector) { |
使用以上 jQuery 库
1 | <script type="text/javascript" src="jQuery 的路径"></script> |
6. 跨域问题
跨域
- 跨域指从一个域名的网页请求另一个域名的资源
- 传统的请求,能直接修改地址的,都能实现跨域访问。但是 AJAX 请求由于
同源策略
的存在,无法跨域访问
同源策略
- 同源策略是指一段脚本只能读取来自同一来源的窗口和文档的属性
- 为了保护网站信息的安全策略
- 同源三要素
- 协议
- 域名
- 端口号
实现 AJAX 跨域
方案1:设置响应头
1 | response.setHeader("Access-Control-Allow-Origin", "允许跨域的 URL"); // 如果设置为 * 号,则允许所有的 URL 跨域访问 |
方案2:jsonp
注意:jsonp 解决跨域问题时,只支持 GET 请求
jsonp:json with padding(带填充的 json)
jsonp 不是 ajax 请求,但是可以完成局部刷新的效果,并且可以解决跨域问题
本质上,是用 script 标签进行跨域访问
打开页面时,跨域访问
1
<script type="text/javascript" src="URL"></script>
点击按钮,跨域访问
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17<script type="text/javascript">
// ES 6 的新写法
window.onload = () => {
document.getElementById("btn").onclick = () => {
// 创建 script 元素对象
const htmlScriptElement = document.createElement("script");
// 设置 type
htmlScriptElement.type = "text/javascript";
// 设置 src
htmlScriptElement.src = "URL";
// 添加到 body 标签中
document.getElementsByTagName("body")[0].appendChild(htmlScriptElement);
}
}
</script>
<button id="btn">我是按钮,点击实现跨域访问</button>
方案3:jQuery 封装的 jsonp
引入 jQuery 库的 js 文件,直接使用封装好的 jsonp
这个 jsonp 就是方案2的高度封装,底层原理完全相同(只是一个工具,会用就行)
示例代码:
一个域中的 ajax.html
1 |
|
另一个域的 JsonpServlet
1 | /** |
方案4:代理机制(httpclient)
怎么使用 Java 程序发送 get/post 请求?
- JDK 内置的 API(java.net.URL…)
- 第三方的开源组件,例如:apache 的 httpclient 组件
方案5:nginx 反向代理
- nginx 反向代理中也是使用了上边的这种代理机制来完成 AJAX 的跨域,实现起来只需要修改一个 nginx 的配置即可。以后会学的~
7. AJAX 实现功能
省市联动
什么是省市联动?
在网页上,选择对应的省份之后,动态的关联出该省份对应的市。选择对应的市后,动态的关联出对应的区数据库表设计
1
2
3
4
5
6
7
8
9t_area(区域表)
id(PK-自增) code name pcode
-------------------------------------
1 001 北京市 null
2 002 上海市 null
3 003 朝阳区 001
4 004 海淀区 001
5 005 闵行区 002
6 006 徐汇区 002前端代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
<html lang="en">
<head>
<meta charset="UTF-8">
<title>省市联动</title>
</head>
<body>
<!-- 引入自己写的 jQuery 库 -->
<script type="text/javascript" src="/ajax04/js/jQuery-1.0.0.js"></script>
<script>
$(function () {
$.ajax({
type: "get",
url: "/ajax04/listArea",
data: "",
async: true,
success: function (jsonArr) {
var html = "<option value=''>--请选择省份--</option>";
for (var i = 0; i < jsonArr.length; i++) {
var area = jsonArr[i];
html += "<option value='" + area.code + "'>" + area.name + "</option>";
}
$("#province").html(html);
}
})
$("#province").change(function () {
$.ajax({
type: "get",
url: "/ajax04/listArea",
data: "pcode=" + this.value,
async: true,
success: function (jsonArr) {
var html = "<option value=''>--请选择市--</option>";
for (var i = 0; i < jsonArr.length; i++) {
var area = jsonArr[i];
html += "<option value='" + area.code + "'>" + area.name + "</option>";
}
$("#city").html(html);
}
})
})
})
</script>
<select id="province"></select>
<select id="city"></select>
</body>
</html>后端代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
public class ListAreaServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String pcode = request.getParameter("pcode");
List<Area> areas = new ArrayList<>();
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
Class.forName("com.mysql.cj.jdbc.Driver");
String url = "jdbc:mysql://localhost:3306/forajax";
String user = "root";
String password = "123456";
conn = DriverManager.getConnection(url, user, password);
String sql = "";
if (pcode == null) {
sql = "select code, name from t_area where pcode is null";
ps = conn.prepareStatement(sql);
} else {
sql = "select code, name from t_area where pcode = ?";
ps = conn.prepareStatement(sql);
ps.setString(1, pcode);
}
rs = ps.executeQuery();
while (rs.next()) {
String code = rs.getString("code");
String name = rs.getString("name");
Area a = new Area(code, name);
areas.add(a);
}
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
if (ps != null) {
try {
conn.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
if (rs != null) {
try {
conn.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
response.setContentType("text/html;charset=UTF-8");
String json = JSON.toJSONString(areas);
response.getWriter().print(json);
}
}
搜索联想 自动补全
搜索联想和自动补全功能,是页面局部刷新效果,需要使用 AJAX 请求来完成
核心原理
- 当键盘事件发生之后,发送 AJAX 请求,请求中提交用户输入的内容
- 后端接收到 AJAX 请求,执行 select 语句进行模糊查询,返回查询结果
- 将查询结果封装成 json 格式的字符串,然后将 json 格式的字符串响应到前端
- 前端接收到 json 格式的字符串后,解析该字符串,动态展示到页面上
核心代码
前端:autocomplete.html
1 |
|
后端:AutoCompleteServlet
1 | /** |