XMLHttpRequest简单介绍

1. 概述

XMLHttpRequest(XHR)对象用于与服务器交互,我们通过 XMLHttpRequest 可以在不刷新页面的情况下请求特定 URL获取数据,并且虽然名字叫XMLHttpRequest,但实际上可以用于获取任何类型的数据。

2. 使用方式

XMLHttpRequest的使用主要可以分为如下几步:

  • 创建XMLHttpRequest对象
  • 建立http连接
  • 发送请求
  • 获取返回数据

2.1创建XMLHttpRequest对象

不同版本的浏览器,以不同形式支持XMLHttpRequest的创建。

var xhr = new XMLHttpRequest();

IE 5.0 以上版本以生成 ActiveX 组件形式支持 XMLHttpRequest,IE 7.0以上 版本开始标准化 XMLHttpRequest。不过好在所有浏览器实现的 XMLHttpRequest 对象都提供相同的接口和用法,所以创建之后不影响使用。

当我们以浏览器不支持的方式创建XHR对象的时候,会引发异常;根据这种机制,我们可以巧妙的通过异常处理,来使用浏览器所能支持的最新方式来创建

//创建XMLHttpRequest 对象
//参数:无
//返回值:XMLHttpRequest 对象
function createXHR () {
    var XHR = [  //兼容不同浏览器和版本得创建函数数组
        function () { return new XMLHttpRequest () },
        function () { return new ActiveXObject ("Msxml2.XMLHTTP") },
        function () { return new ActiveXObject ("Msxml3.XMLHTTP") },
        function () { return new ActiveXObject ("Microsoft.XMLHTTP") }
    ];
    var xhr = null;
    //尝试调用函数,如果成功则返回XMLHttpRequest对象,否则继续尝试
    for (var i = 0; i < XHR.length; i ++) {
        try {
            xhr = XHR[i]();
        } catch(e) {
            continue  //如果发生异常,则继续下一个函数调用
        }
        break;  //如果成功,则中止循环
    }
    return xhr;  //返回对象实例
}

2.2 创建连接

使用open方法建立连接,接口如下

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

其中 xhr 表示 XMLHttpRequest 对象,open() 方法包含 5 个参数,说明如下:

  • method:HTTP 请求方法,必须参数,值包括 POST、GET 和 HEAD,大小写不敏感。
  • url:请求的 URL 字符串,必须参数,大部分浏览器仅支持同源请求。
  • async:指定请求是否为异步方式,默认为 true。如果为 false,当状态改变时会立即调用 onreadystatechange 属性指定的回调函数。
  • username:可选参数,如果服务器需要验证,该参数指定用户名,如果未指定,当服务器需要验证时,会弹出验证窗口。
  • password:可选参数,验证信息中的密码部分,如果用户名为空,则该值将被忽略。

2.3发送请求

建立连接后,发送请求通过send方法来实现,send方法参数可选,通常get方式不带参数,post带参数;post带的参数就是发送的数据

xhr.send(body);

参数 body 表示将通过该请求发送的数据,如果不传递信息,可以设置为 null 或者省略。
发送请求后,可以使用 XMLHttpRequest 对象的 responseBody、responseStream、responseText 或 responseXML 属等待接收响应数据

2.4示例

下面示例简单演示了如何实现异步通信的方法。

        var xhr = creatXHR();  //实例化XMLHttpRequest 对象
        xhr.open ("GET", url, false);  //建立连接
        xhr.send(null);  //发送请求
        request.onreadystatechange=function(){//判断和服务器端的交互是否完成,和服务器端是否返回了正确的数据
             if(request.readyState===4){
                   if(request.status===200){
                        //获得字符串形式的响应数据
                        console.log(xhr.responseText) //接收数据
                   }else{
                        alert("发生错误:"+ request.status);
                   }
             }
       };

 2.5获取和设置头部消息

