瀏覽器與服務(wù)器之間,采用HTTP協(xié)議通信。用戶在瀏覽器地址欄鍵入一個(gè)網(wǎng)址,或者通過網(wǎng)頁表單向服務(wù)器提交內(nèi)容,這時(shí)瀏覽器就會向服務(wù)器發(fā)出HTTP請求。
1999年,微軟公司發(fā)布IE瀏覽器5.0版,第一次引入新功能:允許JavaScript腳本向服務(wù)器發(fā)起HTTP請求。這個(gè)功能當(dāng)時(shí)并沒有引起注意,直到2004年Gmail發(fā)布和2005年Google Map發(fā)布,才引起廣泛重視。2005年2月,AJAX這個(gè)詞第一次正式提出,指圍繞這個(gè)功能進(jìn)行開發(fā)的一整套做法。從此,AJAX成為腳本發(fā)起HTTP通信的代名詞,W3C也在2006年發(fā)布了它的國際標(biāo)準(zhǔn)。
具體來說,AJAX包括以下幾個(gè)步驟。
- 創(chuàng)建AJAX對象
- 發(fā)出HTTP請求
- 接收服務(wù)器傳回的數(shù)據(jù)
- 更新網(wǎng)頁數(shù)據(jù)
概括起來,就是一句話,AJAX通過原生的XMLHttpRequest對象發(fā)出HTTP請求,得到服務(wù)器返回的數(shù)據(jù)后,再進(jìn)行處理。
AJAX可以是同步請求,也可以是異步請求。但是,大多數(shù)情況下,特指異步請求。因?yàn)橥降腁jax請求,對瀏覽器有“堵塞效應(yīng)”。
XMLHttpRequest對象
XMLHttpRequest對象用來在瀏覽器與服務(wù)器之間傳送數(shù)據(jù)。
var ajax = new XMLHttpRequest();
ajax.open('GET', 'http://www.example.com/page.php', true);
上面代碼向指定的服務(wù)器網(wǎng)址,發(fā)出GET請求。
然后,AJAX指定回調(diào)函數(shù),監(jiān)聽通信狀態(tài)(readyState屬性)的變化。
ajax.onreadystatechange = handleStateChange;
一旦拿到服務(wù)器返回的數(shù)據(jù),AJAX不會刷新整個(gè)網(wǎng)頁,而是只更新相關(guān)部分,從而不打斷用戶正在做的事情。
注意,AJAX只能向同源網(wǎng)址(協(xié)議、域名、端口都相同)發(fā)出HTTP請求,如果發(fā)出跨源請求,就會報(bào)錯(cuò)(詳見《同源政策》和《CORS機(jī)制》兩節(jié))。
雖然名字里面有XML,但是實(shí)際上,XMLHttpRequest可以報(bào)送各種數(shù)據(jù),包括字符串和二進(jìn)制,而且除了HTTP,它還支持通過其他協(xié)議傳送(比如File和FTP)。
下面是XMLHttpRequest對象的典型用法。
var xhr = new XMLHttpRequest();
// 指定通信過程中狀態(tài)改變時(shí)的回調(diào)函數(shù)
xhr.onreadystatechange = function(){
// 通信成功時(shí),狀態(tài)值為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);
};
// open方式用于指定HTTP動(dòng)詞、請求的網(wǎng)址、是否異步
xhr.open('GET', '/endpoint', true);
// 發(fā)送HTTP請求
xhr.send(null);
open方法的第三個(gè)參數(shù)是一個(gè)布爾值,表示是否為異步請求。如果設(shè)為false,就表示這個(gè)請求是同步的,下面是一個(gè)例子。
var request = new XMLHttpRequest();
request.open('GET', '/bar/foo.txt', false);
request.send(null);
if (request.status === 200) {
console.log(request.responseText);
}
XMLHttpRequest實(shí)例的屬性
readyState
readyState是一個(gè)只讀屬性,用一個(gè)整數(shù)和對應(yīng)的常量,表示XMLHttpRequest請求當(dāng)前所處的狀態(tài)。
- 0,對應(yīng)常量
UNSENT,表示XMLHttpRequest實(shí)例已經(jīng)生成,但是open()方法還沒有被調(diào)用。 - 1,對應(yīng)常量
OPENED,表示send()方法還沒有被調(diào)用,仍然可以使用setRequestHeader(),設(shè)定HTTP請求的頭信息。 - 2,對應(yīng)常量
HEADERS_RECEIVED,表示send()方法已經(jīng)執(zhí)行,并且頭信息和狀態(tài)碼已經(jīng)收到。 - 3,對應(yīng)常量
LOADING,表示正在接收服務(wù)器傳來的body部分的數(shù)據(jù),如果responseType屬性是text或者空字符串,responseText就會包含已經(jīng)收到的部分信息。 - 4,對應(yīng)常量
DONE,表示服務(wù)器數(shù)據(jù)已經(jīng)完全接收,或者本次接收已經(jīng)失敗了。
在通信過程中,每當(dāng)發(fā)生狀態(tài)變化的時(shí)候,readyState屬性的值就會發(fā)生改變。這個(gè)值每一次變化,都會觸發(fā)readyStateChange事件。
if (ajax.readyState == 4) {
// Handle the response.
} else {
// Show the 'Loading...' message or do nothing.
}
上面代碼表示,只有readyState變?yōu)?時(shí),才算確認(rèn)請求已經(jīng)成功,其他值都表示請求還在進(jìn)行中。
onreadystatechange
onreadystatechange屬性指向一個(gè)回調(diào)函數(shù),當(dāng)readystatechange事件發(fā)生的時(shí)候,這個(gè)回調(diào)函數(shù)就會調(diào)用,并且XMLHttpRequest實(shí)例的readyState屬性也會發(fā)生變化。
另外,如果使用abort()方法,終止XMLHttpRequest請求,onreadystatechange回調(diào)函數(shù)也會被調(diào)用。
var xmlhttp = new XMLHttpRequest();
xmlhttp.open( 'GET', 'http://example.com' , true );
xmlhttp.onreadystatechange = function () {
if ( XMLHttpRequest.DONE != xmlhttp.readyState ) {
return;
}
if ( 200 != xmlhttp.status ) {
return;
}
console.log( xmlhttp.responseText );
};
xmlhttp.send();
response
response屬性為只讀,返回接收到的數(shù)據(jù)體(即body部分)。它的類型可以是ArrayBuffer、Blob、Document、JSON對象、或者一個(gè)字符串,這由XMLHttpRequest.responseType屬性的值決定。
如果本次請求沒有成功或者數(shù)據(jù)不完整,該屬性就會等于null。
responseType
responseType屬性用來指定服務(wù)器返回?cái)?shù)據(jù)(xhr.response)的類型。
- ”“:字符串(默認(rèn)值)
- “arraybuffer”:ArrayBuffer對象
- “blob”:Blob對象
- “document”:Document對象
- “json”:JSON對象
- “text”:字符串
text類型適合大多數(shù)情況,而且直接處理文本也比較方便,document類型適合返回XML文檔的情況,blob類型適合讀取二進(jìn)制數(shù)據(jù),比如圖片文件。
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([this.response], {type: 'image/png'});
// 或者
var blob = oReq.response;
}
};
xhr.send();
如果將這個(gè)屬性設(shè)為ArrayBuffer,就可以按照數(shù)組的方式處理二進(jìn)制數(shù)據(jù)。
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();
如果將這個(gè)屬性設(shè)為“json”,支持JSON的瀏覽器(Firefox>9,chrome>30),就會自動(dòng)對返回?cái)?shù)據(jù)調(diào)用JSON.parse()方法。也就是說,你從xhr.response屬性(注意,不是xhr.responseText屬性)得到的不是文本,而是一個(gè)JSON對象。
XHR2支持Ajax的返回類型為文檔,即xhr.responseType=”document” 。這意味著,對于那些打開CORS的網(wǎng)站,我們可以直接用Ajax抓取網(wǎng)頁,然后不用解析HTML字符串,直接對XHR回應(yīng)進(jìn)行DOM操作。
responseText
responseText屬性返回從服務(wù)器接收到的字符串,該屬性為只讀。如果本次請求沒有成功或者數(shù)據(jù)不完整,該屬性就會等于null。
如果服務(wù)器返回的數(shù)據(jù)格式是JSON,就可以使用responseText屬性。
var data = ajax.responseText;
data = JSON.parse(data);
responseXML
responseXML屬性返回從服務(wù)器接收到的Document對象,該屬性為只讀。如果本次請求沒有成功,或者數(shù)據(jù)不完整,或者不能被解析為XML或HTML,該屬性等于null。
返回的數(shù)據(jù)會被直接解析為DOM對象。
/* 返回的XML文件如下
<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<book>
<chapter id="1">(Re-)Introducing JavaScript</chapter>
<chapter id="2">JavaScript in Action</chapter>
</book>
*/
var data = ajax.responseXML;
var chapters = data.getElementsByTagName('chapter');
如果服務(wù)器返回的數(shù)據(jù),沒有明示Content-Type頭信息等于text/xml,可以使用overrideMimeType()方法,指定XMLHttpRequest對象將返回的數(shù)據(jù)解析為XML。
status
status屬性為只讀屬性,表示本次請求所得到的HTTP狀態(tài)碼,它是一個(gè)整數(shù)。一般來說,如果通信成功的話,這個(gè)狀態(tài)碼是200。
- 200, OK,訪問正常
- 301, Moved Permanently,永久移動(dòng)
- 302, Move temporarily,暫時(shí)移動(dòng)
- 304, Not Modified,未修改
- 307, Temporary Redirect,暫時(shí)重定向
- 401, Unauthorized,未授權(quán)
- 403, Forbidden,禁止訪問
- 404, Not Found,未發(fā)現(xiàn)指定網(wǎng)址
- 500, Internal Server Error,服務(wù)器發(fā)生錯(cuò)誤
基本上,只有2xx和304的狀態(tài)碼,表示服務(wù)器返回是正常狀態(tài)。
if (ajax.readyState == 4) {
if ( (ajax.status >= 200 && ajax.status < 300)
|| (ajax.status == 304) ) {
// Handle the response.
} else {
// Status error!
}
}
statusText
statusText屬性為只讀屬性,返回一個(gè)字符串,表示服務(wù)器發(fā)送的狀態(tài)提示。不同于status屬性,該屬性包含整個(gè)狀態(tài)信息,比如”200 OK“。
timeout
timeout屬性等于一個(gè)整數(shù),表示多少毫秒后,如果請求仍然沒有得到結(jié)果,就會自動(dòng)終止。如果該屬性等于0,就表示沒有時(shí)間限制。
var xhr = new XMLHttpRequest();
xhr.ontimeout = function () {
console.error("The request for " + url + " timed out.");
};
xhr.onload = function() {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
callback.apply(xhr, args);
} else {
console.error(xhr.statusText);
}
}
};
xhr.open("GET", url, true);
xhr.timeout = timeout;
xhr.send(null);
}
事件監(jiān)聽接口
XMLHttpRequest第一版,只能對onreadystatechange這一個(gè)事件指定回調(diào)函數(shù)。該事件對所有情況作出響應(yīng)。 XMLHttpRequest第二版允許對更多的事件指定回調(diào)函數(shù)。
- onloadstart 請求發(fā)出
- onprogress 正在發(fā)送和加載數(shù)據(jù)
- onabort 請求被中止,比如用戶調(diào)用了
abort()方法 - onerror 請求失敗
- onload 請求成功完成
- ontimeout 用戶指定的時(shí)限到期,請求還未完成
- onloadend 請求完成,不管成果或失敗
xhr.onload = function() {
var responseText = xhr.responseText;
console.log(responseText);
// process the response.
};
xhr.onerror = function() {
console.log('There was an error!');
};
注意,如果發(fā)生網(wǎng)絡(luò)錯(cuò)誤(比如服務(wù)器無法連通),onerror事件無法獲取報(bào)錯(cuò)信息,所以只能顯示報(bào)錯(cuò)。
withCredentials
withCredentials屬性是一個(gè)布爾值,表示跨域請求時(shí),用戶信息(比如Cookie和認(rèn)證的HTTP頭信息)是否會包含在請求之中,默認(rèn)為false。即向[example.com](http://example.com/)發(fā)出跨域請求時(shí),不會發(fā)送[example.com](http://example.com/)設(shè)置在本機(jī)上的Cookie(如果有的話)。
如果你需要通過跨域AJAX發(fā)送Cookie,需要打開withCredentials。
xhr.withCredentials = true;
為了讓這個(gè)屬性生效,服務(wù)器必須顯式返回Access-Control-Allow-Credentials這個(gè)頭信息。
Access-Control-Allow-Credentials: true
.withCredentials屬性打開的話,不僅會發(fā)送Cookie,還會設(shè)置遠(yuǎn)程主機(jī)指定的Cookie。注意,此時(shí)你的腳本還是遵守同源政策,無法 從document.cookie或者HTTP回應(yīng)的頭信息之中,讀取這些Cookie。
XMLHttpRequest實(shí)例的方法
abort()
abort方法用來終止已經(jīng)發(fā)出的HTTP請求。
ajax.open('GET', 'http://www.example.com/page.php', true);
var ajaxAbortTimer = setTimeout(function() {
if (ajax) {
ajax.abort();
ajax = null;
}
}, 5000);
上面代碼在發(fā)出5秒之后,終止一個(gè)AJAX請求。
getAllResponseHeaders()
getAllResponseHeaders方法返回服務(wù)器發(fā)來的所有HTTP頭信息。格式為字符串,每個(gè)頭信息之間使用CRLF分隔,如果沒有受到服務(wù)器回應(yīng),該屬性返回null。
getResponseHeader()
getResponseHeader方法返回HTTP頭信息指定字段的值,如果還沒有收到服務(wù)器回應(yīng)或者指定字段不存在,則該屬性為null。
function getHeaderTime () {
console.log(this.getResponseHeader("Last-Modified"));
}
var oReq = new XMLHttpRequest();
oReq.open("HEAD", "yourpage.html");
oReq.onload = getHeaderTime;
oReq.send();
如果有多個(gè)字段同名,則它們的值會被連接為一個(gè)字符串,每個(gè)字段之間使用“逗號+空格”分隔。
open()
XMLHttpRequest對象的open方法用于指定發(fā)送HTTP請求的參數(shù),它的使用格式如下,一共可以接受五個(gè)參數(shù)。
void open(
string method,
string url,
optional boolean async,
optional string user,
optional string password
);
-
method:表示HTTP動(dòng)詞,比如“GET”、“POST”、“PUT”和“DELETE”。 -
url: 表示請求發(fā)送的網(wǎng)址。 -
async: 格式為布爾值,默認(rèn)為true,表示請求是否為異步。如果設(shè)為false,則send()方法只有等到收到服務(wù)器返回的結(jié)果,才會有返回值。 -
user:表示用于認(rèn)證的用戶名,默認(rèn)為空字符串。 -
password:表示用于認(rèn)證的密碼,默認(rèn)為空字符串。
如果對使用過open()方法的請求,再次使用這個(gè)方法,等同于調(diào)用abort()。
下面發(fā)送POST請求的例子。
xhr.open('POST', encodeURI('someURL'));
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.onload = function() {};
xhr.send(encodeURI('dataString'));
上面方法中,open方法向指定URL發(fā)出POST請求,send方法送出實(shí)際的數(shù)據(jù)。
下面是一個(gè)同步AJAX請求的例子。
var request = new XMLHttpRequest();
request.open('GET', '/bar/foo.txt', false);
request.send(null);
if (request.status === 200) {
console.log(request.responseText);
}
send()
send方法用于實(shí)際發(fā)出HTTP請求。如果不帶參數(shù),就表示HTTP請求只包含頭信息,也就是只有一個(gè)URL,典型例子就是GET請求;如果帶有參數(shù),就表示除了頭信息,還帶有包含具體數(shù)據(jù)的信息體,典型例子就是POST請求。
ajax.open('GET'
, 'http://www.example.com/somepage.php?id=' + encodeURIComponent(id)
, true
);
// 等同于
var data = 'id=' + encodeURIComponent(id));
ajax.open('GET', 'http://www.example.com/somepage.php', true);
ajax.send(data);
上面代碼中,GET請求的參數(shù),可以作為查詢字符串附加在URL后面,也可以作為send方法的參數(shù)。
下面是發(fā)送POST請求的例子。
var data = 'email='
+ encodeURIComponent(email)
+ '&password='
+ encodeURIComponent(password);
ajax.open('POST', 'http://www.example.com/somepage.php', true);
ajax.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
ajax.send(data);
如果請求是異步的(默認(rèn)為異步),該方法在發(fā)出請求后會立即返回。如果請求為同步,該方法只有等到收到服務(wù)器回應(yīng)后,才會返回。
注意,所有XMLHttpRequest的監(jiān)聽事件,都必須在send()方法調(diào)用之前設(shè)定。
send方法的參數(shù)就是發(fā)送的數(shù)據(jù)。多種格式的數(shù)據(jù),都可以作為它的參數(shù)。
void send();
void send(ArrayBufferView data);
void send(Blob data);
void send(Document data);
void send(String data);
void send(FormData data);
如果發(fā)送Document數(shù)據(jù),在發(fā)送之前,數(shù)據(jù)會先被串行化。
發(fā)送二進(jìn)制數(shù)據(jù),最好使用ArrayBufferView或Blob對象,這使得通過Ajax上傳文件成為可能。
下面是一個(gè)上傳ArrayBuffer對象的例子。
function sendArrayBuffer() {
var xhr = new XMLHttpRequest();
var uInt8Array = new Uint8Array([1, 2, 3]);
xhr.open('POST', '/server', true);
xhr.onload = function(e) { ... };
xhr.send(uInt8Array.buffer);
}
FormData類型可以用于構(gòu)造表單數(shù)據(jù)。
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);
上面的代碼構(gòu)造了一個(gè)formData對象,然后使用send方法發(fā)送。它的效果與點(diǎn)擊下面表單的submit按鈕是一樣的。
<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也可以將現(xiàn)有表單構(gòu)造生成。
var formElement = document.querySelector("form");
var request = new XMLHttpRequest();
request.open("POST", "submitform.php");
request.send(new FormData(formElement));
FormData對象還可以對現(xiàn)有表單添加數(shù)據(jù),這為我們操作表單提供了極大的靈活性。
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(e) {
// ...
};
xhr.send(formData);
return false;
}
var form = document.querySelector('#registration');
sendForm(form);
FormData對象也能用來模擬File控件,進(jìn)行文件上傳。
function uploadFiles(url, files) {
var formData = new FormData();
for (var i = 0, file; file = files[i]; ++i) {
formData.append(file.name, file); // 可加入第三個(gè)參數(shù),表示文件名
}
var xhr = new XMLHttpRequest();
xhr.open('POST', url, true);
xhr.onload = function(e) { ... };
xhr.send(formData); // multipart/form-data
}
document.querySelector('input[type="file"]').addEventListener('change', function(e) {
uploadFiles('/server', this.files);
}, false);
FormData也可以加入JavaScript生成的文件。
// 添加JavaScript生成的文件
var content = '<a id="a"><b id="b">hey!</b></a>';
var blob = new Blob([content], { type: "text/xml"});
formData.append("webmasterfile", blob);
setRequestHeader()
setRequestHeader方法用于設(shè)置HTTP頭信息。該方法必須在open()之后、send()之前調(diào)用。如果該方法多次調(diào)用,設(shè)定同一個(gè)字段,則每一次調(diào)用的值會被合并成一個(gè)單一的值發(fā)送。
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.setRequestHeader('Content-Length', JSON.stringify(data).length);
xhr.send(JSON.stringify(data));
上面代碼首先設(shè)置頭信息Content-Type,表示發(fā)送JSON格式的數(shù)據(jù);然后設(shè)置Content-Length,表示數(shù)據(jù)長度;最后發(fā)送JSON數(shù)據(jù)。
overrideMimeType()
該方法用來指定服務(wù)器返回?cái)?shù)據(jù)的MIME類型。該方法必須在send()之前調(diào)用。
傳統(tǒng)上,如果希望從服務(wù)器取回二進(jìn)制數(shù)據(jù),就要使用這個(gè)方法,人為將數(shù)據(jù)類型偽裝成文本數(shù)據(jù)。
var xhr = new XMLHttpRequest();
xhr.open('GET', '/path/to/image.png', true);
// 強(qiáng)制將MIME改為文本類型
xhr.overrideMimeType('text/plain; charset=x-user-defined');
xhr.onreadystatechange = function(e) {
if (this.readyState == 4 && this.status == 200) {
var binStr = this.responseText;
for (var i = 0, len = binStr.length; i < len; ++i) {
var c = binStr.charCodeAt(i);
var byte = c & 0xff; // 去除高位字節(jié),留下低位字節(jié)
}
}
};
xhr.send();
上面代碼中,因?yàn)閭骰貋淼氖嵌M(jìn)制數(shù)據(jù),首先用xhr.overrideMimeType方法強(qiáng)制改變它的MIME類型,偽裝成文本數(shù)據(jù)。字符集必需指定為“x-user-defined”,如果是其他字符集,瀏覽器內(nèi)部會強(qiáng)制轉(zhuǎn)碼,將其保存成UTF-16的形式。字符集“x-user-defined”其實(shí)也會發(fā)生轉(zhuǎn)碼,瀏覽器會在每個(gè)字節(jié)前面再加上一個(gè)字節(jié)(0xF700-0xF7ff),因此后面要對每個(gè)字符進(jìn)行一次與運(yùn)算(&),將高位的8個(gè)位去除,只留下低位的8個(gè)位,由此逐一讀出原文件二進(jìn)制數(shù)據(jù)的每個(gè)字節(jié)。
這種方法很麻煩,在XMLHttpRequest版本升級以后,一般采用指定responseType的方法。
var xhr = new XMLHttpRequest();
xhr.onload = function(e) {
var arraybuffer = xhr.response;
// ...
}
xhr.open("GET", url);
xhr.responseType = "arraybuffer";
xhr.send();
XMLHttpRequest實(shí)例的事件
readyStateChange事件
readyState屬性的值發(fā)生改變,就會觸發(fā)readyStateChange事件。
我們可以通過onReadyStateChange屬性,指定這個(gè)事件的回調(diào)函數(shù),對不同狀態(tài)進(jìn)行不同處理。尤其是當(dāng)狀態(tài)變?yōu)?的時(shí)候,表示通信成功,這時(shí)回調(diào)函數(shù)就可以處理服務(wù)器傳送回來的數(shù)據(jù)。
progress事件
上傳文件時(shí),XMLHTTPRequest對象的upload屬性有一個(gè)progress,會不斷返回上傳的進(jìn)度。
假定網(wǎng)頁上有一個(gè)progress元素。
<progress min="0" max="100" value="0">0% complete</progress>
文件上傳時(shí),對upload屬性指定progress事件回調(diào)函數(shù),即可獲得上傳的進(jìn)度。
function upload(blobOrFile) {
var xhr = new XMLHttpRequest();
xhr.open('POST', '/server', true);
xhr.onload = function(e) { ... };
// Listen to the upload progress.
var progressBar = document.querySelector('progress');
xhr.upload.onprogress = function(e) {
if (e.lengthComputable) {
progressBar.value = (e.loaded / e.total) * 100;
progressBar.textContent = progressBar.value; // Fallback for unsupported browsers.
}
};
xhr.send(blobOrFile);
}
upload(new Blob(['hello world'], {type: 'text/plain'}));
load事件、error事件、abort事件
load事件表示服務(wù)器傳來的數(shù)據(jù)接收完畢,error事件表示請求出錯(cuò),abort事件表示請求被中斷。
var xhr = new XMLHttpRequest();
xhr.addEventListener("progress", updateProgress);
xhr.addEventListener("load", transferComplete);
xhr.addEventListener("error", transferFailed);
xhr.addEventListener("abort", transferCanceled);
xhr.open();
function updateProgress (oEvent) {
if (oEvent.lengthComputable) {
var percentComplete = oEvent.loaded / oEvent.total;
// ...
} else {
// 回應(yīng)的總數(shù)據(jù)量未知,導(dǎo)致無法計(jì)算百分比
}
}
function transferComplete(evt) {
console.log("The transfer is complete.");
}
function transferFailed(evt) {
console.log("An error occurred while transferring the file.");
}
function transferCanceled(evt) {
console.log("The transfer has been canceled by the user.");
}
loadend事件
abort、load和error這三個(gè)事件,會伴隨一個(gè)loadend事件,表示請求結(jié)束,但不知道其是否成功。
req.addEventListener("loadend", loadEnd);
function loadEnd(e) {
alert("請求結(jié)束(不知道是否成功)");
}
文件上傳
HTML網(wǎng)頁的<form>元素能夠以四種格式,向服務(wù)器發(fā)送數(shù)據(jù)。
- 使用
POST方法,將enctype屬性設(shè)為application/x-www-form-urlencoded,這是默認(rèn)方法。
<form action="register.php" method="post" onsubmit="AJAXSubmit(this); return false;">
</form>
- 使用
POST方法,將enctype屬性設(shè)為text/plain。
<form action="register.php" method="post" enctype="text/plain" onsubmit="AJAXSubmit(this); return false;">
</form>
- 使用
POST方法,將enctype屬性設(shè)為multipart/form-data。
<form action="register.php" method="post" enctype="multipart/form-data" onsubmit="AJAXSubmit(this); return false;">
</form>
- 使用
GET方法,enctype屬性將被忽略。
<form action="register.php" method="get" onsubmit="AJAXSubmit(this); return false;">
</form>
某個(gè)表單有兩個(gè)字段,分別是foo和baz,其中foo字段的值等于bar,baz字段的值一個(gè)分為兩行的字符串。上面四種方法,都可以將這個(gè)表單發(fā)送到服務(wù)器。
第一種方法是默認(rèn)方法,POST發(fā)送,Encoding type為application/x-www-form-urlencoded。
Content-Type: application/x-www-form-urlencoded
foo=bar&baz=The+first+line.%0D%0AThe+second+line.%0D%0A
第二種方法是POST發(fā)送,Encoding type為text/plain。
Content-Type: text/plain
foo=bar
baz=The first line.
The second line.
第三種方法是POST發(fā)送,Encoding type為multipart/form-data。
Content-Type: multipart/form-data; boundary=---------------------------314911788813839
-----------------------------314911788813839
Content-Disposition: form-data; name="foo"
bar
-----------------------------314911788813839
Content-Disposition: form-data; name="baz"
The first line.
The second line.
-----------------------------314911788813839--
第四種方法是GET請求。
?foo=bar&baz=The%20first%20line.%0AThe%20second%20line.
通常,我們使用file控件實(shí)現(xiàn)文件上傳。
<form id="file-form" action="handler.php" method="POST">
<input type="file" id="file-select" name="photos[]" multiple/>
<button type="submit" id="upload-button">上傳</button>
</form>
上面HTML代碼中,file控件的multiple屬性,指定可以一次選擇多個(gè)文件;如果沒有這個(gè)屬性,則一次只能選擇一個(gè)文件。
file對象的files屬性,返回一個(gè)FileList對象,包含了用戶選中的文件。
var fileSelect = document.getElementById('file-select');
var files = fileSelect.files;
然后,新建一個(gè)FormData對象的實(shí)例,用來模擬發(fā)送到服務(wù)器的表單數(shù)據(jù),把選中的文件添加到這個(gè)對象上面。
var formData = new FormData();
for (var i = 0; i < files.length; i++) {
var file = files[i];
if (!file.type.match('image.*')) {
continue;
}
formData.append('photos[]', file, file.name);
}
上面代碼中的FormData對象的append方法,除了可以添加文件,還可以添加二進(jìn)制對象(Blob)或者字符串。
// FilesformData.append(name, file, filename);// BlobsformData.append(name, blob, filename);// StringsformData.append(name, value);
append方法的第一個(gè)參數(shù)是表單的控件名,第二個(gè)參數(shù)是實(shí)際的值,第三個(gè)參數(shù)是可選的,通常是文件名。
最后,使用Ajax方法向服務(wù)器上傳文件。
var xhr = new XMLHttpRequest();xhr.open('POST', 'handler.php', true);xhr.onload = function () { if (xhr.status !== 200) { alert('An error occurred!'); }};xhr.send(formData);
目前,各大瀏覽器(包括IE 10)都支持Ajax上傳文件。
除了使用FormData接口上傳,也可以直接使用File API上傳。
var file = document.getElementById('test-input').files[0];
var xhr = new XMLHttpRequest();
xhr.open('POST', 'myserver/uploads');
xhr.setRequestHeader('Content-Type', file.type);
xhr.send(file);
可以看到,上面這種寫法比FormData的寫法,要簡單很多。
參考鏈接
MDN, Using XMLHttpRequest
Mathias Bynens, Loading JSON-formatted data with Ajax and xhr.responseType=’json’
Eric Bidelman, New Tricks in XMLHttpRequest2
Matt West, Uploading Files with AJAX
Matt Gaunt, Introduction to fetch()
Nikhil Marathe, This API is so Fetching!
Ludovico Fischer, Introduction to the Fetch API