-ajax教程:https://www.tutorialspoint.com/ajax/index.htm
Demo地址: https://gitee.com/hebing_myproject/xmlhttprequest-demo
MDN参考地址:https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest
XMLHttpRequest标准:https://xhr.spec.whatwg.org/
XMLHttpRequest Level 2 使用指南:http://www.ruanyifeng.com/blog/2012/09/xmlhttprequest_level_2.html
Introduction to XMLHttpRequest Level 2: https://dev.opera.com/articles/xhr2/#xhrprogressevents

一、ajax简介

AJAX 是一种在无需重新加载整个网页的情况下,能够更新部分网页的技术。AJAX 即 Asynchronous JavaScript and XML. AJAX 通过在后台与服务器进行少量数据交换,使网页实现异步更新。这意味着可以在不重载整个页面的情况下,对网页的某些部分进行更新。传统的网页(不使用 AJAX)如果需要更新内容,必须重载整个页面。

1.1 AJAX 如何工作

image.png

二、XMLHttpRequest 的使用及分析

2.1 XMLHttpRequest 的简单使用

如下 是使用浏览器提供的XMLHttpRequest 类实现的 ajax 。简单总结:

  • 创建一个 XMLHttpRequest 对象;
  • 调用XMLHttpRequest 的 open(method, url, ) 方法 创建一个请求,open方法的两个参数,method表示 请求方式GET/POST等。url 表示请求地址(此方法有多个重载,后面详说);
  • 调用 XMLHttpRequest 的 send 发放发送一个请求;
  • 定义 XMLHttpRequest 的 onreadystatechange 事件方法,监听XMLHttpRequest请求的状态;
  • 根据请求返回的 HTTP 状态码做对应的处理

    1. <!DOCTYPE html>
    2. <html>
    3. <head>
    4. <meta charset="utf-8">
    5. <title>XMLHttpRequest的第一次使用</title>
    6. <script src="../js/jquery-3.1.0.js"></script>
    7. <script src="../js/commonUtil.js"></script>
    8. </head>
    9. <body>
    10. <h1>Just test</h1>
    11. <button id="get-data">XMLHttpRequest的第一次 - 从服务器获取数据</button>
    12. </body>
    13. <script>
    14. //给btn绑定一个点击事件,点击的时候通过ajax从后台获取数据
    15. $("#get-data").on("click", function() {
    16. //1. 创建一个 XMLHttpRequest 对象
    17. var request = new XMLHttpRequest();
    18. //2. 准备发送请求的数据: url
    19. var url = $.rootPath + "first/try/string";
    20. var method = "GET";
    21. //3. 调用XMLHttpRequest 的 open 方法 创建一个请求
    22. request.open(method, url);
    23. //4. 调用 XMLHttpRequest 的 send 发放发送一个请求
    24. request.send(null);
    25. //5. 为对象添加onreadystatechang响应函数
    26. request.onreadystatechange = function() {
    27. //6. 判断响应是否完成: XMLHttpRequest 对象的响应值 readyState 为4时,响应完成
    28. if (request.readyState === 4) {
    29. //7. 判断响应是否可用: XMLHttpRequest 对象的status 的值为200
    30. if (request.status == 200) { //请求响应成功
    31. //8. 处理响应结果
    32. alert(request.responseText);
    33. }
    34. }
    35. }
    36. })
    37. </script>
    38. </html>

    2.2 XMLHttpRequest 实例对象的分析

    在浏览器打印XMLHttpRequest 的实例对象,其属性如下图。包含部分可用的属性 和 可自定义的回调函数。且这些属性在原型对象中均存在,所以以下的分析只针对原型对象进行。
    image.png
    其中隐式原型对象 proto的属性如下, 原型对象提供了三种属性可以使用(自己分类定义的,暂且这么叫吧):

  • 实例对象可自定义的回调函数;

  • 实例对象可直接使用的方法;
  • 实例对象的可用属性。

image1.png
后续内容将分开介绍 XMLHttpRequest 的 所有属性,方法和事件。

三、 实例对象可用的属性

从以上打印出的实例对象的属性中可以看出,XMLHttpRequest 实例对象的可用属性有 readyState、response、responseText、responseType、responseURL、responseXML、status、statusText、timeout等。
注:**前面几个DONE、HEADERS_RECEIVED、LOADING、OPENED、UNSET 其实就是 **readyState的各个状态值的常量。

