1 介绍
AJAX(Asynchronized Javascript And Xml,异步的JS与XML)。
1.1 同步和异步
同步和异步是B/S架构的两种重要模式,异步多了一个JS发出请求的函数和一个接收响应的回调(callback)函数,耗资源大但是效率较高。而同步虽然低效,但是耗资源少。异步与同步配合工作。
同步
用户发出请求,必须等待响应的返回,在响应返回前,用户必须等待,
不能进行任何操作,当响应返回整个页面全部刷新
异步
用户发出请求,不需要等待响应的返回,用户可以继续自己的操作,在未来的
一个时间段,响应应该返回,返回之后不影响用户当前的操作,页面部分更新
1.1.1 基于AJAX的异步传输与传统同步传输的对比
大多数情况下,传统同步传输尽管可能只改变了一行文本或者一个图像,但还是不能避免完全的页面刷新。如图:
使用基于AJAX的异步传输,用户不必等候页面长时间加载,甚至服务器处理请求时,用户还可以继续使用页面。如图:
1.1.2 使用时间线表示同步和异步在过程上的区别
-
同步:
-
异步:
1.2 JS与AJAX的关系
JS中并没有异步相关的技术,AJAX是使用JS语法的一个衍生技术,专门用来实现异步操作,严格地说AJAX不能算是JS的类库之一。
- 常见的JS类库
protoType YUI ExtJs jQuery dojo node.js Angular React VUE
1.3 AJAX用到的技术
- 核心语法 JS
- 用来封装和解析数据 XML
- 解析XML DOM4j
- 封装大量数据 JSON
- 动态页面表现技术囊括了CSS XHTML HTML5等前端技术 DHTML
1.4 AJAX支持的三种数据类型(MIME)
- 字符串 text/plain
- JSON text/plain
- XML text/xml
2 在原生JS中使用AJAX的步骤
以查重这个应用为例,学习在原生JS中使用AJAX的步骤。
checkName.html
<!DOCTYPE html>
<html>
<head>
<title>查重</title>
<meta charset="utf-8" />
</head>
<body>
<form action="#" method="get">
<label for="nameid">用户姓名:</label>
<input type="text" name="name" required
id="nameid" onblur="checkName(this.value)" />
<span id="name_msg"></span>
<br />
<input type="submit" value="提交" id="sub" disabled />
</form>
<script>
//1):创建异步请求
let request;
function create(){
/*
以下写法为level2版本的创建异步请求的方式
如果遇到较为陈旧的浏览器 例如IE6 7 8 甚至更老的浏览器
推荐使用level1版本的创建方式
if(window.XMLHttpRequest){
request = new XMLHttpRequest();
}else{
request = new ActiveXObject("Microsoft.XMLHttp");
}
*/
request = new XMLHttpRequest();
}
//2):此函数为主函数,用来发送异步请求
function checkName(value){
//a:创建异步请求
create();
//b:设置发送异步请求的目的地
//request.open(method, url, async, user, password)
/*
method:异步请求的提交方式 get或者post
url:提交到哪里,注意如果是get方式则从此处传递值 url?key=value
async:表示是否使用异步,默认为true使用异步,如果更改为
false则使用同步,请求发出之后响应返回前浏览器锁死等待响应返回
user与password:表示是否开启服务器安全策略,如果不填写表示不开启,
tocmat等WEB服务器默认不开启
*/
request.open("post","servlet/Check",true);
//c:如果请求方式为post,则必须指定字符流来传递数据(request.send())
//get请求方式不需要书写此句
//因为get传递的参数在地址后面拼(虚拟地址),直接以字符解析
request.setRequestHeader("Content-Type","application/x-www-form-urlencoded");
//d:声明回调函数
//这里的函数名为callback也可以随意书写但是注意不能添加括号,
//添加括号表示调用,不加表示声明
request.onreadystatechange = callback;
//另外一种写法,直接等于一个匿名方法
/*
request.onreadystatechange = function(){
}
*/
//e:如果请求方式为post,在此处传递值,即使不传递值也必须书写此句,内部填写null即可
request.send("name="+value);
}
//回调函数的写法之一,另外一种可以直接把这个方法匿名地写在
//onreadystatechange的等号后面
function callback(){
//保证返回的响应完整
if(request.readyState==4){
//保证服务器没有任何异常
if(request.status==200){
//接受服务器返回的数据
let value = request.responseText;
//拿取span
let dom_sp = document.getElementById("name_msg");
//拿取submit
let dom_sub = document.getElementById("sub");
//根据响应数据显示span和submit按钮的内容
if(value=="exist"){
dom_sp.innerHTML="用户名已经被占用";
dom_sp.style.color="red";
dom_sub.disabled = true;
return;
}
dom_sp.innerHTML="用户名可以使用";
dom_sp.style.color="green";
dom_sub.disabled = false;
}
}
}
</script>
</body>
</html>
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" id="WebApp_ID" version="3.1">
<display-name>AjaxDay1_check</display-name>
<servlet>
<servlet-name>Check</servlet-name>
<servlet-class>com.test.servlet.Check</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Check</servlet-name>
<url-pattern>/servlet/Check</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.htm</welcome-file>
<welcome-file>index.jsp</welcome-file>
<welcome-file>default.html</welcome-file>
<welcome-file>default.htm</welcome-file>
<welcome-file>default.jsp</welcome-file>
</welcome-file-list>
</web-app>
servlet/Check.java
package com.test.servlet;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class Check extends HttpServlet {
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/plain;charset=utf-8");
request.setCharacterEncoding("utf-8");
PrintWriter out = response.getWriter();
String value = request.getParameter("test");
System.out.println("接受过来的用户名是:"+value);
if(value.trim().equals("eric")){
//返回响应给回调函数
/*
* 注意
* 1.out.print在这里并不是输出页面,而是给回调函数返回响应
* 2.不能使用println,否则返回的字符串会自动添加“\n”
* */
out.print("exist");
out.close();
return;
}
out.print("suc");
out.close();
}
}
- 效果图:
2.1 以上代码中需要注意的部分
2.1.1 AJAX回调函数
只要readystate参数从2到4发生变化就执行一次回调函数。在这里readystate到4条件判断才执行,其实值为234时也执行了,只不过这里在上面的程序中没有代码执行。结合AJAX执行顺序见图:
- 注释:
-
c、d、e步骤:
c(视请求方式而定).如果请求方式为post,则必须指定字符流来传递数据,指定Content-Type。get请求方式不需要书写此句,因为get只传递字符。request.setRequestHeader(“Content-Type”,“application/x-www-form-urlencoded”);
d.声明回调函数,两种方式,见上面源码。
e.如果请求方式为post,在此处传递值,即使不传递值也必须书写此句,内部填写null即可。request.send(“name=”+value); //这是当post传值时,不传值时填写null
-
- 状态码指令集:
1xx 返回消息
2xx 成功
3xx 重定向
4xx 请求错误 如401 403 404 405
5xx 服务器内部错误 如500
2.1.2 Content-Type(setRequestHeader()中的参数)(了解)
注意:需要先调用open方法,之后再调用setRequestHeader方法!
-
为何要用到setRequestHeader
在HTTP协议里,客户端向服务器请求取得某个网页的时候,必须发送一个HTTP协议的头文件,告诉服务器客户端要下载什么信息以及相关的参数。 而XMLHTTP就是通过HTTP协议取得网站上的文件数据的,所以也要发送HTTP头给服务器。 但XMLHTTP默认的情况下有些参数可能没有说明在HTTP头里,当我们需要修改或添加这些参数时就用到了setRequestHeader方法。 -
setRequestHeader参数详解
使用GET下列参数XMLObject.setRequestHeader (“CONTENT-TYPE”, “application/x-www-form-urlencoded” ),得到HTTP头:
POST /bb.asp HTTP/1.1
Accept: /
Accept-Language: zh-cn
UA-CPU: x86
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.2; SV1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)
CONTENT-TYPE:application/x-www-form-urlencoded
Host: ourys.com
Content-length: 8
Connection: close
Cookie:%C3%F7%CC%EC=%B0%CB;ASPSESSIONIDASDBSDRR=BLEDBIBBCGKBJAKJCFEJKGII
注:
- CONTENT-TYPE: application/x-www-form-urlencoded含义是表示客户端提交给服务器文本内容的编码方式是URL编码,即除了标准字符外,每字节以双字节16进制前加个“%”表示。当然还有其他编码方式,如CONTENT-TYPE:multipart/form-data。
- Content-length:表示提交的数据字节大小,GET方式是没有提交内容的,GET传参通过虚拟地址传送,如GET /bb.asp?www=1234 HTTP/1.1,参数全部就只有 “www=1234” 这么多。所以 CONTENT-TYPE、Content-length在GET模式下是无效的。如果用POST的话就有些不同,POST将参数放到HTTP后面,以上面的HTTP为例,用POST的方法传参数“www=1234”时,发请求时需要声明编码方式,报头中出现Content-length大小了。
- Connection: Close,很明显英文的意思是连接:关闭,只是客户端在提交数据时告诉服务器让谁先关闭连接而已。
本节文字摘自Zero28093@优快云,感谢大佬分享!
3 在原生JS中使用AJAX处理XML数据以及DBUtils的使用
不再赘述数据库设计以及web.xml、Factory类、po层、dao层的接口,前面的文章中有写。在dao层的实现类代码中介绍DBUtils的使用。
3.1 例:使用AJAX异步请求读取数据库信息画出表格并填入信息
dao/StudentDaoImpl.java
package com.test.dao;
import com.test.factory.Factory;
import com.test.po.Student;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import java.sql.Connection;
import java.util.List;
public class StudentDaoImpl implements StudentDaoIf{
Connection con;
//引入DBUtils的查询执行器,QueryRunner是DBUtils框架的核心类
QueryRunner qr = new QueryRunner();
@Override
public List<Student> queryAll() {
try{
String sql = "select * from student";
con = Factory.getCon();
/*
* 进行查询操作,使用DBUtil,减轻增删改查的工作量
* 被操作的实体类对应数据库的表,且有空参和全参构造函数
* 否则会造成无法取出或取出的字段不全
* 不需要再关连接,DBUtils自动关闭。
*
* qr.query(con,sql,ResultSetHandler)
* con:表示数据源
* sql:表示sql语句
* ResultSetHandler:接口 可以通过调用此接口的子接口
* 根据不同的sql语句进行操作
* 返回 独立实体类 new BeanHandler<实体类名>(实体类名.class)
* 返回 多个实体类集合 new BeanListHandler<实体类名>(实体类名.class)
* 返回 long类型变量 new scalarHandler(); 返回的query为long类型,
* 若方法返回值为int,需要转换
* */
return qr.query(con,sql,new BeanListHandler<Student>(Student.class));
}catch(Exception ex){
ex.printStackTrace();
return null;
}
}
@Override
public boolean delStuById(Integer id) {
try{
con = Factory.getCon();
String sql = "delete from student where id = ?";
//增删改均用update方法
return qr.update(con,sql,id)==1;
}catch(Exception ex){
ex.printStackTrace();
return false;
}
}
}
servlet/ShowStudent.java
package com.test.servlet;
import com.test.dao.StudentDaoIf;
import com.test.dao.StudentDaoImpl;
import com.test.po.Student;
import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;
public class ShowStudent extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.setCharacterEncoding("utf-8");
response.setContentType("text/xml;charset=utf-8");
//以字符流将后面定义的XML输出到response中响应到客户端供AJAX解析
PrintWriter out = response.getWriter();
StudentDaoIf dao = new StudentDaoImpl();
List<Student> list = dao.queryAll();
//创建一个文档数据模型(document模型)
Document doc = DocumentHelper.createDocument();
//创建一个根元素
/*
* <root></root>
* */
Element root = doc.addElement("root");
for(Student stu : list){
//以根元素root为根,创建多个一级子元素
/*
* <root>
* <student></student>
* <student></student>
* <student></student>
* ***
* </root>
*
* */
Element student = root.addElement("student");
//在student标签中创建属性
/*
* <root>
* <student id="" name=""></student>
* <student id="" name=""></student>
* <student id="" name=""></student>
* ***
* </root>
*
* */
student.addAttribute("id", stu.getId() + "");
student.addAttribute("name", stu.getName());
//以一级子元素student为根,创建二级子元素
/*
* <root>
* <student id="" name="">
* <email>***</email>
* <phone>***</phone>
* </student>
* <student id="" name="">
* <email>***</email>
* <phone>***</phone>
* </student>
* <student id="" name="">
* <email>***</email>
* <phone>***</phone>
* </student>
* ***
* </root>
*
* */
Element email = student.addElement("email");
email.setText(stu.getEmail());
Element phone = student.addElement("phone");
phone.setText(stu.getPhone());
}
//直接将document模型转换为XML输出到响应中
out.print(doc.asXML());
System.out.println(doc.asXML());
out.close();
}
}
xmlOperation.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>使用xml处理批量数据</title>
<style>
table{
width: 500px;
border-collapse: collapse;
}
table,tr,td{
border:solid 2px silver;
}
</style>
</head>
<!--页面加载完成后就执行showAll()-->
<body onload="showAll()">
<!--定义一个空的div,后面使用原生JS写表格和数据到其中-->
<div id="show"></div>
<script>
let request;
function create(){
request = new XMLHttpRequest();
}
function showAll(){
create();
request.open("post", "showStudent", true);
request.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
//匿名回调函数
request.onreadystatechange = function(){
//前面写到的readyState过程
if(request.readyState==4) {
if (request.status == 200) {
//返回一个xml标准格式的文档数据模型
let doc = request.responseXML;
/*
* <root>
* <student id="" name="">
* <email>***</email>
* <phone>***</phone>
* </student>
* <student id="" name="">
* <email>***</email>
* <phone>***</phone>
* </student>
* <student id="" name="">
* <email>***</email>
* <phone>***</phone>
* </student>
* ***
* </root>
*
* */
//根据标签名拿取多个元素
let array = doc.getElementsByTagName("student");
//在页面显示表格,先画表头
let table = "<table><tr><td>ID</td><td>姓名</td><td>邮箱</td><td>手机</td><td>操作</td></tr>";
for (let i = 0; i < array.length; i++) {
//逐个拿取student标签
let student = array[i];
//拿取student标签中的id属性
let id = student.getAttribute("id");
//拿取同上的name属性
let name = student.getAttribute("name");
//拿取student内部email元素内部的值,
//这种取法虽然啰嗦但是浏览器适配性高(里面就一个文本内容但还要以第一个子元素的身份取,还要再获取一下节点值)
let email = student.getElementsByTagName("email")[0].firstChild.nodeValue;
let phone = student.getElementsByTagName("phone")[0].firstChild.nodeValue;
//画表格,将获取到的值画在表格体中,相当于拼字符串
table += "<tr><td>" + id + "</td><td>" + name + "</td><td>"
+ email + "</td><td>" + phone + "</td><td><label onclick='delStu(" + id + ")' style='cursor:pointer'>删除</label></td></tr>";
}
table += "</table>";
document.getElementById("show").innerHTML = table;
}
}
};
request.send(null);
}
</script>
</body>
</html>
效果图:
3.2 例:使用AJAX异步请求实现假删除(标记删除)
所谓的假删除是只在页面上删除,而数据库中依然存在。这就要求在数据库实体类对应的表中新增一个默认为0(也可以默认为其他数字)的字段,当用户删除一条信息时即更新这个字段为1,表示已删除。指定dao层SQL语句中查询全部数据并显示的方法加入过滤条件,当这个字段为0时可以查出,1时表示已删除不能显示。
更新3.1中dao层实现类的queryAll方法中的SQL语句
String sql = “select * from student where isdeleted = 0”;
新增3.1中dao层实现类delStuById方法
@Override
public boolean delStuById(Integer id) {
try{
con = Factory.getCon();
String sql = "update student set isdeleted = 1 where id = ?";
QueryRunner qr = new QueryRunner();
//增删改均用update方法
return qr.update(con,sql,id)==1;
}catch(Exception ex){
ex.printStackTrace();
return false;
}
}
更新xmlOperation.html中的JS函数
function delStu(id){
if(confirm("确定删除这条数据吗?")){
create();
request.open("post", "del", true);
request.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
request.onreadystatechange = function(){
if(request.readyState == 4){
if(request.status == 200){
let value = request.responseText;
if(value == "suc"){
//执行上面的显示方法再次异步查询显示数据
showAll();
return;
}
alert("删除失败!");
}
}
};
// <a href="目的地?key=value">
request.send("id=" + id);
}
}
servlet/DelStudent.java
package com.test.servlet;
import com.test.dao.StudentDaoIf;
import com.test.dao.StudentDaoImpl;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
public class DelStudent extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.setCharacterEncoding("utf-8");
response.setContentType("text/plain;charset=utf-8");
StudentDaoIf dao = new StudentDaoImpl();
PrintWriter out = response.getWriter();
if(dao.delStuById(Integer.parseInt(request.getParameter("id")))){
out.print("suc");
out.close();
return;
}
out.print("err");
out.close();
}
}