1.说说AJAX和XMLHttpRequest对象的关系
AJAX是一种技术方案,但并不是一种新技术。它依赖的是现有的CSS/HTML/Javascript,而其中AJAX的核心技术就是XMLHttpRequest对象,是这个对象使得浏览器可以发出HTTP请求与接收HTTP响应,说白了就是我们使用XHR对象来发送一个AJAX请求
2.介绍XMLHttpRequest对象(XHR)
XMLHttpRequest对象,我们通常亲切地称它为“小黄人(XHR)”。
小黄人没出来之前
-
问题:浏览器都是没请求一次数据,就会对页面进行一次重绘(整个页面刷新),例如:每点击一次按钮(前提是该按钮绑定了请求数据的事件)就会刷新一次、搜索栏(前提是绑定了请求数据事件)里每输入一个字会进行刷新整个页面
-
造成的危害:每一次请求数据都会造成全页面的刷新,浪费资源(应该是哪里需要更新数据就更新哪里才对)
小黄人出来之后
-
突破:浏览器实现异步从服务器获取额外的数据,意味着用户进行请求数据操作不会导致整个页面刷新也会成功获取到数据,这样就大大降低了资源的浪费
2.1创建使用XMLHttpRequest对象
1.创建XMLHttpRequest对象
let xhr = new XMLHttpRequest()
2.使用XMLHttpRequest对象(同步情况)使用XMLHttpRequest对象(同步情况)
-
首先利用XHR实例对象(xhr)的open()方法对XHR对象进行一个初始化。open()方法接收三个参数,第一个参数为请求方式(GET请求、POST请求等),第二个参数为请求地址(URL地址),第三个参数为布尔值,用来表示是否异步发送请求(ture为异步,false为非异步)。这里要注意,open()方法只是为发送请求之前做好准备,并未发送请求
-
其次是XHR实例对象(xhr)的send()方法来进行发送请求。send()方法接收一个参数,是作为请求体(就是传给服务器数据的载体)发送的数据,如果不发送请求体,则必须将这个参数设置为null,因为这个参数在某些浏览器中是必须的。
-
之后就是等待服务器响应,服务器一旦响应(返回数据放在响应体里面),将会有以下几个属性被填充上数据传入到浏览器
-
responseText:作为响应体返回的文本;
-
responseXML:如果响应的内容类型为"text/xml"或者"application/xml",那就是包含响应数据的XML DOM文档;
-
status:响应的HTTP状态码;
-
statusText:响应的HTTP状态的描述。
-
注意:
1:最好检查status而不是statusText属性,因为后者已经被证明在跨浏览器进行数据请求时不可靠
2:responseText接收任何类型的内容,而responseXML只接收XML格式的数据
let xhr = new XMLHttpRequest(); // 创造XHR对象
xhr.open('GET','http://wwww.127.0.0.1:8000',false); // 初始化XHR对象 ,false是代表同步发送请求
xhr.send(null); // 发送请求,请求体为null
if((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){ // 对xhr的状态进行判断
console.log(xhr.responseText); // 判定成功,也就是请求成功,则输出返回内容
}else{
console.log('Request was None:' + xhr.status); // 判定失败,报出错误状态
}
此外,在同步请求的情况下,我们要注意:(下面三个属性后文会提到)
-
xhr.timeout必须为0
-
xhr.withCredentials必须为 false(withCredentials表示跨域时是否携带cookie)
-
xhr.responseType必须为" "(注意置为"text"也不允许)
-
在 chrome中,当xhr为同步请求时,在xhr.readyState由1变到2、2变到3,并不会触发onreadystatechange事件(也就是说只有在1、4的时候触发)
如果不遵守上面的前三个注意点,代码就会报错
3.使用XMLHttpRequest对象(异步情况)
首先提到readyState属性,表示当前处在请求/响应过程的哪一个阶段。实操中我们只关注4这个阶段
-
0:未初始化(Uninitialized)。也就是没调用open()方法;
-
1:已打开(Open)。调用了open()方法,但是没有调用send()方法;
-
2:已发送(Send)。调用了send()方法,但是服务器还没给回应数据;
-
3:接收中(Receiving)。正在接收服务器的回应数据(已经收到了部分回应数据);
-
4:完成(Complete)。接收了所有回应数据。
其次提及readyState属性值每从一个值变成另一个值都会触发readyStatechange事件(但xhr.readyState由非0值变为0时不触发)。我们可以利用这个事件来检查这个属性的值。为保证跨浏览器请求数据的兼容性,onreadyStatechange事件处理程序应该在调用open()之前赋值(send()方法还是在open()方法的后边)
let xhr = new XMLHttpRequest();
xhr.onreadystatechange = function(){ // 注意:这个事件里面不要用this代替xhr,有些浏览器会报错
if(xhr.readyState === 4){
if((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){
console.log(xhr.responseText);
}else{
console.log('Request was None:' + xhr.status);
}
}
}
xhr.open('GET','http://127.0.0.1:8000',true); // ture 为异步,默认就是true
xhr.send(null);
2.2取消XHR请求方法
利用XHR实例对象(xhr)的abort()方法来在服务器还没响应之前取消异步请求
服务器端代码(NodeJS书写)
import express from "express";
const app = express();
app.post('/user',(req,res) => {
// 设置响应头 设置允许跨域
res.setHeader('Access-Control-Allow-Origin', '*')
// 设置定时器,当客户端发起请求时,服务端延迟3s再将响应数据发给客户端
setTimeout(() => {
// 设置响应体
res.send('hello 延迟响应3秒!!!')
}, 3000)
})
app.listen('8000',() => {
console.log('your sever is running at http://127.0.0.1:8000');
})
客户端代码
let btn = document.querySelectorAll('button');
let xhr = new XMLHttpRequest();
// 发送请求
btn[0].addEventListener('click', () => {
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304) {
console.log(xhr.responseText);
} else {
console.log('Request was None:' + xhr.status);
}
}
}
xhr.open('POST', 'http://127.0.0.1:8000/user', true);
xhr.send(null);
});
// 取消请求
btn[1].addEventListener('click',() => {
xhr.abort()
})
2.3介绍有关Http响应头的XHR方法
1.setRequestHeader()方法,这个方法可以在发送AJAX请求时额外添加Http的请求头部(自定义头部字段),该方法接收两个参数,第一个参数头部字段名称,第二个参数是对应的头部字段的值
xhr.open('POST',url,true);
xhr.setRequestHeader("myHeader","myValue");
xhr.send(null);
2.getResponseHeader()方法,可以利用该方法获取XHR获取响应时的响应头部,直接给上参数(我们自己想获取的响应头部)
let resHeader = xhr.getResponseHeader("myHeader");
3.getAllResponseHeader()方法,可以利用该方法获取几乎所有的响应头部
let resAllheader = xhr.getResponseHeader();
注意点:
1.setRequestHeader必须在open()方法之后,send()方法之前调用,否则会抛错;2.setRequestHeader可以调用多次;
3.getAllResponseHeaders()只能拿到限制以外(即被视为safe)的header字段,而不是全部字段;
4.调用getResponseHeader("myHeader")方法时,myHeader参数必须是限制以外的header字段,否则调用就会报Refused to get unsafe header的错误。
3.介绍XMLHttpRequest Level2
我们在1.1节、1.2节介绍的是老版的XHR对象(XMLHttpRequest Level1),这一节介绍更新版本的XHR对象。但是Level2的兼容性不太行,不过所有浏览器都支持部分的Level2内容
3.1新增FormData类型
该类型便于对表单数据的序列化,方便利用XHR对表单数据的上传或提交,再就是不需要显式提供请求头,XHR对象会自动识别它,将包含它的请求体的Content-Type设置为multipart/form-data格式
formElem.onsubmit = async (e) => {
e.preventDefault();
let xhr = new XMLHttpRequest();
xhr.onreadystatechange = function(){
if(xhr.readyState === 4){
if(xhr.status === 200){
console.log(xhr.responseText);
}else{
console.log('失败')
}
}
}
xhr.open('POST','http://127.0.0.1:8000/user',true)
// xhr.setRequestHeader('Content-Type','multipart/form-data') 设置传输Content-Type : multipart/form-data;
// xhr.send(null)
xhr.send(new FormData(formElem)); // 默认传输Content-Type : multipart/form-data;
};
该类型有一个append()方法,添加键值对
let form = new FormData();
form.append('name','张三'); // name 是键 , 张三 是 值
3.2超时设置
XHR Level2里面新增了一个timeout新属性,该属性可以设置一个时间,如果从请求开始到这个时间结束还没有收到响应,则自动中断请求,相应的还有一个ontimeout事件处理程序,就是终端之后触发的效果
1.服务端代码(NodeJS书写)
import express from "express";
const app = express();
app.post('/user',(req,res) => {
// 设置响应头 设置允许跨域
res.setHeader('Access-Control-Allow-Origin', '*')
// 设置定时器,当客户端发起请求时,服务端延迟3s再将响应数据发给客户端
setTimeout(() => {
// 设置响应体
res.send('hello 延迟响应3秒!!!')
}, 3000) // 服务器这里设置3秒后响应
})
app.listen('8000',() => {
console.log('your sever is running at http://127.0.0.1:8000');
})
2.客户端代码
formElem.onsubmit = async (e) => {
e.preventDefault();
let xhr = new XMLHttpRequest();
xhr.onreadystatechange = function(){
if(xhr.readyState === 4){
if(xhr.status === 200){
console.log(xhr.responseText);
}else{
console.log('失败') // 这里输出 “失败”
}
}
}
xhr.open('POST','http://127.0.0.1:8000/user',true);
xhr.timeout = 1000; // 在1s之内没接收到响应就取消请求
xhr.ontimeout = function(){ // 取消请求后直接触发ontimeout事件,报出 “请求超时了”
console.log('请求超时了');
}
xhr.send(null);
};
注意:
1.不管是因为超时取消请求或者手动取消请求,readyState属性都会经历0、1、4这几个状态,也就是说我们最终需要在判定readyState状态之后还要判定下status状态码才能确保不会出错,可以把检查status状态的代码放到try/catch语句中
2.可以在send()之后再设置xhr.timeout,但计时起始点仍为调用xhr.send()方法的时刻
3.3overrideMimeType()方法、responseType属性值
1.overrideMimeType()用于强行更改MIME类型。假设服务器实际发送了XML数据,但是响应头设置的MIME类型是text/plain,这样子的结果就会导致,浏览器收到的XML数据为null,那我们就可以通过这个方法来更改MIME类型,达到我们想要的效果。该方法支持所有的MIME类型【媒体类型(通常称为Multipurpose Internet Mail Extensions或MIME类型)是一种标准,用来表示文档、文件或字节流的性质和格式】
let xhr = new XMLHttpRequest();
xhr.open(...); // 懒得写数据了
xhr.overrideMimeType('text/xml'); // 将MIME类型更改为text/xml
xhr.send(null);
// MIME类型的通用结构
type/subtype
// 例如:
text/plain
text/html
image/jpeg
image/png
audio/mpeg
audio/ogg
audio/*
video/mp4
// application表明是某种二进制数据
application/*
application/json
application/javascript
application/ecmascript
application/octet-stream
...
2.responseType 是一个枚举字符串值,用于指定响应中包含的数据类型。它还允许我们更改响应类型。如果将responseType的值设置为空字符串,则会使用text作为默认值。支持的值如下表:
值(type) | xhr.response数据类型 | 说明 |
---|---|---|
"" | String字符串 | 默认值 |
"text" | String字符串 | DOMString 对象中的文本 |
"document" | Document对象 | 希望返回XML格式数据 |
"json" | javascript对象 | 将接收到的数据内容解析为 JSON |
"blob" | Blob对象 | 一个包含二进制数据的 Blob 对象 |
"arrayBuffer" | ArrayBuffer对象 | 包含二进制数据 |
"ms-stream" | 此响应类型仅允许用于下载请求,并且仅受 Internet Explorer 支持 |
//语法
let xhr = new XMLHttpRequest();
xhr.responseType = type;
注意:
1.为了正确的覆盖MIME类型,我们必须在调用send()方法之前调用overrideMimeType()方法
2.responseType属性还存在兼容性问题,将responseType设置为特定值时,我们应确保服务器实际发送的响应与该格式兼容。如果服务器返回的数据与设置的responseType不兼容,则response的值将为null
3.4进度事件(Progress Events)
进度事件起初只针对XHR,现在推广到其他类似的API,有6个进度相关事件
1.loadstart事件:调用xhr.send()方法后立即触发,若xhr.send()未被调用则不会触发此事件
2.progress事件:xhr.onprogress在下载阶段(即xhr.readystate=3时)触发,每50ms触发一 次
3.error事件:只有发生了网络层级别的异常才会触发此事件,对于应用层级别的异常(也就是网络异常)
4.abort事件:在调用abort()方法终止连接时触发
5.load事件:当请求成功完成时触发,此时xhr.readystate=4
6.loadend事件:当请求结束(包括请求成功和请求失败)时触发,且在error、abort、load之后触发
let xhr = new XMLHttpRequest();
let btn = document.querySelector('button');
btn.addEventListener('click',() => {
xhr.abort()
})
formElem.onsubmit = async (e) => {
e.preventDefault();
xhr.onreadystatechange = function(){
if(xhr.readyState === 0){
console.log(0)
}
if(xhr.readyState === 1){
console.log(1)
}
if(xhr.readyState === 2){
console.log(2)
}
if(xhr.readyState === 3){
console.log(3)
}
if(xhr.readyState === 4){
console.log(4)
}
}
xhr.onprogress = function(){
console.log('我正在下载东西----progress');
}
xhr.open('GET','http://127.0.0.1:8000/user?name=hzh&age=23',true);
xhr.onloadstart = function(){
console.log('刚触发了send()方法----loadstart');
}
xhr.send(null);
xhr.onerror = function(){
console.log('请求出问题了----error');
}
xhr.onabort = function(){
console.log('你取消了连接----abort');
}
xhr.onload = function(){
console.log('你完成了响应----load');
}
xhr.onloadend = function(){
console.log('你的请求结束了----loadend');
}
};
注意:
1.onloadstart处理程序事件应该放在send()方法之前(不放在它之前的话,第一次请求就不会触发onloadstart处理程序事件)。在不在open()方法之后无所谓,可以在也可以不在,不过我建议还是放在open方法之后、send()方法之前,主要是符合规矩,虽然放在open()方法之前也可以。
2.为了确保正确执行,必须再调用open()方法之前调用onprogress()处理程序事件
-
下面我来具体说说onload处理程序事件:
在火狐浏览器中onload事件可以直接替代readystatechange事件,因为load事件是在触发完成时,也就是readyState为4时触发的,这样我们就无需利用readyreadystatechange以及readyState来进行判定之后再处理数据,但是我们还是要根据status状态码来判定数据是否有效。(onload事件会收到一个event对象,该对象有个target属性能访问到XHR实例的所有方法和属性,但是这个对象的兼容不是很好,则对于跨浏览器而言,我们不用这个对象和这个属性)
formElem.onsubmit = (e) => {
e.preventDefault();
xhr.onload = function() { // 利用onload代替readystatechange事件
if(xhr.status === 200){
console.log(xhr.responseText);
}
}
xhr.open('GET','http://127.0.0.1:8000/user',true);
xhr.send();
};
-
下面我来具体说说onprogress处理程序事件
每次触发onprogress处理程序事件时,我们都会收到一个event对象,该对象的target属性是XHR对象,根据这个event对象,我们可以利用它的lengthComputable、loaded和total属性来完成一个上传进度条。(lengthComputable表示进度信息是否可用、loaded表示加载的字节数、total表示响应的Content-Length头部定义的总字节数)
xhr.onprogress = function (e) {
console.log(e.target); // XHR对象
console.log(e.lengthComputable); // 进度信息是否可用
console.log(e.loaded); // 加载的字节数
console.log(e.total); // 响应的Content-Length头部定义的总字节数
console.log('我正在下载东西----progress');
}
4.总结
1.Level1 有以下缺点:
-
受同源策略的限制,不能发送跨域请求;
-
不能发送二进制文件(如图片、视频、音频等),只能发送纯文本数据;
-
在发送和获取数据的过程中,无法实时获取进度信息,只能判断是否完成;
2.Level2 改善情况:
-
可以发送跨域请求,在服务端允许的情况下;
-
支持发送和接收二进制数据;
-
新增FormData对象,支持发送表单数据;
-
发送和获取数据时,可以获取进度信息;
-
可以设置请求的超时时间;