3.1 readyState(只读)

readyState 属性返回一个 XMLHttpRequest 代理当前所处的状态。一个 XHR 代理总是处于下列状态中的一个:
image.png

  • UNSENT:XMLHttpRequest 代理已被创建, 但尚未调用 open() 方法。
  • OPENED:open() 方法已经被触发。在这个状态中,可以通过 setRequestHeader() 方法来设置请求的头部, 可以调用 send() 方法来发起请求。
  • HEADERS_RECEIVED:send() 方法已经被调用,响应头也已经被接收(调用send()方法后,readyState的值并没有变成 2,而是在 onreadystatechange 才能拿到,也就是所获得头部只能在onreadystatechange方法中)。
  • LOADING:响应体部分正在被接收。如果 responseType 属性是“text”或空字符串, responseText 将会在载入的过程中拥有部分响应数据。
  • DONE:请求操作已经完成。这意味着数据传输已经彻底完成或失败。

    <!DOCTYPE html>
    <html>
      <head>
          <meta charset="utf-8">
          <title>查看readyState的5个状态的触发时间</title>
          <script src="../../js/jquery-3.1.0.js"></script>
          <script src="../../js/commonUtil.js"></script>
      </head>
      <body>
          <h1>测试 - 查看各个状态的触发时间</h1>
          <button id="get-data">查看各个状态的触发出发时间</button>
      </body>
      <script>
          //给btn绑定一个点击事件,点击的时候通过ajax从后台获取数据
          $("#get-data").on("click", function() {
    
              var xhr = new XMLHttpRequest();
              console.log("创建了xhr对象:" + xhr.readyState);  //0 : XMLHttpxhr 代理已被创建
    
              var url = $.rootPath + "ready/state/check";
              var method = "GET";
              xhr.open(method, url);
              console.log("调用了open方法:" + xhr.readyState); //1: open() 方法已经被触发 在这个状态中,可以通过  setxhrHeader() 方法来设置请求的头部, 可以调用 send() 方法来发起请求。
    
              xhr.send(null);
              console.log("调用了send方法:" + xhr.readyState); // 1: 此时状态还是1
    
              xhr.onreadystatechange = function() {
                  console.log("onreadystatechange 回调方法:" + xhr.readyState); // 2,3,4: 此处在状态变为2,3,4时都会调用 onreadystatechange 回调
    
                  if (xhr.readyState === 4 && xhr.status == 200) {
                      console.log(xhr.responseText);
                  } 
              }
          })
      </script>
    </html>
    

    3.2 response(只读) 和 responseType

    response 是一个对象,其类型取决于 responseType 的值。可以通过设置 responseType 的值,以便通过特定的类型请求数据。 responseType 要在**调用 open() 初始化请求之后调用,并且要在调用 send() 发送请求到服务器之前调用**。
    responseType 属性是一个枚举类型的属性,返回响应数据的类型。它允许我们手动的设置返回数据的类型。如果我们将它设置为一个空字符串,它将使用默认的”text”类型。
    在工作环境(Work Environment)中将responseType的值设置为”document”通常会被忽略. 当将responseType设置为一个特定的类型时,你需要确保服务器所返回的类型和你所设置的返回值类型是兼容的。那么如果两者类型不兼容呢?恭喜你,你会发现服务器返回的数据变成了null,即使服务器返回了数据。还有一个要注意的是,给一个同步请求设置responseType会抛出一个InvalidAccessError 的异常。
    response 对象与responseType的值得对应关系如下

responseType response
“” (默认值) responseType 为空字符串时,采用默认类型 DOMString,与设置为 text 相同。
arraybuffer response 是一个包含二进制数据的 JavaScript ArrayBuffer
blob response 是一个包含二进制数据的 Blob 对象 。
document response 是一个 HTML DocumentXML XMLDocument,这取决于接收到的数据的 MIME 类型。
json response 是一个 JavaScript 对象。这个对象是通过将接收到的数据类型视为 JSON 解析得到的。
text response 是一个以 DOMString 对象表示的文本。
ms-stream(**未标准化**) response 是下载流的一部分;此响应类型仅允许下载请求,并且仅受 Internet Explorer 支持。

语雀内容

3.3 responseText(只读)、responseXML(只读) 、responseURL(只读)