HTTP 请求和响应都包含一组头部消息,获取和设置头部消息可以使用下面两个方法。

  • getAllResponseHeaders():获取响应的 HTTP头部消息。
  • getResponseHeader("Header-name"):获取指定的 HTTP 头部消息。
  • setRequestHeader():设置HTTP请求头部的方法。此方法必须在 open()方法和 send() 之间调用

为了方便服务器接收数据,当提交请求体时,需要指定一个叫做 Content-Type 的请求头

示例:

 let btnadd = document.querySelector('.btnadd')
 let bookname = document.querySelector('[name="bookname"]')
 let author = document.querySelector('[name="author"]')
 let publisher = document.querySelector('[name="publisher"]')
 ​
 btnadd.addEventListener('click', function() {
 // 收集数据
 let booknameV = bookname.value
 let authorV = author.value
 let publisherV = publisher.value
 ​
 // 创建异步对象
  let xhr = new XMLHttpRequest();
 ​
 // 发起请求
 // 请求行:post不能在请求行中拼接参数,否则相当于没有传递
  xhr.open('post', 'http://www.itcbc.com:3006/api/addbook',true)
     
 // 请求头:post方式传递普通键值对,需要设置Content-type编码格式,否则后台无法正确的获取到参数
 // xhr.setRequestHeader('Content-Type','application/x-www-form-urlencoded')
 // json格式,上面的是字符串格式
  xhr.setRequestHeader('Content-Type', 'application/json')
     
 // 请求体,参数格式与get一样,除非你进行其它的处理
 // xhr.send(`bookname=${booknameV}&author=${authorV}&publisher=${publisherV}`)
 // 需要对象方式提交,则需要转换JSON,否则会报错,请求头也需要更换json
    xhr.send(
        JSON.stringify({
          bookname: booknameV,
          author: authorV,
          publisher: publisherV
        })
    )
 ​
 // 接收响应
 xhr.addEventListener('load', function() {
      console.log(JSON.parse(xhr.response))
    })
 })

2.6中止请求

使用 abort() 方法可以中止正在进行的请求。用法如下:

xhr.onreadystatechange = function () {};  //清理事件响应函数
xhr.abort();  //中止请求

2.7属性

  • readyState

[只读属性]用于追踪 xhr 当前的状态,共有 5 种可能的值,分别对应 xhr不同的阶段。
每次 readyState 值变化时,都会触发 xhr.onreadystatechange 事件。

如果 readyState 属性值为 4,则说明响应完毕,那么就可以安全的读取响应的数据。

示例代码如下:

    var url = "server.php";  //设置请求的地址
    var xhr =new XMLHttpRequest();//实例化XMLHttpRequest对象
    xhr.open("POST", url, true); //建立间接,要求异步响应
    xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); //设置为表单方式提交 
    xhr.onreadystatechange = function () { //绑定响应状态事件监听函数 
        if (xhr.readyState == 4) { //监听readyState状态 
            if (xhr.status == 200 || xhr.status == 0) { //监听HTTP状态码 
                console.log(xhr.responseText); //接收数据  
                } 
        } 
    }
    xhr.send("callback=functionName"); //发送请求 
  • status 和 statusText

status 属性表示 HTTP 响应状态码,如 200302400等。
statusText 属性表示 HTTP响应状态的描述文本,如 OKNot Found等。
注意,在 xhr.onload 事件中,不能简单的判断 xhr.status === 200,因为 20x304等 HTTP 状态码也被认为是请求成功。
参考以下代码:

