Ajax

111 阅读7分钟

AJAX (Asynchronous Javascript And Xml)

ajax意为"异步的Javascript和Xml"

ajax不是一个技术,而是多个技术结合的产物

传统的请求,如url地址栏回车,form表单提交,js代码,超链接的方式发送的请求有什么缺点?

  1. 收到服务器响应后页面会全部刷新,用户体验差
  2. 传统请求导致用户的体验有空白期,体验不连贯

AJAX不但可以做到不刷新页面展示新页面,还可以在浏览器中发送异步请求,请求A和B是异步的,谁也不需要等谁,类似于多线程并发,从现在起,学了ajax后尽可能使用ajax发送请求

对于AJAX来说,服务器(WEB Server端)可能会给WEB前端响应回来三种数据:可能是普通文本,可能是XML字符串,可能是JSON字符串

想在浏览器发送ajax请求,这些代码都是js语法的代码,其实发送ajax请求,就是要编写js代码,必须用前端浏览器里的XMLHttpRequest对象发送ajax请求

XMLHttpRequest

Ajax请求的发送和接收服务器的响应,完全靠XMLHttpRequest对象完成

  • XMLHttpRequest对象中的readyState属性记录下了XMLHttpRequest对象的状态

    readyState属性值

    0 请求未初始化 1 服务器连接已建立 2 请求已收到 3 正在处理情求 4 请求已完成且响应已就绪

    属性的值变成4的时候,表示这个AJAX请求以及响应己经全部完成了

  • XMLHttpRequest对象中的onreadystatechange属性用来定义当上面的readyState属性值发生变化时被调用的函数

  • 通过XMLHttpRequest对象的responseText属性来获取响应的信息

下面写一个简单的ajaxGET请求

服务器端代码该咋写还咋写,只不过返回的响应内容在浏览器端被XMLHttpRequest对象接收到了而已

package com.study.ajax;

import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;
import java.io.PrintWriter;

@WebServlet("/request01")
public class AjaxServlet1 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        PrintWriter out = resp.getWriter();
        out.print("nb666");
    }
}

前端代码,当判断XMLHttpRequest对象的状态是4且HTTP状态是200成功时,就收内容,用XMLHttpRequest对象的responseText属性接收文本形式的响应内容放到div,可以看到页面没有刷新,但内容更新了

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>ajax get请求</title>
</head>
<body>

<script type="text/javascript">
    window.onload = function () {
        document.getElementById("btn").onclick = function () {
            // 发送ajax的get请求,一共四步

            // 1. 创建XMLHttpRequest对象
            var xhr = new XMLHttpRequest()

            // 2. 注册回调函数
            xhr.onreadystatechange = function () {

                // readyState状态码为4表示xhr对象请求已完成且响应已就绪
                if (this.readyState == 4) {
                    // status是HTTP状态码,注意和上面区分
                    if (this.status == 404) {
                        alert("访问的资源不存在")
                    } else if (this.status == 500) {
                        alert("服务器错误")
                    } else if (this.status == 200) {
                        console.log("响应成功")
                        // 将收到的结果放的div中
                        document.getElementById("myDiv").innerHTML = this.responseText
                    }
                }
            }

            // 3. 开启通道(只是浏览器和服务器建立连接,现在还不会发送请求)
            //
            // open(method,url,async,user,psw)
            // method:请求的方式,可以是GET,也可以是POST,也可以是其它请求方式
            //    url:请求的路径
            //  async:只能是true或者false,true表示此ajax请求是一个异步请求,
            //        false表示此ajax请求是一个同步请求,大部分请求都是true
            //        异步,极少数情况需要同步
            //   user:用户名
            //    pwd:密码用,户名和密码是进行身份认证的,说明要想访问这个服务器上
            //        的资源,可能需要提供一些口令才能访问,需不需要用户名和密码,
            //        主要看服务器的态度
            xhr.open("GET", "/ajax/request01", true)

            // 4. 发送请求
            xhr.send()
        }
    }

</script>

<input type="button" value="发送ajax" id="btn">
<div id="myDiv"></div>
</body>
</html>

ajax请求的get怎么提交数据?

在"请求行"上提交,格式是:url?name=value&name=value&name=value,也就是在url后面跟上键值对即可

xhr.open("GET", "/ajax/request01?name=zhangsan&age=18", true)

对于低版本的IE浏览器来说,ajax的get请求可能会走缓存,存在缓存问题,对于现代的浏览器来说,大部分浏览器都已经不存在ajax的get缓存问题了

什么是AJAX GET请求缓存问题呢?

  • 在HTTP协议中是这样规定get请求的:get请求会被缓存起来,POST请求不会被浏览器缓存
  • GET请求缓存的优缺点:
    • 优点:直接从浏览器缓存中获取资源,不需要从服务器上重新加载资源,速度较快,用户体验好
    • 缺点:无法实时获取最新的服务器资源
  • 浏览器什么时候会走缓存?
    • 第一:是一个GET请求
    • 第二:请求路径已经被浏览器缓存过了,第二次发送请求的时候,这个路径没有变化,会走浏览器缓存

怎么解决GET请求缓存问题呢

