var xhr = new XMLHttpRequest();
if(window.XMLHttpRequest === undefined){
window.XMLHttpRequest = function(){
try{
//如果可用,則使用ActiveX對象的最新版本
return new ActiveXObject('Msxml2.XMLHTTP.6.0');
}catch(e1){
try{
//否則,回退到較舊的版本
return new ActiveXObject('Msxml2.XMLHTTP.3.0');
}catch(e2){
throw new Error('XMLHttpRequest is not supported');
}
}
}
}
xhr.open('GET','example.php');
xhr.setRequestHeader('Content-Type','text/plain');
xhr.send(null);
xhr.onreadystatechange=function(){}
xhr.open('GET','example.php',false);
function get(url,callback){
var xhr = new XMLHttpRequest(); //創(chuàng)建新請求
xhr.open('GET',url);
xhr.onreadystatechange=function(){
//如果請求完成且成功
if(xhr.readyState === 4 && xhr.status === 200){
//獲得響應(yīng)的類型
var type = xhr.getResponseHeader('Content-type');
if(type.indexOf('xml') !== -1 && xhr.responseXML){
callback(xhr.responseXML); //Document對象響應(yīng)
}else if(type === 'application/json'){
callback(JSON.parse(xhr.responseText)); //JSON響應(yīng)
}else{
callback(xhr.responseText); //字符串響應(yīng)
}
}
};
xhr.send(null); //立即發(fā)送請求
}
user=TG&age=18;
application/x-www-form-urlencoded
xhr.setRequestHeader('Content-Type','application/x-www-form-urlencoded');
/*
* {
* name:'TG',
* age:18
* }
*/
function encodeFormData(data){
if(!data) return '';
var pairs = [];
for(var name in data){
if(!data.hasOwnProperty(name)) continue;
if(typeof data[name] === 'function') continue;
var value = data[name].toString();
name = encodeURIComponent(name.replace('%20','+'));
value = encodeURIComponent(value.replace('%20','+'));
pairs.push(name + '=' + value);
}
return pairs.join('&');
}
xhr.setRequestHeader('Content-Type','application/json');
xhr.send(JSON.stringify(data));
0,對應(yīng)常量UNSENT,表示XMLHttpRequest實(shí)例已經(jīng)生成,但是open()方法還沒有被調(diào)用。
1,對應(yīng)常量OPENED,表示open()已調(diào)用,但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)失敗了。
200, OK,訪問正常
301, Moved Permanently,永久移動
302, Move temporarily,暫時(shí)移動
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ā)生錯誤
if(xhr.readyState === 4){
//請求完成
if(xhr.status === 200){
//請求成功
}
}
(3)statusText
statusText屬性為只讀屬性,返回一個(gè)字符串,表示服務(wù)器發(fā)送的狀態(tài)提示。不同于status屬性,該屬性包含整個(gè)狀態(tài)信息,比如”200 OK“。
(4)timeout
timeout屬性等于一個(gè)整數(shù),表示多少毫秒后,如果請求仍然沒有得到結(jié)果,就會自動終止。如果該屬性等于0,就表示沒有時(shí)間限制。
對應(yīng)超時(shí),還有個(gè)監(jiān)聽函數(shù):
xhr.ontimeout = function(){
//請求超時(shí)
}
(5)response
response屬性為只讀,返回接收到的數(shù)據(jù)體(即body部分)。它的類型可以是ArrayBuffer、Blob、Document、JSON對象、或者一個(gè)字符串,這由XMLHttpRequest.responseType屬性的值決定。
如果本次請求沒有成功或者數(shù)據(jù)不完整,該屬性就會等于null。
(6)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ù),比如圖片文件。
(7)responseText
responseText屬性返回從服務(wù)器接收到的字符串,該屬性為只讀。如果本次請求沒有成功或者數(shù)據(jù)不完整,該屬性就會等于null。
如果服務(wù)器返回的數(shù)據(jù)格式是JSON,就可以使用responseText屬性。
var data = xhr.responseText;
data = JSON.parse(data);
(8)responseXML
responseXML屬性返回從服務(wù)器接收到的Document對象,該屬性為只讀。如果本次請求沒有成功,或者數(shù)據(jù)不完整,或者不能被解析為XML或HTML,該屬性等于null。
返回的數(shù)據(jù)會被直接解析為DOM對象。
(9)事件監(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 請求完成,不管成果或失敗
1.6.XMLHttpRequest實(shí)例的方法
(1)abort()
abort方法用來終止已經(jīng)發(fā)出的HTTP請求。
(2)getAllResponseHeaders()
getAllResponseHeaders方法返回服務(wù)器發(fā)來的所有HTTP頭信息。格式為字符串,每個(gè)頭信息之間使用CRLF分隔,如果沒有受到服務(wù)器回應(yīng),該屬性返回null。
(3)getResponseHeader()
getResponseHeader方法返回HTTP頭信息指定字段的值,如果還沒有收到服務(wù)器回應(yīng)或者指定字段不存在,則該屬性為null。
如果有多個(gè)字段同名,則它們的值會被連接為一個(gè)字符串,每個(gè)字段之間使用“逗號+空格”分隔。
(4)open()
XMLHttpRequest對象的open方法用于指定發(fā)送HTTP請求的參數(shù),它的使用格式如下,一共可以接受五個(gè)參數(shù)。
void open( string method, string url, optional boolean async, optional string user, optional string password);
參數(shù)說明:
method:表示HTTP動詞,比如“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)為空字符串。
(5)send()
send方法用于實(shí)際發(fā)出HTTP請求。如果不帶參數(shù),就表示HTTP請求只包含頭信息,也就是只有一個(gè)URL,典型例子就是GET請求;如果帶有參數(shù),就表示除了頭信息,還帶有包含具體數(shù)據(jù)的信息體,典型例子就是POST請求。
(6)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ù)。
(6)overrideMimeType()
該方法用來指定服務(wù)器返回?cái)?shù)據(jù)的MIME類型。該方法必須在send()之前調(diào)用。// 強(qiáng)制將MIME改為文本類型
xhr.overrideMimeType('text/plain; charset=x-user-defined');
在XMLHttpRequest版本升級后,一般采用指定的responseType的方法。
xhr.responseType = 'text';
<form id="form" action="upload.php" method="POST">
<input type="file" id="files" name="photos[]"/>
<button type="submit" id="upload">上傳</button>
</form>
<input type="file" multiple/>
var fileInput = document.getElementById('files');
var files = fileInput.files;
fileInput.addEventListener('change',function(){
var file = this.files[0];
if(!file) return;
var xhr = new XMLHttpRequest();
xhr.open('POST','upload.php');
xhr.send(file);
},false);
xhr.setRequestHeader('Content-Type', file.type);
var formdata = new FormData();
for(var i = 0;i < files.length; i++){
var file = files[i];
formdata.append('photos[]',file,file.name);
}
xhr.send(formdata);
// Files
formdata.append(name, file, filename);
// Blobs
formdata.append(name, blob, filename);
// Strings
formdata.append(name, value);
3、HTTP進(jìn)度事件
除了使用readystatechange事件來探測HTTP請求的完成外,在XHR2中,還定義了多個(gè)有用的事件。
abort事件:當(dāng)進(jìn)度事件被中止時(shí)觸發(fā)。如果發(fā)生錯誤,導(dǎo)致進(jìn)程中止,不會觸發(fā)該事件。
error事件:由于錯誤導(dǎo)致資源無法加載時(shí)觸發(fā)。
load事件:進(jìn)度成功結(jié)束時(shí)觸發(fā)。
loadstart事件:進(jìn)度開始時(shí)觸發(fā)。
loadend事件:進(jìn)度停止時(shí)觸發(fā),發(fā)生順序排在error事件\abort事件\load事件后面。
progress事件:當(dāng)操作處于進(jìn)度之中,由傳輸?shù)臄?shù)據(jù)塊不斷觸發(fā)。
timeout事件:進(jìn)度超過限時(shí)觸發(fā)。
當(dāng)調(diào)用send()時(shí),觸發(fā)單個(gè)loadstart事件。當(dāng)正在加載服務(wù)器的響應(yīng)時(shí),XMLHttpRequest對象會發(fā)現(xiàn)progress事件,通常每隔50毫秒左右,可以使用這些事件給用戶反饋請求的進(jìn)度。當(dāng)事件完成,會觸發(fā)load事件。
HTTP請求無法完成有3種情況:
注意:對于任何具體請求,瀏覽器將只會觸發(fā)load、abort、timeout和error事件中的一個(gè)。一旦這些事件中的一個(gè)發(fā)生后,瀏覽器應(yīng)該觸發(fā) loadend 事件。
要使用這些事件,有兩種方式:
xhr.onload=function(){}
xhr.addEventListener('load',function(){})
3.1 progress事件
因?yàn)檫@些事件是XHR2中才定義的,所以有時(shí)需要檢查瀏覽器是否支持progress事件:
if('onprogress' in (new XMLHttpRequest())){
//支持progress事件
}
除了像type和timestamp這樣的常用Event對象屬性外,與progress事件相關(guān)聯(lián)的事件對象有3個(gè)有用的屬性:
lengthComputable:返回一個(gè)布爾值,表示當(dāng)前進(jìn)度是否具有可計(jì)算的長度。如果為false,就表示當(dāng)前進(jìn)度無法測量。
total:返回一個(gè)數(shù)值,表示當(dāng)前進(jìn)度的總長度。如果是通過HTTP下載某個(gè)資源,表示內(nèi)容本身的長度,不含HTTP頭部的長度。如果lengthComputable屬性為false,則total屬性就無法取得正確的值。
loaded:返回一個(gè)數(shù)值,表示當(dāng)前進(jìn)度已經(jīng)完成的數(shù)量。該屬性除以total屬性,就可以得到目前進(jìn)度的百分比。
我們可以利用total和loaded屬性來獲取當(dāng)前進(jìn)度:
xhr.onprogress = function(e){
if(e.lengthComputable){
var percentComplete = e.loaded / e.total;
}
}
3.2 上傳進(jìn)度事件
在XHR2中,也提供了用于監(jiān)控HTTP請求上傳的事件。在實(shí)現(xiàn)這些特性的瀏覽器中,XMLHttpRequest對象有一個(gè)upload屬性,upload屬性值是一個(gè)對象,它定義了addEventListener()方法和整個(gè)progress事件集合,比如onprogress和onload。
xhr.upload.onprogress = function(e){
var percentComplete = e.loaded / e.total;
}
3.3 中止請求和超時(shí)
我們可以通過調(diào)用XMLHttpRequest對象的abort()方法來取消正在進(jìn)行的HTTP請求,調(diào)用abort()方法時(shí),會觸發(fā)abort事件。
在XHR2中,還定義了timeout屬性來指定自動中止后的毫秒數(shù)。
xhr.timeout = 1000;
4、同源策略
同源策略是對JavaScript代碼能夠操作哪些Web內(nèi)容的一條完整的安全限制。當(dāng)Web頁面使用多個(gè)<iframe>元素或打開其他瀏覽器窗口的時(shí)候,這一策略通常就會發(fā)揮作用。
所謂“同源”指的是”三個(gè)相同“。
從不同Web服務(wù)器載入的文檔具有不同的來源。通過同一主機(jī)的不同端口載入的文檔具有不同的來源。使用http:協(xié)議載入的文檔和使用https:協(xié)議載入的文檔具有不同的來源,即使它們來自同一個(gè)服務(wù)器。
同源政策的目的,是為了保證用戶信息的安全,防止惡意的網(wǎng)站竊取數(shù)據(jù)。
4.1 不嚴(yán)格的同源策略
有三種不嚴(yán)格的同源策略
(1)使用Document對象的domain屬性
在默認(rèn)情況下,屬性domain存放的是載入文檔的服務(wù)器的主機(jī)名。
比如:來自home.example.com的文檔里的腳本要合法的讀取developer.example.com載入的文檔的屬性(默認(rèn)情況下是不允許的,會受到同源策略的限制)。
document.domain = 'example.com';
一旦上面兩個(gè)文檔里包含的腳本把domain都設(shè)置成了上面相同的值,那么這兩個(gè)文檔就不會受同源策略的約束了,可以相互讀取對方的屬性。
(2)跨域資源共享(Cross-Origin Resource Sharing)
這種方式是應(yīng)用到后臺中:
Access-Control-Allow-Origin:*,//允許所有域名的腳本訪問該資源。
Access-Control-Allow-Origin:http://www.example.com //允許特定的域名訪問。
(3)跨文檔消息(cross-document messaging)
跨文檔消息允許來自一個(gè)文檔的腳本可以傳遞文本消息到另一個(gè)文檔里的腳本。
4.2 跨站腳本
跨站腳本(Cross-site scripting,XSS),這個(gè)術(shù)語用來表示一類安全問題,也就是攻擊者向目標(biāo)Web站點(diǎn)注入HTML標(biāo)簽或腳本。
通常,防止XSS攻擊的方式是,在使用任何不可信的數(shù)據(jù)來動態(tài)的創(chuàng)建文檔內(nèi)容之前,從中移除HTML標(biāo)簽。
5、Fetch API
Fetch API是一種新規(guī)范,用來取代XMLHttpRequest對象。
5.1 特性檢測
if(self.fetch){
//支持
}else{
//不支持
}
在Fetch API中,最常用的就是fetch()函數(shù),它接收一個(gè)URL參數(shù)(也可以是request對象),返回一個(gè)promise來處理response。response參數(shù)還帶著一個(gè)Response對象。
fetch(url).then(function(response){
console.log(response);
});
fetch方法還可以設(shè)置第二個(gè)參數(shù),用來配置其他值,可選的參數(shù)有:
method: 請求使用的方法,如 GET、POST。
headers: 請求的頭信息,形式為 Headers 對象或 ByteString。
body: 請求的 body 信息:可能是一個(gè) Blob、BufferSource、FormData、URLSearchPara ms 或者 USVString 對象。注意 GET 或 HEAD 方法的請求不能包含 body 信息。
mode: 請求的模式,如 cors、 no-cors 或者 same-origin。
credentials: 請求的 credentials,如 omit、same-origin 或者 include。
cache: 請求的 cache 模式: default, no-store, reload, no-cache, force-cache, or only-if-cached.
下面是發(fā)出POST請求
fetch(url,{
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: 'name=TG&love=1'
}).then(function(response){})
注意:fetch() 方法的參數(shù)與 Request() 構(gòu)造器是一樣的。
fetch(input, init).then(function(response) { ... });
var myRequest = new Request(input, init);
下面是一個(gè)Fetch API完整請求的簡單例子:
fetch(url).then(function (response) {
return response.json();
}).then(function (jsonData) {
console.log(jsonData);
}).catch(function () {
console.log('出錯了');
});
上面代碼向指定的URL發(fā)出請求,得到回應(yīng)后,將其轉(zhuǎn)為JSON格式,輸出到控制臺。如果出錯,則輸出一條提示信息。
Fetch API引入三個(gè)新的對象(也是構(gòu)造函數(shù)):Headers, Request和Response。
5.2 Headers
Headers對象用來構(gòu)造/讀取HTTP數(shù)據(jù)包的頭信息。
reqHeaders = new Headers({
"Content-Type": "text/plain",
"Content-Length": content.length.toString(),
"X-Custom-Header": "ProcessThisImmediately",
});
我們還可以使用append方法:
var content = 'Hello World';
var headers = new Headers();
headers.append("Accept", "application/json");
headers.append("Content-Type", "text/plain");
headers.append("Content-Length", content.length.toString());
headers.append("X-Custom-Header", "ProcessThisImmediately");
Headers對象實(shí)例還提供了一些方法:
reqHeaders.has("Content-Type") // true
reqHeaders.has("Set-Cookie") // false
reqHeaders.set("Content-Type", "text/html")
reqHeaders.append("X-Custom-Header", "AnotherValue")
reqHeaders.get("Content-Length") // 11
reqHeaders.getAll("X-Custom-Header") // ["ProcessThisImmediately", "AnotherValue"]
reqHeaders.delete("X-Custom-Header")
reqHeaders.getAll("X-Custom-Header") // []
生成Header實(shí)例以后,可以將它作為第二個(gè)參數(shù),傳入Request方法。
var headers = new Headers();
headers.append('Accept', 'application/json');
var request = new Request(URL, {headers: headers});
fetch(request).then(function(response) {
console.log(response.headers);
});
5.3 Request對象
Request對象用來構(gòu)造HTTP請求。
var req = new Request("/index.html");
req.method // "GET"
req.url // "http://example.com/index.html"
Request對象的第二個(gè)參數(shù),表示配置對象,
var uploadReq = new Request("/uploadImage", {
method: "POST",
headers: {
"Content-Type": "image/png",
},
body: "image data"
});
Request對象實(shí)例的mode屬性,用來設(shè)置是否跨域,合法的值有以下三種:same-origin、no-cors(默認(rèn)值)、cors。當(dāng)設(shè)置為same-origin時(shí),只能向同域的URL發(fā)出請求,否則會報(bào)錯。
5.4 Response對象
Fetch API 的Response接口呈現(xiàn)了對一次請求的響應(yīng)數(shù)據(jù)
5.4.1 屬性
(1)ok
如果ok屬性返回的狀態(tài)碼在200到299之間(即請求成功),這個(gè)屬性為true,否則為false。因此,我們可以這樣判斷請求是否成功:
fetch(url).then(function(response){
if(response.ok){
//請求成功
}else{
//請求失敗
}
});
(2)status、statusText
status屬性返回HTTP的狀態(tài)碼;statusText返回一個(gè)字符串,表示服務(wù)器發(fā)送的狀態(tài)提示。比如通信成功時(shí),status是200,而statusText是“OK”
(3)url
返回完整的請求地址
(4)type
type屬性表示HTTP回應(yīng)的類型。合法的值有五個(gè)basic、cors、default、error、opaque。basic表示正常的同域請求;cors表示CORS機(jī)制的跨域請求;error表示網(wǎng)絡(luò)出錯,無法取得信息,status屬性為0,
如果需要在CORS機(jī)制下發(fā)出跨域請求,需要指明狀態(tài)。
fetch(url,{mode: 'cors'}).then(function(response){})
(5)headers
Headers對象,表示HTTP回應(yīng)的頭信息
(6)body
表示請求的內(nèi)容。
Request對象和Response對象都有body屬性,表示請求的內(nèi)容。body屬性可能是以下的數(shù)據(jù)類型。
ArrayBuffer
ArrayBufferView (Uint8Array等)
Blob/File
string
URLSearchParams
FormData
注意:上面這些方法都只能使用一次,第二次使用就會報(bào)錯,也就是說,body屬性只能讀取一次。Request對象和Response對象都有bodyUsed屬性,返回一個(gè)布爾值,表示body是否被讀取過。
如果希望多次使用body屬性,可以使用Response對象和Request對象的clone方法。它必須在body還沒有讀取前調(diào)用,返回一個(gè)新的body,也就是說,需要使用幾次body,就要調(diào)用幾次clone方法。
response.clone()
(7)bodyUsed
bodyUsed屬性,返回一個(gè)布爾值,表示body是否被讀取過。
5.4.2 Response對象的方法
(1)text()、json()、FormData()、blob()、arrayBuffer()
在Fetch API中,數(shù)據(jù)傳送是以數(shù)據(jù)流(stream)的形式進(jìn)行的。對于大文件,數(shù)據(jù)是一段一段得到的。
而Fetch API提供了五個(gè)數(shù)據(jù)流讀取器。
text():返回字符串
json():返回一個(gè)JSON對象
formData():返回一個(gè)FormData對象
blob():返回一個(gè)blob對象
arrayBuffer():返回一個(gè)二進(jìn)制數(shù)組
簡單例子:
response.json().then(function(json){
console.log(json);
})
參考資料:
書籍:《JavaScript權(quán)威指南》
文章:阮一峰:Fetch API
文檔:MDN:Fetch API
更多建議: