1. 程式人生 > 實用技巧 >XMLHttpRequest 和 AJAX 2

XMLHttpRequest 和 AJAX 2

對於Ajax,肯定很多小夥伴都聽過甚至用過了,那麼沒聽過的也不用著急,本文會對Ajax進行講解,其次,一定還有一些人只用過JQuery封裝好了的Ajax卻對原生的Ajax並不瞭解,那麼也不用著急,本文從最基本的Ajax開始講起,然後最後會盡可能得模仿JQuery對其進行封裝,讓我剛才提到的兩類人能對Ajax有進一步的瞭解。

一、什麼是Ajax

Ajax(Asynchronous JavaScript And XML)是2005年新出現的技術,它的出現是為了解決這樣一個場景:整個頁面中,只有一小部分的資料需要進行更新,按照傳統的前後端互動,我們需要向伺服器請求該網頁的所有資料,然後再在客戶端重新渲染,這無疑是非常低效的操作。因此,Ajax就可以做到只向伺服器請求我們想要的那一小部分資料,而不用請求全部資料,進而在重新整理整個頁面的前提下更新那部分的資料。

舉個例子,我們去飯店吃飯,然後點了一桌子菜,後來發現其中有一道菜太鹹了,因此我們只需要讓服務員端回去給廚師重新做這一道菜再拿回來就行了。

在這個例子中的人、物對比Ajax的關係如下表:

吃飯事件資料更新
我們 客戶端
菜品 頁面所有的資料
服務員 ajax物件
廚師 伺服器

當我們發現有一道菜太鹹了,不需要讓廚師把所有的菜重新做一遍,只要讓服務員拿這一道菜回去給廚師重做這一操作就相當於讓ajax物件向後端請求那一小部分資料再拿回來更新頁面而無需重新整理整個頁面。

二、Ajax的優缺點

瞭解了Ajax的作用和定義,我們再來看看它的優缺點

(1)優點

  1. 瀏覽器預設支援(一般瀏覽器都是支援JavaScript的)
  2. 提高使用者體驗(不需要重新整理整個頁面,而只需要區域性重新整理)
  3. 提高頁面的效能(只需要請求部分資料,所以資料量就明顯下降了)

(2)缺點

  1. 破壞了瀏覽器的前進和後退功能(Ajax不會改變網頁URL,因此不會在瀏覽器記錄前後頁面)
  2. 對搜尋引擎的支援較弱(搜尋引擎無法監測到JS引起的資料變化)

三、Ajax的使用

Ajax的基本流程:建立XHR物件 => 傳送資料 => 接收資料

(1)狀態碼

既然Ajax涉及到前後端的資料互動,那麼我們就先來簡單的看一下幾種型別的狀態碼,如下表:

狀態碼含義
100 ~ 199 連線繼續
200 ~ 299 各種成功的請求
300 ~ 399 重定向
400 ~ 499 客戶端錯誤
500 ~ 599 服務端錯誤

(2)xhr的基本使用

在使用xhr之前,我們要建立一個xhr的例項物件

letxhr=newXMLHttpRequest()

然後再呼叫xhr物件上的open()方法,表示建立一個請求。

open()方法接收三個引數:

  • 第一個引數:請求的型別(例如get 、post)
  • 第二個引數:請求的URL
  • 第三個引數:是否非同步傳送請求(預設為true)
//建立了一個Ajax請求
xhr.open('get','example.php','true')

光呼叫了open()方法還不夠,它只是建立了一個請求,但還沒有傳送請求,因此我們還要呼叫xhr物件上的另一個方法,即send()方法,表示將請求傳送給目標URL

send()方法接收一個引數:

  • 第一個引數:作為請求主體傳送的資料(例如post請求攜帶的資料)
//我們上面建立的是get請求,因此send()方法無需傳參
xhr.send()

請求傳送出去後,客戶端需要接收伺服器響應回來的資料,xhr物件中有一些屬性,它們儲存著服務端返回來的一些資料資訊,如下表所示