给请求路径后面加一个时间戳或者随机数,浏览器一看这个请求没发过,就会主动发送新的get请求了,不会走缓存

下面写一个简单的ajaxPOST请求

html

注意,设置xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded")才会模拟成表单提交,不加这句话会报错

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>ajax post</title>
</head>
<body>
<script>
    window.onload = function () {
        document.getElementById("btn").onclick = function () {
            //发送ajax post
            // 1 创建xhr对象
            var xhr = new XMLHttpRequest();
            // 2 创建回调函数
            xhr.onreadystatechange = function () {
                if (this.readyState == 4) {
                    if (this.status == 200) {
                        document.getElementById("p1").innerHTML = this.responseText
                    } else {
                        alert(this.status)
                    }
                }
            }
            // 3 开启通道
            xhr.open("POST", "/ajax/request03", true)
            // 4 发送请求
            // 模拟form表单,设置请求头的内容类型,非常关键!
            xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded")// 设置请求头的内容类型,模拟form表单提交数据

            // 获取网页上的数据
            var username = document.getElementById("username").value;
            var password = document.getElementById("password").value;
            
            // 放到send函数里面的数据,会在请求体中提交数据
            // 使用js代码获取用户名和密码,填写进去,注意格式不能乱来,还是键值对&的形式
            xhr.send("username=" + username + "&password=" + password)
        }
    }
</script>
<button id="btn">发送ajax post</button>
user:<input type="text" id="username"><br/>
pwd:<input type="password" id="password"><br/>
<p id="p1"></p>
</body>
</html>

注意,post传参需要将参数放到send函数里,格式是name=value&name=value&name=value的格式,可以拼接字符串

servlet

package com.study.ajax;

import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;
import java.io.PrintWriter;

@WebServlet("/request03")
public class AjaxServlet3 extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html;charset=UTF-8");
        // 获取前端的数据
        String username = req.getParameter("username");
        String password = req.getParameter("password");
        PrintWriter writer = resp.getWriter();
        writer.print("username:" + username + ",password:" + password);
    }
}

下面实现一个文本框填写用户名后,鼠标失焦显示用户名是否可用的功能

  1. 在前端用户输入用户名之后,失去焦点事件blur发生,然后发送AJAX POST请求,提交用户名
  2. 在后端,接收到用户名,连接数据库,根据用户名去表中搜索
    • 如果用户名已存在
      • 后端响应消息:对不起,用户名已存在(在前端页面以红色字体展示)
    • 如果用户名不存在
      • 后端响应消息:用户名可以使用(在前端页面以绿色字体展示)

java

package com.study.ajax;

import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;
import java.io.PrintWriter;
import java.sql.*;

// 验证用户名是否可用
@WebServlet("/request04")
public class AjaxServlet4 extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String username = req.getParameter("username");
        // 连接数据库,验证用户名是否存在
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;
        boolean flag = false;
        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
            connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/test?serverTimezone=UTC", "root", "root");
            String sql = "select * from stu where name = ?";
            preparedStatement = connection.prepareStatement(sql);
            preparedStatement.setString(1, username);
            resultSet = preparedStatement.executeQuery();
            while (resultSet.next()) {
                // 用户名存在
                flag = true;
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (connection != null) {
                try {
                    connection.close();
                } catch (SQLException e) {
                    throw new RuntimeException(e);
                }
            }
            if (preparedStatement != null) {
                try {
                    preparedStatement.close();
                } catch (SQLException e) {
                    throw new RuntimeException(e);
                }
            }
            if (resultSet != null) {
                try {
                    resultSet.close();
                } catch (SQLException e) {
                    throw new RuntimeException(e);
                }
            }
        }
        resp.setContentType("text/html;charset=UTF-8");
        PrintWriter out = resp.getWriter();
        if (flag) {
            out.print("<span style=\"color: red\">exist</span>");
        } else {
            out.print("<span style=\"color: green\">ok</span>");
        }
    }
}

html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>验证用户名是否可用</title>
</head>
<body>
<script type="text/javascript">
    window.onload = function () {
        document.getElementById("username").onblur = function () {
            //1
            var xhr = new XMLHttpRequest();
            //2
            xhr.onreadystatechange = function () {
                if (this.readyState === 4) {
                    if (this.status === 200) {
                        console.log("成功")
                        document.getElementById("result").innerHTML = this.responseText
                    } else {
                        alert("fail, " + this.status)
                    }
                }
            }
            //3
            xhr.open("POST", "/ajax/request04", true)
            xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded")
            //4
            var username = document.getElementById("username").value
            xhr.send("username=" + username)
        }
    }
</script>
用户名:<input type="text" id="username">
<span id="result"></span>
</body>
</html>

JSON格式

后端能不能只返回数据,即不用再返回out.print("<span style=\"color: red\">exist</span>");这样的代码了,而是只给"exit"值让前端自己去渲染,那么Java和JavaScript之间的桥梁就是JSON格式

再JavaScript中如何创建json对象?

var jsonobj = {
    "id" : 1,
    "name" : "张三",
    "age" : true
}

// 属性值的类型随意

