共计 11964 个字符,预计需要花费 30 分钟才能阅读完成。
简介 同源
XMLHttpRequest 的实例属性
XMLHttpRequest.readyState
XMLHttpRequest.onreadystatechange
XMLHttpRequest.response
XMLHttpRequest.responseType
XMLHttpRequest.responseText
XMLHttpRequest.responseXML
XMLHttpRequest.responseURL
XMLHttpRequest.status,XMLHttpRequest.statusText
XMLHttpRequest.timeout,XMLHttpRequestEventTarget.ontimeout
事件监听属性
XMLHttpRequest.withCredentials
XMLHttpRequest.upload
XMLHttpRequest 的实例方法
XMLHttpRequest.open()
XMLHttpRequest.send()
XMLHttpRequest.setRequestHeader()
XMLHttpRequest.overrideMimeType()
XMLHttpRequest.getResponseHeader()
XMLHttpRequest.getAllResponseHeaders()
XMLHttpRequest.abort()
XMLHttpRequest 实例的事件
readyStateChange 事件
progress 事件
load 事件、error 事件、abort 事件
loadend 事件
timeout 事件
Navigator.sendBeacon()
1. 简介
浏览器与服务器之间,采用 HTTP 协议通信。
主要是输入网页,a iframe 提交表单 window.open
AJAX 包括以下几个步骤。
- 创建 XMLHttpRequest 实例
- 发出 HTTP 请求
- 接收服务器传回的数据
- 更新网页数据
概括起来,就是一句话,AJAX 通过原生的 XMLHttpRequest 对象发出 HTTP 请求,得到服务器返回的数据后,再进行处理
XMLHttpRequest 对象是 AJAX 的主要接口,用于浏览器与服务器之间的通信。尽管名字里面有 XML 和 Http,它实际上可以使用多种协议(比如 file 或 ftp),发送任何格式的数据(包括字符串和二进制)。
XMLHttpRequest 本身是一个构造函数,可以使用 new 命令生成实例。它没有任何参数。
var xhr = new XMLHttpRequest();
一旦新建实例,就可以使用 open()方法发出 HTTP 请求。
xhr.open(‘GET’, ‘http://www.example.com/page.php’, true);
然后,指定回调函数,监听通信状态(readyState 属性)的变化。
xhr.onreadystatechange = handleStateChange;
function handleStateChange() {
// …
}
上面代码中,一旦 XMLHttpRequest 实例的状态发生变化,就会调用监听函数 handleStateChange
注意,AJAX 只能向同源网址(协议、域名、端口都相同)发出 HTTP 请求,如果发出跨域请求,就会报错(详见《同源政策》和《CORS 通信》两章)。
下面是 XMLHttpRequest 对象简单用法的完整例子。
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function(){
// 通信成功时,状态值为 4
if (xhr.readyState === 4){
if (xhr.status === 200){console.log(xhr.responseText);
} else {console.error(xhr.statusText);
}
}
};
xhr.onerror = function (e) {
console.error(xhr.statusText);
};
xhr.open(‘GET’, ‘/endpoint’, true);
xhr.send(null);
2.1.XMLHttpRequest 的实例属性
2.2.XMLHttpRequest.readyState
返回整数
0. 实例生成,open 未调用
1.open 调用 未 send 仍然可以使用实例的 setRequestHeader() 方法,设定 HTTP 请求的头信息。
2,表示实例的 send()方法已经调用,并且服务器返回的头信息和状态码已经收到。
3. 正在发送数据体 body,这时,如果实例的 responseType 属性等于 text 或者空字符串,responseText 属性就会包含已经收到的部分信息
4. 接收完成或者接收失败
通信过程中,每当实例对象发生状态变化,它的 readyState 属性的值就会改变。这个值每一次变化,都会触发 readyStateChange 事件
2.3.XMLHttpRequest.onreadystatechange
readystatechange 事件发生时(实例的 readyState 属性变化),就会执行这个属性。
另外,如果使用实例的 abort()方法,终止 XMLHttpRequest 请求,也会造成 readyState 属性变化,导致调用 XMLHttpRequest.onreadystatechange 属性。
下面是一个例子。
var xhr = new XMLHttpRequest();
xhr.open(‘GET’, ‘http://example.com’ , true);
xhr.onreadystatechange = function () {
if (xhr.readyState !== 4 || xhr.status !== 200) {
return;
}
console.log(xhr.responseText);
};
xhr.send();
2.4.XMLHttpRequest.response
返回数据 bodyy 具体的类型由 XMLHttpRequest.responseType 属性决定。该属性只读。不成功 null
2.5.XMLHttpRequest.responseType
“”(空字符串):等同于 text,表示服务器返回文本数据。
“arraybuffer”:ArrayBuffer 对象,表示服务器返回二进制数组。
“blob”:Blob 对象,表示服务器返回二进制对象。
“document”:Document 对象,表示服务器返回一个文档对象。
“json”:JSON 对象。
“text”:字符串
text 类型适合大多数情况,而且直接处理文本也比较方便。document 类型适合返回 HTML / XML 文档的情况
blob 类型适合读取二进制数据,比如图片文件。
var xhr = new XMLHttpRequest();
xhr.open(‘GET’, ‘/path/to/image.png’, true);
xhr.responseType = ‘blob’;
xhr.onload = function(e) {
if (this.status === 200) {
var blob = new Blob([xhr.response], {type: 'image/png'});
// 或者
var blob = xhr.response;
}
};
xhr.send();
如果将这个属性设为 ArrayBuffer,就可以按照数组的方式处理二进制数据。
var xhr = new XMLHttpRequest();
xhr.open(‘GET’, ‘/path/to/image.png’, true);
xhr.responseType = ‘arraybuffer’;
xhr.onload = function(e) {
var uInt8Array = new Uint8Array(this.response);
for (var i = 0, len = binStr.length; i < len; ++i) {
// var byte = uInt8Array[i];
}
};
xhr.send();
如果将这个属性设为 json,浏览器就会自动对返回数据调用 JSON.parse()方法。也就是说,从 xhr.response 属性(注意,不是 xhr.responseText 属性)得到的不是文本,而是一个 JSON 对象
2.6.XMLHttpRequest.responseText
收到返回额字符串
2.7.XMLHttpRequest.responseXML
收到返回的 xml 或 html,如果不能被解析成 xmlhtml 返回 null
该属性生效的前提是 HTTP 回应的 Content-Type 头信息等于 text/xml 或 application/xml。这要求在发送请求前,XMLHttpRequest.responseType 属性要设为 document
果 HTTP 回应的 Content-Type 头信息不等于 text/xml 和 application/xml,但是想从 responseXML 拿到数据(即把数据按照 DOM 格式解析),那么需要手动调用 XMLHttpRequest.overrideMimeType()方法,强制进行 XML 解析
2.8.XMLHttpRequest.responseURL
表示发送数据的服务器的网址
2.9.XMLHttpRequest.status,XMLHttpRequest.statusText
200, OK,访问正常
301, Moved Permanently,永久移动
302, Moved temporarily,暂时移动
304, Not Modified,未修改
307, Temporary Redirect,暂时重定向
401, Unauthorized,未授权
403, Forbidden,禁止访问
404, Not Found,未发现指定网址
500, Internal Server Error,服务器发生错误
XMLHttpRequest.statusText 属性返回一个字符串,表示服务器发送的状态提示。不同于 status 属性,该属性包含整个状态信息,比如“OK”和“Not Found”。
如果服务器没有返回状态提示,该属性的值默认为“OK”。该属性为只读属性
2.10.XMLHttpRequest.timeout,XMLHttpRequestEventTarget.ontimeout
返回整数,多少毫秒后,请求终止。等于 0,没有限制。
XMLHttpRequestEventTarget.ontimeout 属性用于设置一个监听函数,如果发生 timeout 事件,就会执行这个监听函数
2.11 事件监听属性
XMLHttpRequest.onloadstart:loadstart 事件(HTTP 请求发出)的监听函数
XMLHttpRequest.onprogress:progress 事件(正在发送和加载数据)的监听函数
XMLHttpRequest.onabort:abort 事件(请求中止,比如用户调用了 abort() 方法)的监听函数
XMLHttpRequest.onerror:error 事件(请求失败)的监听函数
XMLHttpRequest.onload:load 事件(请求成功完成)的监听函数
XMLHttpRequest.ontimeout:timeout 事件(用户指定的时限超过了,请求还未完成)的监听函数
XMLHttpRequest.onloadend:loadend 事件(请求完成,不管成功或失败)的监听函数
progress 事件的监听函数有一个事件对象参数,该对象有三个属性:loaded 属性返回已经传输的数据量,total 属性返回总的数据量,lengthComputable 属性返回一个布尔值,表示加载的进度是否可以计算
只有 progress 事件的监听函数有参数,其他函数都没有参数。
注意,如果发生网络错误(比如服务器无法连通),onerror 事件无法获取报错信息。也就是说,可能没有错误对象,所以这样只能显示报错的提示。
2.12XMLHttpRequest.withCredentials
返回真假
表示跨域请求时,用户信息(比如 Cookie 和认证的 HTTP 头信息)是否会包含在请求之中,默认为 false
如果需要跨域 AJAX 请求发送 Cookie,需要 withCredentials 属性设为 true。注意,同源的请求不需要设置这个属性
为了让这个属性生效,服务器必须显式返回 Access-Control-Allow-Credentials 这个头信息。
Access-Control-Allow-Credentials: true
withCredentials 属性打开的话,跨域请求不仅会发送 Cookie,还会设置远程主机指定的 Cookie。反之也成立,如果 withCredentials 属性没有打开,那么跨域的 AJAX 请求即使明确要求浏览器设置 Cookie,浏览器也会忽略。
2.13XMLHttpRequest.upload
返回一个对象,
可以得知上传的进展。主要方法就是监听这个对象的各种事件:loadstart、loadend、load、abort、error、progress、timeout。
XMLHttpRequest 不仅可以发送请求,还可以发送文件
假定网页上有一个 <progress> 元素。
<progress min=”0″ max=”100″ value=”0″>0% complete</progress>
文件上传时,对 upload 属性指定 progress 事件的监听函数,即可获得上传的进度
function upload(blobOrFile) {
var xhr = new XMLHttpRequest();
xhr.open(‘POST’, ‘/server’, true);
xhr.onload = function (e) {};
var progressBar = document.querySelector(‘progress’);
xhr.upload.onprogress = function (e) {
if (e.lengthComputable) {progressBar.value = (e.loaded / e.total) * 100;
// 兼容不支持 <progress> 元素的老式浏览器
progressBar.textContent = progressBar.value;
}
};
xhr.send(blobOrFile);
}
upload(new Blob([‘hello world’], {type: ‘text/plain’}));
3.1.XMLHttpRequest 的实例方法
3.2.XMLHttpRequest.open()
指定参数,初始化
void open(
string method,
string url,
optional boolean async,
optional string user,
optional string password
);
method:表示 HTTP 动词方法,比如 GET、POST、PUT、DELETE、HEAD 等。
url: 表示请求发送目标 URL。
async: 布尔值,表示请求是否为异步,默认为 true。如果设为 false,则 send() 方法只有等到收到服务器返回了结果,才会进行下一步操作。该参数可选。由于同步 AJAX 请求会造成浏览器失去响应,许多浏览器已经禁止在主线程使用,只允许 Worker 里面使用。所以,这个参数轻易不应该设为 false。
user:表示用于认证的用户名,默认为空字符串。该参数可选。
password:表示用于认证的密码,默认为空字符串。该参数可选。
3.3.XMLHttpRequest.send()
XMLHttpRequest.send()方法用于实际发出 HTTP 请求 参数为数据体
它的参数是可选的,如果不带参数,就表示 HTTP 请求只有一个 URL,没有数据体,典型例子就是 GET 请求;如果带有参数,就表示除了头信息,还带有包含具体数据的信息体,典型例子就是 POST 请求
下面是 GET 请求的例子。
var xhr = new XMLHttpRequest();
xhr.open(‘GET’,
‘http://www.example.com/?id=’ + encodeURIComponent(id),
true
);
xhr.send(null);
上面代码中,GET 请求的参数,作为查询字符串附加在 URL 后面。
下面是发送 POST 请求的例子。
var xhr = new XMLHttpRequest();
var data = ’email=’
- encodeURIComponent(email)
- ‘&password=’
- encodeURIComponent(password);
xhr.open(‘POST’, ‘http://www.example.com’, true);
xhr.setRequestHeader(‘Content-Type’, ‘application/x-www-form-urlencoded’);
xhr.send(data);
所有 XMLHttpRequest 的监听事件,都必须在 send()方法调用之前设定
send 方法的参数就是发送的数据。多种格式的数据,都可以作为它的参数。
void send();
void send(ArrayBufferView data);
void send(Blob data);
void send(Document data);
void send(String data);
void send(FormData data);
如果 send()发送 DOM 对象,在发送之前,数据会先被串行化
下面是发送表单数据的例子。FormData 对象可以用于构造表单数据。
var formData = new FormData();
formData.append(‘username’, ‘ 张三 ’);
formData.append(’email’, ‘zhangsan@example.com’);
formData.append(‘birthDate’, 1940);
var xhr = new XMLHttpRequest();
xhr.open(‘POST’, ‘/register’);
xhr.send(formData);
上面代码中,FormData 对象构造了表单数据,然后使用 send()方法发送。它的效果与发送下面的表单数据是一样的。
<form id=’registration’ name=’registration’ action=’/register’>
<input type=’text’ name=’username’ value=’ 张三 ’>
<input type=’email’ name=’email’ value=’zhangsan@example.com’>
<input type=’number’ name=’birthDate’ value=’1940′>
<input type=’submit’ onclick=’return sendForm(this.form);’>
</form>
下面的例子是使用 FormData 对象加工表单数据,然后再发送。
function sendForm(form) {
var formData = new FormData(form);
formData.append(‘csrf’, ‘e69a18d7db1286040586e6da1950128c’);
var xhr = new XMLHttpRequest();
xhr.open(‘POST’, form.action, true);
xhr.onload = function() {
// ...
};
xhr.send(formData);
return false;
}
var form = document.querySelector(‘#registration’);
sendForm(form);
如果发送二进制数据,最好是发送 ArrayBufferView 或 Blob 对象,这使得通过 Ajax 上传文件成为可能。
3.4.XMLHttpRequest.setRequestHeader()
xhr.setRequestHeader(‘Content-Type’, ‘application/json’);
xhr.setRequestHeader(‘Content-Length’, JSON.stringify(data).length);
xhr.send(JSON.stringify(data));
3.5.XMLHttpRequest.overrideMimeType()
服务器返回的数据类型是 text/xml,由于种种原因浏览器解析不成功报错,这时就拿不到数据了。为了拿到原始数据,我们可以把 MIME 类型改成 text/plain,这样浏览器就不会去自动解析,从而我们就可以拿到原始文本了。
xhr.overrideMimeType(‘text/plain’)
3.6.XMLHttpRequest.getResponseHeader()
3.7.XMLHttpRequest.getAllResponseHeaders()
date: Fri, 08 Dec 2017 21:04:30 GMTrn
content-encoding: gziprn
x-content-type-options: nosniffrn
server: meinheld/0.6.1rn
x-frame-options: DENYrn
content-type: text/html; charset=utf-8rn
connection: keep-alivern
strict-transport-security: max-age=63072000rn
vary: Cookie, Accept-Encodingrn
content-length: 6502rn
x-xss-protection: 1; mode=blockrn
然后,对这个字符串进行处理。
var arr = headers.trim().split(/[rn]+/);
var headerMap = {};
arr.forEach(function (line) {
var parts = line.split(‘: ‘);
var header = parts.shift();
var value = parts.join(‘: ‘);
headerMap[header] = value;
});
headerMap[‘content-length’] // “6502”
3.8.XMLHttpRequest.abort()
readyState 属性变为 4,status 属性变为 0
4.1.XMLHttpRequest 实例的事件
4.2.readyStateChange 事件
readyState 属性的值发生改变,就会触发 readyStateChange 事件
4.progress 事件
上传文件时,XMLHttpRequest 实例对象本身和实例的 upload 属性,都有一个 progress 事件,会不断返回上传的进度。
var xhr = new XMLHttpRequest();
function updateProgress (oEvent) {
if (oEvent.lengthComputable) {
var percentComplete = oEvent.loaded / oEvent.total;
} else {
console.log('无法计算进展');
}
}
xhr.addEventListener(‘progress’, updateProgress);
xhr.open();
4.load 事件、error 事件、abort 事件
load 事件表示服务器传来的数据接收完毕,error 事件表示请求出错,abort 事件表示请求被中断(比如用户取消请求)。
var xhr = new XMLHttpRequest();
xhr.addEventListener(‘load’, transferComplete);
xhr.addEventListener(‘error’, transferFailed);
xhr.addEventListener(‘abort’, transferCanceled);
xhr.open();
function transferComplete() {
console.log(‘ 数据接收完毕 ’);
}
function transferFailed() {
console.log(‘ 数据接收出错 ’);
}
function transferCanceled() {
console.log(‘ 用户取消接收 ’);
}
4.loadend 事件
abort、load 和 error 这三个事件,会伴随一个 loadend 事件,表示请求结束,但不知道其是否成功。
xhr.addEventListener(‘loadend’, loadEnd);
function loadEnd(e) {
console.log(‘ 请求结束,状态未知 ’);
}
4.timeout 事件
服务器超过指定时间还没有返回结果,就会触发 timeout 事件,具体的例子参见 timeout 属性一节
5.Navigator.sendBeacon()
用户卸载网页的时候,有时需要向服务器发一些数据。很自然的做法是在 unload 事件或 beforeunload 事件的监听函数里面,使用 XMLHttpRequest 对象发送数据。但是,这样做不是很可靠,因为 XMLHttpRequest 对象是异步发送,很可能在它即将发送的时候,页面已经卸载了,从而导致发送取消或者发送失败。
解决方法就是 unload 事件里面,加一些很耗时的同步操作。这样就能留出足够的时间,保证异步 AJAX 能够发送成功。
function log() {
let xhr = new XMLHttpRequest();
xhr.open(‘post’, ‘/log’, true);
xhr.setRequestHeader(‘Content-Type’, ‘application/x-www-form-urlencoded’);
xhr.send(‘foo=bar’);
}
window.addEventListener(‘unload’, function(event) {
log();
// a time-consuming operation
for (let i = 1; i < 10000; i++) {
for (let m = 1; m < 10000; m++) {continue;}
}
});
上面代码中,强制执行了一次双重循环,拖长了 unload 事件的执行时间,导致异步 AJAX 能够发送成功。
类似的还可以使用 setTimeout。下面是追踪用户点击的例子。
// HTML 代码如下
// click
const clickTime = 350;
const theLink = document.getElementById(‘target’);
function log() {
let xhr = new XMLHttpRequest();
xhr.open(‘post’, ‘/log’, true);
xhr.setRequestHeader(‘Content-Type’, ‘application/x-www-form-urlencoded’);
xhr.send(‘foo=bar’);
}
theLink.addEventListener(‘click’, function (event) {
event.preventDefault();
log();
setTimeout(function () {
window.location.href = theLink.getAttribute('href');
}, clickTime);
});
上面代码使用 setTimeout,拖延了 350 毫秒,才让页面跳转,因此使得异步 AJAX 有时间发出。
这些做法的共同问题是,卸载的时间被硬生生拖长了,后面页面的加载被推迟了,用户体验不好。
为了解决这个问题,浏览器引入了 Navigator.sendBeacon()方法。这个方法还是异步发出请求,但是请求与当前页面线程脱钩,作为浏览器进程的任务,因此可以保证会把数据发出去,不拖延卸载流程。
window.addEventListener(‘unload’, logData, false);
function logData() {
navigator.sendBeacon(‘/log’, analyticsData);
}
Navigator.sendBeacon 方法接受两个参数,第一个参数是目标服务器的 URL,第二个参数是所要发送的数据(可选),可以是任意类型(字符串、表单对象、二进制对象等等)。
navigator.sendBeacon(url, data)
这个方法的返回值是一个布尔值,成功发送数据为 true,否则为 false。
该方法发送数据的 HTTP 方法是 POST,可以跨域,类似于表单提交数据。它不能指定回调函数。
下面是一个例子。
// HTML 代码如下
// <body onload=”analytics(‘start’)” onunload=”analytics(‘end’)”>
function analytics(state) {
if (!navigator.sendBeacon) return;
var URL = ‘http://example.com/analytics’;
var data = ‘state=’ + state + ‘&location=’ + window.location;
navigator.sendBeacon(URL, data);
}