屬性名含義
responseText 服務端返回的文字資訊
responseXML 服務端返回的XML DOM文件
status HTTP狀態碼
statusText HTTP狀態碼說明
readyState xhr物件的請求響應階段

既然我們要獲取服務端返回的資料,我們就要知道服務端是何時返回資料的,這就可以通過上面表格中的readyState屬性來判斷了

readyState屬性一共有5個值,分別表示不同的請求響應階段:

  • 0:還未建立請求,即未呼叫open()方法
  • 1:已呼叫open()方法,但未傳送send()方法
  • 2:已呼叫send()方法,但未接收到響應
  • 3:已接收到部分響應
  • 4:已接收到全部的響應

同時,xhr物件可以繫結一個readystatechange事件,每當readyState屬性發生改變,都會觸發該事件,因此,該事件在一次請求中會被多次觸發

xhr.onreadystatechange=function(){
console.log('readyState屬性發生改變了')
}

所以,我們可以在readystatechange事件中判斷一下readyState屬性是否為4,即是否已經接收所有的響應,然後還可以再繼續判斷一下status屬性,看看狀態碼是否為200,當上述都成立了,我們再去responseText屬性 或responseXML屬性中獲取響應資料

xhr.onreadystatechange=function(){
//判斷是否已接收所有響應
if(xhr.readyState===4){
//判斷狀態碼是否為200
if(xhr.status===200){
console.log(xhr.responseText)
}
}
}

(3)傳送get請求

上面也講解了Ajax請求的簡單應用,同時也是拿get請求來舉得例子,因此這裡我就不多做說明,唯一要講的就是,get請求所攜帶的資料是明文的,大小隻有4k左右,而且它是寫在URL的?後面的,例如這樣example.php?query=4&em=0,所以若是我們要在傳送get請求時攜帶資料,只需要在呼叫open()方法時,將資料寫在第二個引數的URL的?後面即可

直接來寫一次完整的get請求,程式碼如下:

letxhr=newXMLHttpRequest()
xhr.open('get','example.php?query=4&em=0')
xhr.send()
xhr.onreadystatechange=function(){
if(xhr.readyState===4){
if(xhr.status===200){
console.log(xhr.responseText);
}
}
}

(4)傳送post請求

傳送post請求的過程幾乎和get請求一樣,唯一不一樣的是資料的傳遞。大家都知道post請求的資料是放在請求體中的,因此我們需要呼叫xhr物件上的setRequestHeader()方法來模仿表單提交時的內容型別

該方法傳入的引數比較固定,程式碼如下

xhr.setRequestHeader('Content-Type','application/x-www-form-urlencoded')

然後我們上面也說過,send()方法接收的一個引數是請求主體傳送的資料,所以我們的post請求要傳送的資料就要作為該方法的引數,程式碼如下:

xhr.send('query=4&em=0')

那我們來看一次完整的post請求是怎麼樣的吧,程式碼如下:

letxhr=newXMLHttpRequest()
xhr.open('post','example.php')
xhr.setRequestHeader('Content-Type','application/x-www-form-urlencoded')
xhr.send('query=4&em=0')
xhr.onreadystatechange=function(){
if(xhr.readyState===4){
if(xhr.status===200){
console.log(xhr.responseText);
}
}
}

四、封裝Ajax

文章開頭提到,JQuery早已對Ajax請求進行了成熟的封裝,所以我們可以借鑑它,甚至儘可能地去模仿它進行封裝,在這之前,我們得先了解JQuery中Ajax的使用

(1)JQuery中的Ajax

這裡我找來了幾段使用JQuery傳送Ajax請求的程式碼,如下所示:

  • 傳送get請求
$.get('example.php',{query:4,em:0},function(data,status,xhr){
console.log(`
返回的資料為${data}
返回的狀態為${status}
返回xhr物件為${xhr}
`)
},'json')