response是接收接口返回数据的属性,器类型会根据 responseType 设置的不同而不同,3.2节中已有详细的内容,此处指测试 responseText 和 responseXML
responseText 和 responseXML都是只读属性。

3.3.1 responseText 只有在 responseType 的值为 “” 或者 text 时才有值,其他情况下报错。

代码

function bindFun() {
        $("#response-text").on("click", {
            responseType : "text",
            url : "text"
        }, sendRequest);
        $("#response-not-text").on("click", {
            responseType : "blob",
            url : "not/text"
        }, sendRequest);
    }
/**
     requestType: 请求方式
     url:请求地址
     */
    function sendRequest(event) {
        var responseType = event.data.responseType;
        var inurl = event.data.url;
        console.log("responseType -> " , responseType);

        var xhr = new XMLHttpRequest();
        var url = $.rootPath + curModule + inurl;
        var method = "GET";
        xhr.open(method, url);

        xhr.responseType = responseType; //responseType 是可读可写属性, 直接通过赋值的方式设置

        xhr.send(null);
        xhr.onreadystatechange = function() {
            if (xhr.readyState == 4 && xhr.status === 200) {
                console.log("responseText -> ",xhr.responseText)
            }
        }
    }

控制台结果:
image.png
image.png

3.3.2 responseXML 只有在 responseType 的值为 document 时才有值,其他情况下报错。

function bindFun() {
        $("#response-document").on("click", {
            responseType : "document",
            url : "document"
        }, sendRequest);
        $("#response-not-document").on("click", {
            responseType : "blob",
            url : "not/document"
        }, sendRequest);
    }

/**
     requestType: 请求方式
     url:请求地址
     */
    function sendRequest(event) {
        var responseType = event.data.responseType;
        var inurl = event.data.url;
        console.log("responseType -> " , responseType);

        var xhr = new XMLHttpRequest();
        var url = $.rootPath + curModule + inurl;
        var method = "GET";
        xhr.open(method, url);

        xhr.responseType = responseType; //responseType 是可读可写属性, 直接通过赋值的方式设置

        xhr.send(null);
        xhr.onreadystatechange = function() {
            if (xhr.readyState == 4 && xhr.status === 200) {
                console.log("responseXML -> ",xhr.responseXML)
            }
        }

    }

image.png
image.png

3.3.3 responseURL返回经过序列化(serialized)的响应 URL,如果该 URL 为空,则返回空字符串。

function sendRequest(event) {
        var responseType = event.data.responseType;
        var inurl = event.data.url;

        var xhr = new XMLHttpRequest();
        var url = $.rootPath + curModule + inurl;
        var method = "GET";
        xhr.open(method, url);

        xhr.responseType = responseType; //responseType 是可读可写属性, 直接通过赋值的方式设置

        xhr.send(null);
        xhr.onreadystatechange = function() {
            if (xhr.readyState == 4 && xhr.status === 200) {
                console.log("responseURL -> ",xhr.responseURL)
            }
        }
    }

image.png

3.4 status(只读) 和 statusText(只读)

status 返回一个无符号短整型(unsigned short)数字,代表请求的响应状态(也是http请求的响应状态码)。
返回一个 DOMString,其中包含 HTTP 服务器返回的响应状态。与 XMLHTTPRequest.status 不同的是,它包含完整的响应状态文本(例如,”200 OK”)。

测试:如下模拟了几种响应 status

后端代码:

package com.binghe.xmlhttprequestdemo.controller.available.attritube;

/**
 * ResponseController
 * 用于测试 XMLHttpRequest 响应状态和响应状态文字
 * @author bing he
 * @version 2020.10.13
 *
 */
@RestController
@RequestMapping("status")
public class StatusAndStatusTextContrller {

    /**
     * status/200
     */
    @RequestMapping("status/200")
    public String status200(HttpServletRequest request, HttpServletResponse response) {
        return "返回一个字符串";
    }

    /**
     * status/405
     */
    @PostMapping("status/405")
    public void status405(HttpServletRequest request, HttpServletResponse response) {

    }

    /**
     * status/500
     */
    @RequestMapping("status/500")
    public void status500(HttpServletRequest request, HttpServletResponse response) {
        String null500 = null;
        null500.charAt(0);
    }
}

前端代码