如何访问json对象的属性?

  1. console.log(jsonobj.id)
  2. console.log(jsonobj["sex"])

但是,后端一般返回的是一个JSON格式的字符串,并不是一个JSON对象,那么怎样将接收到的字符串转换成JSON对象?

  1. eval函数

  2. 调用JavaScript的内置对象JSON的一个方法parse

    JSON.parse()

使用JSON字符串响应到浏览器,浏览器解析后渲染

java

package com.study.ajax;

import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;
import java.io.PrintWriter;
import java.sql.*;

@WebServlet("/request06")
public class AjaxServlet6 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String username = req.getParameter("username");
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;
        StringBuilder json = new StringBuilder();
        String jsonStr = "";
        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
            connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/test?serverTimezone=UTC", "root", "root");
            String sql = "select name,age,addr from t_user";
            preparedStatement = connection.prepareStatement(sql);
            resultSet = preparedStatement.executeQuery();
            json.append("[");
            while (resultSet.next()) {
                String name = resultSet.getString("name");
                String age = resultSet.getString("age");
                String addr = resultSet.getString("addr");
                json.append("{\"name\":\"");
                json.append(name);
                json.append("\",\"age\":\"");
                json.append(age);
                json.append("\",\"addr\":\"");
                json.append(addr);
                json.append("\"},");
            }
            jsonStr = json.substring(0, json.length() - 1) + "]";
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (connection != null) {
                try {
                    connection.close();
                } catch (SQLException e) {
                    throw new RuntimeException(e);
                }
            }
            if (preparedStatement != null) {
                try {
                    preparedStatement.close();
                } catch (SQLException e) {
                    throw new RuntimeException(e);
                }
            }
            if (resultSet != null) {
                try {
                    resultSet.close();
                } catch (SQLException e) {
                    throw new RuntimeException(e);
                }
            }
        }
        resp.setContentType("text/html;charset=UTF-8");
        PrintWriter out = resp.getWriter();
        out.print(jsonStr);
        System.out.println(jsonStr);
    }
}

html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<script type="text/javascript">
    window.onload = function () {
        document.getElementById("btn").onclick = function () {
            var xhr = new XMLHttpRequest();
            xhr.onreadystatechange = function () {
                if (this.readyState === 4) {
                    if (this.status === 200) {
                        console.log("成功")
                        var userList = JSON.parse(this.responseText);
                        var html = ""
                        for (let i = 0; i < userList.length; i++) {
                            var user = userList[i]
                            html += "<tr>"
                            html += "<td>" + (i + 1) + "</td>"
                            html += "<td>" + user.name + "</td>"
                            html += "<td>" + user.age + "</td>"
                            html += "<td>" + user.addr + "</td>"
                            html += "</tr>"
                        }
                        document.getElementById("result").innerHTML = html
                    } else {
                        alert("fail, " + this.status)
                    }
                }
            }
            //3
            xhr.open("GET", "/ajax/request06", true)
            //4
            xhr.send()
        }
    }
</script>
<button id="btn">点击获取列表</button>
<table>
    <tr>
        <td>NO.</td>
        <td>name</td>
        <td>age</td>
        <td>addr</td>
    </tr>
    <tbody id="result"></tbody>
</table>
</body>
</html>

这里手动拼json字符串太累,可以使用第三方库调用方法,传入一个对象或集合,自动生成json字符串

XML格式

前后端数据交换也可以使用xml,但是没有JSON格式用的多

后端:

  1. 设置响应的格式

    response.setContentType("text/xml;charset=UTF-8");
    
  2. 给字符串中拼接标签内容

    resp.setContentType("text/xml;charset=UTF-8");
    PrintWriter out = resp.getWriter();
    StringBuilder xml = new StringBuilder();
    xml.append("<students>");
    xml.append("<student>");
    xml.append("<name>tom</name>");
    xml.append("</student>");
    xml.append("<student>");
    xml.append("<name>jerry</name>");
    xml.append("</student>");
    xml.append("</students>");
    out.print(xml);
    

前端

  1. 使用XMLHttpRequest对象的responseXML属性,接收返回之后,可以自动封装成document对象(文档对象)

    var xmlDoc = this.responseXML//文档对象
    var students = xmlDoc.getElementsByTagName("student");//返回数组
    for (let i = 0; i < students.length; i++) {
        var student = students[i]
        var properties = student.childNodes
        console.log(properties[0].textContent)//得到名字name
    }
    

对于Tomcat10来说,不会出现乱码问题

Tomcat9及之前的版本Get的响应必须加response.setContentType("text/html;charset=UTF-8");,不加的话响应到浏览器会乱码

Post的请求必须加requset.setCharacterEncoding("UTF-8");,不加的话获取前端请求会乱码

Ajax什么时候选择异步请求,什么时候选择异步请求?

大部分情况都是异步,只有再一些如注册页面,上面的校验用户名是否可用,下面提交注册,这时候校验用户名是否可用的请求只能用同步,否则点击提交上面的校验结果还没回来,可能会将非法数据提交上去

后面的封装没看,地址: www.bilibili.com/video/BV1cR…

看到p22