這段程式碼傳送了一個get請求,攜帶的引數有query值為4em值為0,規定返回的資料型別為json,同時設定了一個回撥函式用於接收請求返回的資料、狀態和xhr物件

  • 傳送post請求
$.post('example.php',{query:4,em:0},function(data,status,xhr){
console.log(`
返回的資料為${data}
返回的狀態為${status}
返回xhr物件為${xhr}
`)
},'json')

這段程式碼傳送了一個post請求,攜帶的引數有query值為4em值為0,規定返回的資料型別為json,同時設定了一個回撥函式用於接收請求返回的資料、狀態和xhr物件

  • 綜合方法
//該方法既可以傳送get請求又可以傳送post請求
$.ajax({
url:'example.php',//請求的URL
type:'get',//請求型別,若為post,則表示傳送post請求
data:{query:4,em:0},//請求攜帶資料
dataType:'json',//接收的資料型別
isAsync:true//是否非同步請求
})
.then(data=>{
console.log(`請求成功,資料為${data}`)
})
.catch(err=>{
console.log(`請求失敗,狀態為${err}`)
})

其呼叫的是一個綜合的方法,傳入的引數是一個物件,物件中傳入多個引數。這段程式碼是傳送了一個get請求,地址為example.php,攜帶的引數有query值為4em值為0,所接收返回資料的型別為json,請求為非同步請求

特別的是,該方法的回撥函式是通過promise實現的,即該方法返回一個promise物件,在then函式中處理請求成功的情況,在catch函式中處理請求失敗的情況

若沒有了解過promise的小夥伴建議先花幾分鐘瞭解一下,因為這是非同步程式設計最常用的一個語法,下面放上文章連結——深入瞭解Promise物件,寫出優雅的回撥程式碼,告別回撥地獄

接下來我們就針對上述給出的例子,逐個封裝

(2)封裝準備工作

因為XMLHttpRequest物件有一定的相容性,因此我們在封裝ajax方法之前可以先封裝一個方法用來動態建立一個相容性稍微好點的XHR物件(其中主要是相容IE5和IE6)

我們都知道JQuery都是將方法封裝在一個名為$的物件中的,我們也這麼做

let$={
createXHR:function(){
//若瀏覽器支援,則建立XMLHttpRequest物件
if(window.XMLHttpRequest){
returnnewXMLHttpRequest()
}
//若不支援,則建立ActiveXobject物件
else{
returnnewActiveXObject()
}
}
}

(3)封裝$.get方法

首先查閱JQuery的get方法可知,其接收四個引數:URLdatacallbackdataType,分別表示請求的url地址、攜帶的引數、成功回撥函式、返回資料的型別

let$={
//動態生成XHR物件的方法
createXHR:function(){
if(window.XMLHttpRequest){
returnnewXMLHttpRequest()
}else{
returnnewActiveXObject()
}
},
get:function(url,data,callback,dataType){
//避免dataType大小寫的問題
letdataType=dataType.toLowerCase()
//如果有傳入data,則在url後面跟上引數
if(data){
url+='?'
Object.keys(data).forEach(key=>url+=`${key}=${data[key]}&`)
url=url.slice(0,-1)
}
//呼叫我們封裝的方法生成XHR物件
letxhr=this.createXHR()
//建立get請求
xhr.open('get',url)
//傳送請求
xhr.send()
xhr.onreadystatechange=function(){
if(xhr.readyState===4){
if(xhr.status>=200&&xhr.status<300||xhr.status==304){
//若dataType為json,則將返回的資料通過JSON.parse格式化
letres=dataType==='json'?JSON.parse(xhr.responseText):xhr.responseText
//呼叫回撥函式,並把引數傳進去
callback(res,xhr.status,xhr)
}
}
}
},
}

(4)封裝$.post方法

JQuery的post方法傳入的引數跟get方法一樣,只不過其內部的實現有略微的區別,就是攜帶引數的傳送不一樣,所以直接來看程式碼吧