var curModule = "status/";

    bindFun();

    function bindFun() {
        $("#status-200").on("click", {
            url : "status/200"
        }, sendRequest);
        $("#status-404").on("click", {
            url : "status/404"
        }, sendRequest);
        $("#status-405").on("click", {
            url : "status/405"
        }, sendRequest);
        $("#status-500").on("click", {
            url : "status/500"
        }, sendRequest);
    }
    /**
     requestType: 请求方式
     url:请求地址
     */
    function sendRequest(event) {
        var inurl = event.data.url;

        var xhr = new XMLHttpRequest();
        //console.log('0 UNSENT', xhr.statusText);
        var url = $.rootPath + curModule + inurl;
        var method = "GET";
        xhr.open(method, url);

        xhr.send(null);

        xhr.onprogress = function() {
            console.log("status为:",xhr.status);
            console.log("statusText为:", xhr.statusText);
        }
        xhr.onload = function() {
            console.log("status为:", xhr.status);
            console.log("statusText为:",xhr.statusText);
        }
    }

输出结果:
image.png
注:输出结果只显示了405状态码的,其他输出类似,但是都没有statusText, 不同的浏览器返回的内容也不同,具体见:https://blog.csdn.net/weixin_30596735/article/details/94855793
据说是http 2.0不在返回 statusText,这个问题待学习http时再弄清楚。

3.5 timeout 和 ontimeout

虽然 ontimeout 属于 xhr 的实例的事件,但是此处放在与 timeout 一起测试,后续的事件的测试中不在测试次事件。
timeout 是一个无符号长整型数,代表着一个请求在被自动终止前所消耗的毫秒数。默认值为 0,意味着没有超时。超时并不应该用在一个 document environment 中的同步 XMLHttpRequests 请求中,否则将会抛出一个 InvalidAccessError 类型的错误。当超时发生, timeout 事件将会被触发。
以下通过设置 timeout 为1ms 来模拟请求超时。请求超时是会触发ontimeout事件。

测试:timeout 和 ontimeout

$("#timeout").on('click', function() {
        var xhr = new XMLHttpRequest();
        var url = $.rootPath + "timeout/timeout";
        xhr.timeout = 1; //设置超时为1ms,模拟超时

        xhr.ontimeout = function() { //监听超时事件
            console.log("请求超时了")
        }

        xhr.open("GET", url)
        xhr.send();
    })

控制台输出
image.png

四、实例对象的可用方法

xhr可用的实例对象主要包括open()、setRequestHeader()、send()、getAllResponseHeaders()、getResponseHeader()、abort()、overrideMimeType()。

4.1 open()、setRequestHeader()、send()

以上三个方法都是在 xhr 对象创建之后和请求发送之前可调用的方法。调用顺序也和上面的顺序一样,但是 setRequestHeader 并不是发送请求必须调用的。下面就分别详情介绍三个方法的用法。

4.1.1 open()

open() 方法初始化一个请求。在创建xhr对象后可调用来初始化请求。
语法

xhr.open(method, url);
xhr.open(method, url, async);
xhr.open(method, url, async, user);
xhr.open(method, url, async, user, password);

参数:

method 要使用的HTTP方法,比如「GET」、「POST」、「PUT」、「DELETE」、等。对于非HTTP(S) URL被忽略。大小写都可以,混着写也没事,例如get, Get都能正常使用,建议都大写
url 一个DOMString表示要向其发送请求的URL。
async(可选) 一个可选的布尔参数,默认为true,表示要不要异步执行操作。如果值为falsesend()方法直到收到答复前不会返回。如果true,已完成事务的通知可供事件监听器使用。如果multipart属性为true则这个必须为true,否则将引发异常。
user(可选) 可选的用户名用于认证用途,如果服务器需要验证;默认为null
password(可选) 可选的密码用于认证用途,如果服务器需要验证; 默认为null