var url = "server.php";  //设置请求的地址
var xhr = new XMLHttpRequest();  //实例化XMLHttpRequest对象
xhr.open("post", url, true);  //建立间接,要求异步响应
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');  //设置为表单方式提交
xhr.onload = function () {
   //如果请求成功
   if((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){
       //do successCallback
      }
}
xhr.send();
//发送请求
  • responseType 和 response

可在 xhr.send() 前设置 responseType ,用于指定返回的响应数据类型。和 xhr.overrideMimeType() 方法效果相同,推荐使用 responseType

IE10/IE11 不支持 xhr.responseType 为 json。部分浏览器不支持 xhr.responseType 为 blob

XMLHttpRequest 对象响应信息属性

在实际应用中,一般将格式设置为 XML、HTML、JSON 或其他纯文本格式。具体使用哪种响应格式,可以参考下面几条原则。

  • 如果向页面中添加大块数据,选择 HTML 格式会比较方便。
  • 如果需要协作开发,且项目庞杂,选择 XML 格式会更通用。
  • 如果要检索复杂的数据,且结构复杂,那么选择 JSON 格式更加轻便。
  • 获取 XML 字符串

    • 默认值为 null
    • 只有当 responseType 为 text""document时,xhr 对象上才有此属性,此时才能调用 xhr.responseXML,否则抛错。
    • 只有当请求成功且返回数据被正确解析时,才能拿到正确值。以下 3 种情况下值都为 null :请求未完成、请求失败、请求成功但返回数据无法被正确解析时。
<input name="submit" type="button" id="submit" value="向服务器发出请求" />
<script>
    window.onload = function () {  //页面初始化
        var b = document.getElementsByTagName("input")[0];
        b.onclick = function () {
            var xhr = new XMLHttpRequest();  //实例XMLHttpRequest对象
            xhr.open("GET", "server.xml", true);  //建立连接,要求异步响应
            xhr.onreadystatechange = function () {  //绑定响应状态事件监听函数
                if (xhr.readyState == 4) {  //监听readyState状态
                    if (xhr.state == 200 || xhr.status == 0) {  //监听HTTP状态码
                        var info = xhr.responseXML;
                        console.log(info.getElementsByTagName("the")[0].firstChild.data);  //返回元信息字符串“XML 数据”
                    }
                }
            }
            xhr.send();  //发送请求
        }
    }
</script>

在上面代码中,使用 XML DOM 的 getElementsByTagName() 方法获取 the 节点,然后再定位第一个 the 节点的子节点内容。此时如果继续使用 responseText 属性来读取数据,则会返回 XML 源代码字符串。

  • 获取 HTML 字符串

设计响应信息为 HTML 字符串,然后使用 innerHTML 把获取的字符串插入到网页中。

<input name="submit" type="button" id="submit" value="向服务器发出请求" />
<script>
    window.onload = function () {  //页面初始化
        var b = document.getElementsByTagName("input")[0];
        b.onclick = function () {
            var xhr = new XMLHttpRequest();  //实例化XMLHttpRequest对象
            xhr.open("GET", "server.xml", true);  //建立连接,要求异步响应
            xhr.onreadystatechange = function () {  //绑定响应状态事件监听函数
                if (xhr.readyState == 4) {  //监听readyState状态
                    if (xhr.state == 200 || xhr.status == 0) {  //监听HTTP状态码
                        var o = document.getElementById("grid");
                        o.innerHTML = xhr.responseText;  //直接插入到页面中
                    }
                }
            }
            xhr.send();  //发送请求
        }
    }
</script>

在某些情况下,HTML 字符串可能为客户端解析响应信息节省了一些 JavaScript 脚本,但是也带来了一些问题。

  • 响应信息中包含大量无用的字符,响应数据会变得很臃肿。因为 HTML 标记不含有信息,完全可以把它们放置在客户端,由 JavaScript 脚本负责生成。
  • 响应信息中包含的 HTML 结构无法有效利用,对于 JavaScript 脚本来说,它们仅仅是一堆字符串。同时结构和信息混合在一起,也不符合标准化设计原则。
  • 获取 JavaScript 脚本

  • 默认值为空字符串 ""
  • 只有当 responseType 为 text""时,xhr 对象上才有此属性,此时才能调用 xhr.responseText ,否则抛错。
  • 只有当请求成功时,才能拿到正确值。以下 2 种情况下值都为空字符串 "":请求未完成、请求失败。

设计相应为 JavaScript 代码,与 JSON 数据不同,它是可执行的命令或脚本。

<input name="submit" type="button" id="submit" value="向服务器发出请求" />
<script>
    window.onload = function () {  //页面初始化
        var b = document.getElementsByTagName("input")[0];
        b.onclick = function () {
            var xhr = new XMLHttpRequest(); //实例化XMLHttpRequest对象
            xhr.open("GET", "server.xml", true);  //建立连接,要求异步响应
            xhr.onreadystatechange = function () {  //绑定响应状态事件监听函数
                if (xhr.readyState == 4) {  //监听readyState状态
                    if (xhr.state == 200 || xhr.status == 0) {  //监听HTTP状态码
                        var info = xhr.responseText;
                        var o = eval("(" + info + ")" + "()");  //用eval()把字符串转换为脚本
                        console.log(o);  //返回客户端当前信息
                    }
                }
            }
            xhr.send();  //发送请求
        }
    }
</script>

使用 eval() 方法时,在字符串前后附加两个小括号:一个是包含函数结构体的,一个是表示调用函数的。不建议直接使用 JavaScript 代码作为响应格式,因为它不能传递更丰富的信息,同时 JavaScript 脚本极易引发安全隐患。

  • 获取 JSON 数据

使用 responseText 可以获取 JSON 格式的字符串,然后使用 eval() 方法将其解析为本地 JavaScript 脚本,再从该数据对象中读取信息。

<input name="submit" type="button" id="submit" value="向服务器发出请求" />
<script>
    window.onload = function () {  //页面初始化
        var b = document.getElementsByTagName("input")[0];
        b.onclick = function () {
            var xhr = new XMLHttpRequest();  //实例化XMLHttpRequest对象
            xhr.open("GET", "server.xml", true);  //建立连接,要求异步响应
            xhr.onreadystatechange = function () {  //绑定响应状态事件监听函数
                if (xhr.readyState == 4) {  //监听readyState状态
                    if (xhr.state == 200 || xhr.status == 0) {  //监听HTTP状态码
                        var info = xhr.responseText;
                        var o = eval("(" + info + ")");  //调用eval()把字符串转换为本地脚本
                        console.log(info);  //显示JSON对象字符串
                        console.log(o.user);  //读取对象属性值,返回字符串“css8”
                    }
                }
            }
            xhr.send();  //发送请求
        }
    }
</script>

eval() 方法在解析 JSON 字符串时存在安全隐患。如果 JSON 字符串中包含恶意代码,在调用回调函数时可能会被执行。解决方法:先对 JSON 字符串进行过滤,屏蔽掉敏感或恶意代码。不过,确信所响应的 JSON 字符串是安全的,没有被人恶意攻击,那么可以使用 eval() 方法解析 JSON 字符串。

  • 获取纯文本

对于简短的信息,可以使用纯文本格式进行响应。但是纯文本信息在传递过程中容易丢失,且没有办法检测信息的完整性。

服务器端响应信息为字符串“true”,则可以在客户端这样设计。

var xhr = new XMLHttpRequest();  //实例化XMLHttpRequest对象
xhr.open("GET", "server.txt", true);  //建立连接,要求异步响应
xhr.nreadystatechange = function () {  //绑定响应状态事件监听函数
    if (xhr.readyState == 4) {  //监听readyState函数
        if (xhr.status == 200 || xhr.status == 0) {  //监听HTTP状态码
            var info = xhr.responseText;
            if (info == "true") console.log("文本信息传输完整");  //检测信息是否完整
            else console.log("文本信息可能存在丢失");
        }
    }
}
xhr.send();  //发送请求

 

  • upload

  • 是一个XMLHttpRequestUpload对象,用于收集传输信息。支持事件

    • onloadstart
    • onprogress
    • onabort
    • ontimeout
    • onerror
    • onload
    • onloadend
      具体触发顺序及条件,参考事件章节。
      其中,xhr.upload.onprogress在上传阶段(即xhr.send()之后,xhr.readystate=2之前)触发,每 50ms 触发一次。可获得上传信息、进度等。
      上述事件回调的参数为 XMLHttpRequestEventTarget 对象,详见 事件补充。
    • 如何获取上传、下载的进度

      在上传或者下载比较大的文件时,实时显示当前的上传、下载进度是很普遍的产品需求。
      我们可以通过onprogress事件来实时显示进度,默认情况下这个事件每50ms触发一次。需要注意的是,上传过程和下载过程触发的是不同对象的onprogress事件:

      • 上传触发的是xhr.upload对象的 onprogress事件
      • 下载触发的是xhr对象的onprogress事件

    • xhr.onprogress = updateProgress;
      xhr.upload.onprogress = updateProgress;
      function updateProgress(event) {
        if (event.lengthComputable) {
          var completedPercent = event.loaded / event.total;
        }
       }

 

  • timeout

 

  • 单位毫秒,默认值 0 ,即不设置超时。
  • 计时从onloadstart 事件触发开始(即xhr.send()开始),以onloadend 事件触发为结束。
  • 在 IE 中,只能在调用open()方法后send()方法前设置。其他浏览器无此限制,但仍然从xhr.send()方法调用计时。
  • 不能为同步请求设置 timeout ,否则会报错。
  • 早期较多浏览器不支持,可通过 setTImeOut 实现。

withCredentials

boolean 类型,默认值 false, 用于跨域请求时将 cookie 加入到 request header

xhr.withCredentials 与  CORS 什么关系
我们都知道,在发同域请求时,浏览器会将  cookie 自动加在  request header 中。但在发送跨域请求时,  cookie 并不会自动加在  request header 中。
造成这个问题的原因:在 CORS 标准有如下规定,默认情况下,浏览器在发送跨域请求时,不能发送任何认证信息( credentials)如  cookies 和  HTTP authentication schemes 。除非  xhr.withCredentials 为  true。
cookies 也是一种认证信息,在跨域请求中,  client 端必须手动设置  xhr.withCredentials=true,且  server 端也必须允许  request 能携带认证信息(即  response header 中包含  Access-Control-Allow-Credentials:true),这样浏览器才会自动将  cookie 加在  request header 中。
注意,一旦跨域  request 能够携带认证信息,  server 端一定不能将  Access-Control-Allow-Origin 设置为 *,而必须设置为请求页面的域名。

2.8xhr 的事件回调

xhr 共有 8 个事件,分别如下:

  • onloadstart
  • onprogress
  • onabort
  • ontimeout
  • onerror
  • onload
  • onloadend
  • onreadystatechange

请求正常时,事件触发顺序

  1. 触发 xhr.onreadystatechange (之后每次 readyState 变化时,都会触发一次)
  2. 触发 xhr.onloadstart //上传阶段开始:
  3. 触发 xhr.upload.onloadstart
  4. 触发 xhr.upload.onprogress
  5. 触发 xhr.upload.onload
  6. 触发 xhr.upload.onloadend //上传结束,下载阶段开始:
  7. 触发 xhr.onprogress
  8. 触发 xhr.onload
  9. 触发 xhr.onloadend

发生 abort / timeout / error 时事件触发顺序

  1. 触发 xhr.onreadystatechange 事件,此时 readystate 为 4
  2. 如果上传阶段还没有结束,则依次触发以下事件:

    • xhr.upload.onprogress
    • xhr.upload.[onabort或ontimeout或onerror]
    • xhr.upload.onloadend
  3. 触发 xhr.onprogress 事件
  4. 触发 xhr.[onabort或ontimeout或onerror] 事件
  5. 触发 xhr.onloadend 事件

事件补充

xhr.upload.onprogress 和 xhr.onprogress 的回调参数为 XMLHttpRequestEventTarget 对象。属性如下:

    • lengthComputable
      【只读】,为 boolean值,表示资源是否有可计算的长度。
    • loaded
      已接收或已上传的字节数。
    • total
      文件总字节数。
    • xhr.upload.onprogress 事件触发于上传阶段,可用于获取上传进度。
    • xhr.onprogress 事件触发于下载阶段,可用于获取下载进度。

2.9同源策略 & 跨域

同源指的是两个 URL 地址具有相同的协议、主机名、端口号。

例如,下表给出了相对于 www.test.com/index.html 页面的 5 个同源检测结果

同源策略(英文全称 Same origin policy)是浏览器提供的一个安全功能。

浏览器的同源策略规定:不允许非同源的 URL 之间进行资源的交互。

同源指的是两个 URL 的协议、主机名、端口号完全一致,反之,则是跨域。

出现跨域的根本原因:浏览器的同源策略不允许非同源的 URL 之间进行资源的交互。例如:

网页:http:// www.test.com / index.html

接口:http:// www.api.com /userlist

受到同源策略的限制,上面的网页请求下面的接口会失败!

浏览器对跨域请求的拦截过程
浏览器允许发起跨域请求。但跨域请求回来的数据,会被浏览器拦截,无法被页面获取到!示意图如下:

突破浏览器跨域限制的两种方案

JSONP 和 CORS 是实现跨域数据请求的两种技术方案。

注意:目前 JSONP 在实际开发中很少会用到,CORS 是跨域的主流技术解决方案

CORS 的概念
CORS 是解决跨域数据请求的终极解决方案,全称是 Cross-origin resource sharing。

CORS 技术需要浏览器和服务器同时支持,二者缺一不可:

浏览器要支持 CORS 功能(主流的浏览器全部支持,IE 不能低于 IE10)

服务器要开启 CORS 功能(需要后端开发者为接口开启 CORS 功能)

CORS 的原理
服务器端通过 Access-Control-Allow-Origin 响应头,来告诉浏览器当前的 API 接口是否允许跨域请求。

CORS 的两个主要优势

CORS 是真正的 Ajax 请求,支持 GET、POST、DELETE、PUT、PATCH 等这些常见的 Ajax 请求方式

只需要后端开启 CORS 功能即可,前端的代码无须做任何改动

JSONP

JSONP 是实现跨域数据请求的一种技术解决方案。它只支持 GET 请求,不支持 POST、DELETE 等其它请求。

在实际开发中很少被使用 在面试中可能会问到 JSONP 的原理

JSONP 不是真正的 Ajax 技术

在解决跨域问题时:

CORS 方案用到了 XMLHttpRequest 对象,发起的是纯正的 Ajax 请求

JSONP 方案没有用到 XMLHttpRequest 对象,因此,JSONP 不是真正的 Ajax 技术

结论:只要用到了 XMLHttpRequest 对象,发起的就是 Ajax 请求!

Access-Control-Allow-Origin: 允许跨域访问的域,可以是一个域的列表,也可以是通配符"*"。这里要注意Origin规则只对域名有效,并不会对子目录有效。
即http://www.test/test/是无效的。但是不同子域名需要分开设置,这里的规则可以参照那篇同源策略
Access-Control-Allow-Credentials: 是否允许请求带有验证信息,这部分将会在下面详细解释
Access-Control-Expose-Headers: 允许脚本访问的返回头,请求成功后,脚本可以在XMLHttpRequest中访问这些头的信息(貌似webkit没有实现这个)
Access-Control-Max-Age: 缓存此次请求的秒数。在这个时间范围内,所有同类型的请求都将不再发送预检请求而是直接使用此次返回的头作为判断依据,非常有用,大幅优化请求次数
Access-Control-Allow-Methods: 允许使用的请求方法,以逗号隔开
Access-Control-Allow-Headers: 允许自定义的头部,以逗号隔开,大小写不敏感

 客户端也需要在发起请求的时设置请求头信息,以通知服务器请求是来自哪个域名:

xhr.setRequestHeader("Origin",location.origin) //域名
//xhr.setRequestHeader("Origin","http://example.com")

 

感谢分享:

https://www.cnblogs.com/chenpingzhao/p/4517410.html
https://blog.csdn.net/web2022050903/article/details/124840748
https://www.cnblogs.com/chenpingzhao/p/4517410.html
 

 

posted @ 2023-06-04 17:09  浩浩学习  阅读(5901)  评论(0编辑  收藏  举报