let$={
//動態生成XHR物件的方法
createXHR:function(){
if(window.XMLHttpRequest){
returnnewXMLHttpRequest()
}else{
returnnewActiveXObject()
}
},
post:function(url,data,callback,dataType){
//避免dataType大小寫的問題
letdataType=dataType.toLowerCase()
//呼叫我們封裝的方法動態生成XHR物件
letxhr=this.createXHR()

letstr=''
//若傳入引數,則將引數序列化
if(data){
Object.keys(data).forEach(key=>str+=`${key}=${data[key]}&`)
str=str.slice(0,-1)
}
//設定頭部資訊
xhr.setRequestHeader('Content-Type','application/x-www-form-urlencoded')
//傳送請求,並攜帶引數
xhr.send(str)
xhr.onreadystatechange=function(){
if(xhr.readyState===4){
if(xhr.status>=200&&xhr.status<300||xhr.status==304){
//若dataType為json,則將返回的資料通過JSON.parse格式化
letres=dataType==='json'?JSON.parse(xhr.responseText):xhr.responseText
//呼叫回撥函式,把對應引數傳進去
callback(res,xhr.status,xhr)
}
}
}
}
}

(5)封裝$.ajax方法

在JQuery中還有一個ajax方法,其既可以傳送get請求,也可以傳送post請求,該方法可傳入多種引數,且支援promise處理回撥函式

let$={
createXHR:function(){
if(window.XMLHttpRequest){
returnnewXMLHttpRequest()
}else{
returnnewActiveXObject()
}
},
ajax:function(params){
//初始化引數
lettype=params.type?params.type.toLowerCase():'get'
letisAsync=params.isAsync?params.isAsync:'true'
leturl=params.url
letdata=params.data?params.data:{}
letdataType=params.dataType.toLowerCase()
//用我們封裝的方法動態生成XHR物件
letxhr=this.createXHR()

letstr=''

//拼接字串
Object.keys(data).forEach(key=>str+=`${key}=${data[key]}&`)
str=str.slice(0,-1)
//如果是get請求就把攜帶引數拼接到url後面
if(type==='get')url+=`?${str}`;
//返回promise物件,便於外部then和catch函式呼叫
returnnewPromise((resolve,reject)=>{
//建立請求
xhr.open(type,url,isAsync)

if(type==='post'){
xhr.setRequestHeader('Content-Type','application/x-www-form-rulencoded')
xhr.send(str)
}else{
xhr.send()
}

xhr.onreadystatechange=function(){
if(xhr.readyState===4){
if(xhr.status>=200&&xhr.status<300||xhr.status==304){
letres=dataType==='json'?JSON.parse(xhr.responseText):xhr.responseText
resolve(res)//請求成功,返回資料
}else{
reject(xhr.status)//請求失敗,返回狀態碼
}
}
}
})
}
}

五、Ajax的約束

預設情況下,Ajax一般只能向同源的域傳送請求,這是受到了瀏覽器的同源策略的限制,關於同源策略,你們可以去看一下我以前寫過的一篇部落格,裡面寫了同源策略的定義以及解決方案——前端人員都懂的瀏覽器的同源策略,以及如何進行不同源間的相互訪問

瞭解過同源策略以後,我們來看看如何讓Ajax不受同源策略的限制而成功傳送請求。CORS(跨域資源共享)要求我們在傳送請求時自定義一個HTTP頭部與伺服器進行溝通,我們只需要設定一個名為Origin的頭部,值為當前頁面的源資訊(協議、域名、埠),例如Origin : http://example.com;然後伺服器需要設定一個名為Access-Control-Allow-Origin的響應頭部,其值為允許跨域訪問的源資訊,若伺服器設定的Access-Control-Allow-Origin與我們設定的Origin相同,則表示伺服器允許我們跨域請求其資源,或者伺服器可以將Access-Control-Allow-Origin值設為*,此時表示允許任何域向其傳送請求並且不受同源策略的限制。

現在的大部分瀏覽器幾乎都支援了在傳送Ajax請求後,自動向請求頭部添加當前的源資訊

六、結束語