method 和 url 已经使用过,且是必须的参数,此处就测试一下 async 、user、password。(user、**password不知道是怎么使用的,暂时待测试
async 的测试,以下是将async设置为 true 和 false 的测试结果。 async 为true 时,表示请求是异步执行的,即不会等到请求完成才去执行请求之后的代码。所以控制台先打印了 “异步请求发送之后的代码”,而后才打印了请求的结果。async 为true 时,表示请求是同步执行的,此时会阻塞浏览器 js 引擎的主线程,只有在请求结束之后才会执行请求之后的代码(使用同步是浏览器会提示:Synchronous XMLHttpRequest on the main thread is deprecated because of its detrimental effects to the end user’s experience)。
此处可以简单的得出结论,不要轻易使用同步,可能导致页面的卡顿,影响用户体验。**

测试:open同步和异步的区别

代码:

    $("#open-async-yes").on('click', function() {

        var xhr = new XMLHttpRequest();
        var url = $.rootPath + "method/launch";

        xhr.open("GET", url, true); //将async设置成false,表示该请求是同步执行
        xhr.onload = function() {
            console.log("异步请求后台返回的结果:",xhr.responseText)
        }
        xhr.send();

        console.log("异步请求发送之后的代码")
    })
    $("#open-async-false").on('click', function() {

        var xhr = new XMLHttpRequest();
        var url = $.rootPath + "method/launch";

        xhr.open("GET", url, false); //将async设置成false,表示该请求是同步执行
        xhr.onload = function() {
            console.log("同步请求后台返回的结果:", xhr.responseText)
        }

        xhr.send();
        console.log("同步请求发送之后的代码")
    })

控制台输出结果为:
image.png
image.png

4.1.2 setRequestHeader()

setRequestHeader() 是设置HTTP请求头部的方法。此方法必须在 open() 方法和 send() 之间调用。可以用于设置http协议的头部信息,也可以设置自定义的头部信息。
语法:

xhr.setRequestHeader(header, value);

参数:

header 请求头的名字
value 请求头的值

测试1:设置http协议提供请求头

如下测试设置http的请求头,未设置 Accept-Language 时,默认值是zh-CN,将Accept-Language 设置为 en-US,en;q=0.5。查看浏览器开发工具可以看到设置已经生效。
默认:
image.png
设置Accept-Language 设置为 en-US,en;q=0.5

$("#set-request-header").on("click", function() {
        var xhr = new XMLHttpRequest();
        var url = $.rootPath + "method/launch";

        xhr.open("GET", url);
        xhr.setRequestHeader("Accept-Language", "en-US,en;q=0.5"); //设置语言
        xhr.onload = function() {
            console.log("同步请求后台返回的结果:", xhr.responseText)
        }
        xhr.send();
    })

image.png

测试2:自定义请求头

可以自定义请求后,在 java 后台使用 getHeader(headerName) 可以获取自定义请求头的值。

$("#set-custom-request-header").on("click", function() {
        var xhr = new XMLHttpRequest();
        var url = $.rootPath + "method/launch";

        xhr.open("GET", url);
        xhr.setRequestHeader("Custom-Header", "test"); //自定义请求头
        xhr.onload = function() {
            console.log("同步请求后台返回的结果:", xhr.responseText)
        }
        xhr.send();
    })

后台:

@RequestMapping("launch")
    public String launch(HttpServletRequest request, HttpServletResponse XMLHttpRequest) {
        String customHeader = request.getHeader("Custom-Header");
        System.err.println(customHeader);
        return "这是MethodController的launch方法";
    }

image.png

4.1.3 send()

send() 方法用于发送 HTTP 请求。如果是异步请求(默认为异步请求),则此方法会在请求发送后立即返回;如果是同步请求,则此方法直到响应到达后才会返回。XMLHttpRequest.send() 方法接受一个可选的参数,其作为请求主体;如果请求方法是 GET 或者 HEAD,则应将请求主体设置为 null
Note: 请注意不要使用一个简单的AarryBuffer对象作为参数,ArrayBuffer已经不再是ajax规范的一部分,请改用ArrayBufferView
语法:

xhr.send(body)

参数:

body(可选)
- null
- FormData对象FormData对象
- Document对象 在这种情况下,它在发送之前被序列化
- Blob对象
- ArrayBufferView对象
- DOMString

异常:
image.png

测试1:DOMString

DOMString的方式传数据,需要设置正确的请求头 application/x-www-form-urlencoded
前端代码:

$("#send-dom-string").on("click", function() {
        var xhr = new XMLHttpRequest();
        var url = $.rootPath + "method/send/dom/string";

        xhr.open("POST", url);
        xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");//需要设置请求同
        xhr.onload = function() {
            console.log("send/dom/string的结果:", xhr.responseText)
        }

        xhr.send("name=domstring&age=xxx"); 
    })

后端代码:

@RequestMapping("send/dom/string")
    public String sendDOMString(HttpServletRequest request, HttpServletResponse XMLHttpRequest) {
        String name = request.getParameter("name");
        String age = request.getParameter("age");
        return "后台收到的数据为:" + name + "," + age;
    }

控制台打印:
image.png

测试2:FormData对象上传参数

FormData 接口提供了一种表示表单数据的键值对 key/value 的构造方式,并且可以轻松的将数据通过XMLHttpRequest.send() 方法发送出去,本接口和此方法都相当简单直接。如果送出时的编码类型被设为 “multipart/form-data”,它会使用和表单一样的格式。
前端代码:

$("#send-form-data").on("click", function() {
        var xhr = new XMLHttpRequest();
        var url = $.rootPath + "method/send/form/data";

        xhr.open("POST", url);  //注意:不能使用GET请求,GET请求是body应该设置为null
        xhr.onload = function() {
            console.log("send/form/data的结果:", xhr.responseText)
        }

        var formData = new FormData();  //创建一个FormData对象
        formData.append("name", "张三"); //在对象中添加 键值对k/v name/张三
        formData.append("age", "18");

        xhr.send(formData); //发出请求
    })

后端代码:后端直接使用request.getParameter(String name)方法获取 send 提交的数据

/**
     * 测试 XMLHttpRequest send方法,body的内容为FormData
     * @param request
     * @param response
     */
    @RequestMapping("send/form/data")
    public String sendFormData(HttpServletRequest request, HttpServletResponse XMLHttpRequest) {
        String name = request.getParameter("name");
        String age = request.getParameter("age");
        return "后台收到的数据为:" + name + "," + age;
    }

控制台打印结果:
image.png

测试3:formData上传文件

ajax可以利用 type 为 file 的 input 来实现文件的上传。前端使用FormData,后端使用 MultipartFile 来接收文件。
前端代码:

$("#send-form-data-upload").on("click", function() {
        var xhr = new XMLHttpRequest();
        var url = $.rootPath + "method/send/form/data/upload";

        var formData = new FormData();
        var file = $("#upload-file")[0].files[0];

        xhr.open("POST", url);
        xhr.onload = function() {
            console.log("send/form/data/upload的结果:", xhr.responseText)
        }
        xhr.upload.onprogress = function(event) { //监听上传的事件,才控制带打印出上传的进度
            var loaded = event.loaded;
            var total = event.total;
            var percent = (loaded/total)*100
            console.log(percent.toFixed(2), "%")
        }

        console.log(file)
        formData.append("file", file); //第一个参数是后台读取的请求key值
        formData.append("fileName", file.name);

        xhr.send(formData);
    })

后端代码:

private String tempPath = "E:\\project\\BackEnd\\xmlhttprequest-demo\\src\\main\\resources\\static\\upload";
    /**
     * 测试 XMLHttpRequest send方法,body的内容为FormData 上传文件
     * @param request
     * @param response
     * @throws FileUploadException 
     * @throws IOException 
     */
    @RequestMapping("send/form/data/upload")
    public String sendFormDataUpload(HttpServletRequest request,@RequestParam("file") MultipartFile file, HttpServletResponse response) {
        OutputStream os = null;
        InputStream inputStream = null;
        String fileName = request.getParameter("fileName");
        System.err.println(fileName);
        try {
            inputStream = file.getInputStream();
        } catch (IOException e) {
            e.printStackTrace();
        }
        try {
            byte[] bs = new byte[1024];
            // 读取到的数据长度
            int len;
            // 输出的文件流保存到本地文件
            File tempFile = new File(tempPath);
            if (!tempFile.exists()) {
                tempFile.mkdirs();
            }
            os = new FileOutputStream(tempFile.getPath()+ "/" + File.separator + fileName);
            // 开始读取
            while ((len = inputStream.read(bs)) != -1) {
                os.write(bs, 0, len);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 完毕,关闭所有链接
            try {
                os.close();
                inputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return "文件上传成功";
    }

控制台打印结果:
image.png
注:spring默认的上传文件限制是1M,需要配置最大请求内容和最大的文件大小。在yml文件中的配置如下:

server:
  port: 8080
  tomcat:
    max-swallow-size: 104857600

spring:
  servlet:
    multipart:
      max-file-size: 104857600
      max-request-size: 104857600
      file-size-threshold: 10485760

测试4:Document

Document 的方式在body中传数据。后台需要使用流来接收文件。
前端代码:

$("#send-document").on("click", function() {
        var xhr = new XMLHttpRequest();
        var url = $.rootPath + "method/send/document";

        xhr.open("POST", url);
        xhr.setRequestHeader("",)

        xhr.onload = function() {
            console.log("send/document的结果:", xhr.responseText)
        }
        //新建一个文档
        var doc = new Document();
        var personNode = doc.createElement("person");
        var name = doc.createElement("name");
        var nameValue = doc.createTextNode("张三");
        name.appendChild(nameValue);

        var age = doc.createElement("age");
        var ageValue = doc.createTextNode("18");
        age.appendChild(ageValue);

        personNode.appendChild(name);
        personNode.appendChild(age);
        doc.append(personNode);

        xhr.send(doc);
    })

后端代码:

/**
     * 测试 XMLHttpRequest send方法,body的内容为document
     * @param request
     * @param response
     */
    @RequestMapping("send/document")
    public String sendDocument(HttpServletRequest request, HttpServletResponse XMLHttpRequest) {
        ServletInputStream in = null;
        try {
            byte[] doc = new byte[1024];
            in = request.getInputStream();
            in.read(doc);
            String aa = new String(doc);
            System.err.println("啥也没有:" + aa);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                in.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return "后台收到数据";
    }

后台输出数据为:
image.png

测试5:Blob

Blob对象表示一个不可变、原始数据的类文件对象。它的数据可以按文本或二进制的格式进行读取,也可以转换成 ReadableStream 来用于数据操作。其具体的介绍见:TODO
前端可以用 js 对象来生成 Blob 对象,并直接将 Blob对象发送到后端。后端通过 request.getInputStream() 获取输入流读取请求体重的文件内容。
前端代码:

//使用blob的方式上传文件
    $("#send-blob").on("click", function() {
        var json = { hello: "world" };
        var blob = new Blob([JSON.stringify(json, null, 2)], { type: 'application/json' });
        var url = $.rootPath + "method/send/blob";

        var xhr = new XMLHttpRequest();
        xhr.open("POST", url); 
        xhr.onload = function() {
            console.log("同步请求后台返回的结果:", xhr.responseText)
        }
        xhr.send(blob);
    })

后端代码

@RequestMapping("send/blob")
    public String sendBlob(HttpServletRequest request, HttpServletResponse response) {
        String fileName = "blobCreate.json";
        OutputStream os = null;
        InputStream inputStream = null;
        try {
            inputStream = request.getInputStream();  //从请求中获取输入流

            // 输出的文件流保存到本地文件
            File tempFile = new File(tempPath);
            if (!tempFile.exists()) {
                tempFile.mkdirs();
            }
            os = new FileOutputStream(tempFile.getPath()+ "/" + File.separator + fileName);//创建文件输出流

            byte[] bs = new byte[1024];
            // 读取到的数据长度
            int len;
            // 开始读取
            while ((len = inputStream.read(bs)) != -1) {
                os.write(bs, 0, len);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 完毕,关闭所有链接
            try {
                os.close();
                inputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        return "文件上传成功";
    }

测试6:arrayBufferView

此测试未成功。@TODO

4.2 getAllResponseHeaders()、getResponseHeader()

获取请求头相关的方法

4.2.1 getResponseHeader()

返回包含指定响应头的字符串,如果响应尚未收到或响应中不存在该报头,则返回 null。
语法:

var header = xhr.getResponseHeader(name);

参数:

name 一个字符串,表示要返回的报文项名称。

返回值:
报文项值,如果连接未完成,响应中不存在报文项,或者被W3C限制,则返回null。

测试1:获取请求头

前端代码:

$("#get-response-header").on("click", function() {
        var xhr = new XMLHttpRequest();
        var url = $.rootPath + "method/get/response/header";

        xhr.open("POST", url);
        xhr.onload = function() {
            var contentType = xhr.getResponseHeader("Content-Type");
            var customHeader = xhr.getResponseHeader("Custom-Header");
            console.log("Content-Type:" + contentType)
            console.log("Custom-Header:" + customHeader)
        }
        xhr.send();
    })

后端代码

@RequestMapping("get/response/header")
    public String getResponseHeader(HttpServletRequest request, HttpServletResponse response) {
        response.setHeader("Custom-Header", "test-header");;
        return "getResponseHeader方法";
    }

控制台打印:
image.png

4.2.2 getAllResponseHeaders()

getAllResponseHeaders() 方法返回所有的响应头,以 CRLF 分割的字符串,或者 null 如果没有收到任何响应。 注意: 对于复合请求 ( multipart requests ),这个方法返回当前请求的头部,而不是最初的请求的头部。
语法:

var headers = xhr.getAllResponseHeaders();

返回值:
返回当前请求所有的请求头。以 CRLF 分割的字符串,即后面都有回车换行符 ‘\r\n’

测试:获取所有请求头

前端代码:

$("#get-all-response-headers").on("click", function() {
        var xhr = new XMLHttpRequest();
        var url = $.rootPath + "method/get/response/header";

        xhr.open("POST", url);
        xhr.onload = function() {
            var allHeaders = xhr.getAllResponseHeaders();
            console.log("allHeaders:\n" + allHeaders)
        }
        xhr.send();
    })

控制台输出结果:
image.png

4.3 abort方法 和 onabort事件

如果该请求已被发出,xhr.abort() 方法将终止该请求。当一个请求被终止,它的 readyState 将被置为 XMLHttpRequest.UNSENT (0),并且请求的 status 置为 0。
当调用abort终止请求后,会触发 onabort 事件。

测试:abort 方法和 onabort 事件

前端代码:

$("#abort").on("click", function() {
        var xhr = new XMLHttpRequest();
        var url = $.rootPath + "method/abort";

        xhr.open("POST", url);
        xhr.onabort = function() {
            console.log("请求被终止了");
            console.log("readyState:" + xhr.readyState)
        }
        xhr.send();
        xhr.abort();
        console.log("abort之后的readyState:" + xhr.readyState)

    })

控制台输出的结果:
image.png

4.4 overrideMimeType() - 实在没搞懂

overrideMimeType 方法是指定一个MIME类型用于替代服务器指定的类型,使服务端响应信息中传输的数据按照该指定MIME类型处理。例如强制使流方式处理为”text/xml”类型处理时会被使用到,即使服务器在响应头中并没有这样指定。此方法必须在send方法之前调用方为有效
语法:

xhr.overrideMimeType(mimeType)

参数:

mimeType 一个 DOMString 指定具体的MIME类型去代替有服务器指定的MIME类型. 如果服务器没有指定类型,那么 XMLHttpRequest 将会默认为 "text/xml".

五、实例对象的事件

5.1 onabort、onerror

onabort 事件会在XMLHttpRequest 请求操作被诸如 XMLHttpRequest.abort() 函数中止时调用。测试建 abort方法的测试
onerror 是XMLHttpRequest 事务由于错误而失败时调用的函数。例如连不上服务器

测试:onerror 关闭服务器

跳转到当前测试页面后,关闭服务器。点击onerror的按钮,此时由于服务器关闭,请求会出错,然后出发 onerror 事件。
前端代码:

$("#onerror").on('click', function() {
        var xhr = new XMLHttpRequest();
        var url = $.rootPath + "event/onerror";

        xhr.open("POST", url); 
        xhr.onerror = function() {
            console.log("出现错误了")
        }
        xhr.send();
    })

控制台返回结果:链接拒绝
image.png

5.2 onloadstart、onprogress、onload、onloadend

onloadstart 在XMLHttpRequest 开始传送数据时被调用
onprogress 是在 XMLHttpRequest 完成之前周期性调用的函数。
onload 当一个XMLHttpRequest请求完成的时候会触发load 事件,abort 或 error 是不会触发onload 的
onloadend 当请求结束时触发, 无论请求成功 ( load) 还是失败 (abort 或 error)。

测试:正常情况下,onloadstart、onprogress、onload、onloadend是按顺序触发的。

前端代码:

$("#process").on('click', function() {
        var xhr = new XMLHttpRequest();
        var url = $.rootPath + "event/process";

        xhr.open("POST", url); 
        xhr.onloadstart = function() {
            console.log("onloadstart事件被触发")
        }
        xhr.onprogress = function() {
            console.log("onprogress事件被触发")
        }
        xhr.onload = function() {
            console.log("onload事件被触发")
        }
        xhr.onloadend = function() {
            console.log("onloadend事件被触发")
        }

        xhr.send();
    })

控制台输出结果:
image.png

5.3 ontimeout

当进度由于预定时间到期而终止时,会触发timeout 事件。其测试见 3.5节 属性 timeout部分。