AJAX詳解

客戶端與服務器的鏈接請參考https://juejin.im/post/5d8088c7e51d453b39774443javascript

1.什麼是AJAX?

async javascript and xml : 異步的JS和XML;html

此處的異步指的是:局部刷新(對應的是全局刷新);java

XML : 可擴展的標記語言,用本身定義的標籤來存儲數據的ajax

在很早之前,咱們基於AJAX和服務器進行交互的數據格式通常都是以XML格式爲主,由於它可以清晰的展現出對應的數據和結構層級;編程

但後來隨着JSON數據格式的到來, 它不只比XML更清晰展現數據的結構,並且一樣的數據存儲,JSON更加輕量,也方便解析和相關的操做,因此如今先後端的數據交互都以JSON格式數據爲主;json

// ===XML===
![](https://user-gold-cdn.xitu.io/2019/9/17/16d3de27498ce477?w=1486&h=716&f=png&s=368962)
<?xml version="1.0" encoding="UTF-8"?>
<root> <student> <name>張三</name> <age>25</age> <score> <english>95</english> </score> </student> <student> <name>張三</name> <age>25</age> </student> <student> <name>張三</name> <age>25</age> </student> </root>

//===JSON===
[{
	"name": "張三",
	"age": 25,
	"score": {
		"english": 95
	}
}, {
	"name": "張三",
	"age": 25
}, {
	"name": "張三",
	"age": 25
}]
複製代碼

那咱們接下來先來看看所有刷新和局部刷新後端

1.全局刷新api

2.局部刷新 跨域

3.當代項目開發的整個架構模型瀏覽器

2.AJAX的基礎操做

1.建立AJAX實例

let xhr = new XMLHttpRequest;
// IE低版本瀏覽器中用的是new ActiveXObject(),
能夠看看高程中JS惰性編程思想,關於XHR的兼容處理;
複製代碼

2.打開URL(配置發送請求的信息)

* method:  HTTP請求方式
* url: 請求地址(API接口地址)
* async :  設置同步或者異步,默認是TRUE異步,FALSE同步
* user-name: 傳遞給服務器的用戶名;
* user-pass:    傳遞給服務器的密碼;

xhr.open('GET','./json/xxx.json',true); // 默認異步
複製代碼

3.監聽AJAX狀態

// 監聽AJAX的狀態,在狀態爲xxx的時候,獲取服務器響應的內容;
// AJAX狀態碼:0 1 2 3 4
// 當HTTP狀態碼爲2或者3開頭,而且AJAX狀態碼爲4時,獲取響應內容;
xhr.onreadystatechange=function(){
   if(xhr.readyState===4 && /^(2|3)\d{2}$/.test(xhr.status)){
      let result = xhr.responseText; // =>響應內容
   }
}

=> AJAX 狀態碼
`xhr.readyState` 獲取狀態碼
* unsend 0 :未發送(建立一個XHR,初始狀態是0)
* opend  1 :  已經打開(執行了xhr.open);
* headers_received  2  :  響應頭信息 已經返回給客戶端(發送請求後,服務器會一次返回響應頭和響應主體的信息)
* loading  3 :等待服務器返回響應內容
* done  4: 響應主體信息已經返回給客戶端
複製代碼

4.發送請求

send中放的是請求主體的內容;

//SEND中放的是請求主體的內容
xhr.send(null);
複製代碼

AJAX任務:發送一個請求給服務器,從服務器獲取到對應的內容,從send後開始,到xhr.readystate === 4 的時候算任務結束;

HTTP的請求方式

  • GET 系列
    • GET
    • DELETE 通常應用於告訴服務器,從服務器上刪除點東西
    • HEAD 只想獲取響應頭內容,告訴服務器響應主體內容不要
    • OPTIONS 試探性請求,發個請求給服務器,看看服務器能不能接收到,能不能返回
  • POST系列
    • POST
    • PUT 和 DELETE對應,通常只是想讓服務器把我傳遞的信息存儲到服務器上(通常應用於文件和大型數據內容)

=> 真實項目中用對應的請求方式,會使請求變得更加明確(語義化),不遵循這些方式也能夠,最起碼瀏覽器在語法上是容許的;可是這些是開發者們相互約定俗稱的規範

get 和 post 的區別?

GET系列通常用於從服務器獲取信息,POST系列通常用於給服務器推送信息,可是不論GET和POST均可以把信息傳遞給服務器,也能從服務器獲取到結果,只不過是誰多誰少的問題

  • GET:給的少,拿的多
  • POST:給的多,拿的少
  • 客戶端怎麼把信息傳遞給服務器?

    • 問號傳參 xhr.open('GET','/getdata?xxx=xxx&xxx=xxx')
    • 設置請求頭 xhr.setRequestHeader([key],[value])
    • 設置請求主體 xhr.send(請求主體信息)
  • 服務器怎麼把信息返回給客戶端?

    • 經過響應頭
    • 經過響應主體(大部分信息都是基於響應主體返回的)

get 和 post 的本質區別

GET系列傳遞給服務器信息的方式通常採用:問號傳參 POST系列傳遞給服務器信息的方式通常採用:設置請求主體

1.GET傳遞給服務器的內容比POST少,由於URL有最長大小限制(IE通常限制2KB,其他瀏覽器通常限制4-8K,超過長度的部分自動被瀏覽器截取了)

xhr.open('GET','/list?name=lanlan&age=17...');
xhr.send('...') // 請求主體中傳遞的內容理論上沒有大小限制,
// 可是真實項目中,爲了保證傳輸的速度,咱們會本身限制一些
複製代碼

2.GET會產生緩存(緩存不是本身可控制的),由於請求的地址(尤爲是問號傳參的信息同樣),瀏覽器有時候會認爲你要和上次請求的數據同樣,拿的是上一次信息; 這種緩存咱們不指望有,咱們指望的緩存是本身可控的,因此真實項目中,若是一個地址,GET請求屢次,咱們要去除這個緩存

xhr.open('GET','/list?name=lanlan&age=17...');
xhr.open('GET','/list?name=lanlan&age=17...');

**解決辦法設置隨機數或者時間戳**
xhr.open('GET','/list?name=lanlan&age=17...'+Math.random());
xhr.open('GET','/list?name=lanlan&age=17...'+Math.random());
複製代碼

3.GET相比較POST來講不安全: GET是基於問號傳參傳遞給服務器內容,有一種技術叫作URL劫持,這樣別人能夠獲取或者篡改傳遞信息;而POST基於請求主體傳遞信息,不容易被劫持;

彙總XHR的屬性和方法及事件

  • xhr.response / xhr.responseText / xhr.responseXML

  • xhr.status / xhr.statusText

  • xhr.timeout

  • xhr.withCredentials

  • xhr.abort()

  • xhr.getAllResponseHeaders()

  • xhr.getResponseHeader([key])

  • xhr.open()

  • xhr.overrideMimeType()

  • xhr.send()

  • xhr.setRequestHeader()

一些簡單方法的使用;

let xhr = new XMLHttpRequest;
console.log(xhr.readyState); //=>0
xhr.open('GET', 'json/data.json');
// console.log(xhr.readyState); //=>1
xhr.onreadystatechange = function () {
	// console.log(xhr.readyState); //=>2 3 4
	if (!/^(2|3)\d{2}$/.test(xhr.status)) return;
	if (xhr.readyState === 2) {
		//=>獲取響應頭信息
		//獲取的服務器時間是標準的日期格式對象(GMT格林尼治時間)
		//new Date()能把格林尼治時間轉換爲北京時間
		let serverTime = xhr.getResponseHeader('Date');
		// console.log(new Date(serverTime));
	}
	if (xhr.readyState === 4) {
		//=>獲取響應主體信息:咱們通常用responseText,由於服務器返回的信息通常都是JSON格式的字符串,若是返回的是XML格式,咱們用responseXML...
		// xhr.responseXML
		// xhr.response
		// xhr.responseType
		// console.log(xhr.responseText);
	}
}
xhr.send(null);


// ====setTimeout===
let xhr = new XMLHttpRequest;
xhr.timeout = 1; //=>設置AJAX等待時間,超過這個時間算AJAX延遲
xhr.ontimeout = function () {
	console.log('請求超時~~'); //若是超過期間就會輸出;
	xhr.abort(); //=>手動中斷AJAX的請求
}
xhr.withCredentials = true; //=>在跨域請求中是否容許攜帶證書(攜帶COOKIE)
xhr.open('GET', 'json/data.json');
xhr.send();

// 設置請求頭
let xhr = new XMLHttpRequest;
xhr.open('GET', 'json/data.json');
xhr.setRequestHeader('AAA', '藍藍');
// Uncaught TypeError: Failed to execute 'setRequestHeader' on 'XMLHttpRequest': Value is not a valid ByteString. //`設置的請求頭信息值不能是中文 能夠經過encodeURIComponent解碼
xhr.setRequestHeader('AAA', encodeURIComponent('藍藍'));
xhr.onreadystatechange = function () {
	if (!/^(2|3)\d{2}$/.test(xhr.status)) return;
	if (xhr.readyState === 4) {
		console.log(xhr.getRequestHeader);
	}
}
xhr.send(null); 
複製代碼

4.AJAX中的同步異步

//=>AJAX任務的異步
 let xhr = new XMLHttpRequest;
xhr.open('GET', 'json/data.json');
//=>設置事件綁定以前狀態1
xhr.onreadystatechange = function () {
	console.log(xhr.readyState); //=>2 3 4
}
xhr.send(null); 

let xhr = new XMLHttpRequest;
xhr.open('GET', 'json/data.json');
xhr.send(null);
xhr.onreadystatechange = function () {
	console.log(xhr.readyState); //=>2 3 4
}


//=>AJAX的同步
 let xhr = new XMLHttpRequest;
xhr.open('GET', 'json/data.json', false);
xhr.onreadystatechange = function () {
	console.log(xhr.readyState); //=>4 使用AJAX同步編程,不能在狀態碼爲2的時候獲取到響應頭的信息,可是狀態碼爲4的時候也是能夠獲取到頭和主體信息
}
xhr.send(null); 

let xhr = new XMLHttpRequest;
xhr.open('GET', 'json/data.json', false);
xhr.send(null);//=>執行後,只有狀態碼爲4纔會繼續處理下面的代碼
//=>狀態碼爲4的時候綁定的,而狀態不會在變了,因此方法不會執行
xhr.onreadystatechange = function () {
    console.log(xhr.readyState);
}
複製代碼

JQ中的AJAX

/* * $.ajax() 基於原生JS的AJAX四步操做進行封裝 * $.ajax([URL],[OPTIONS]) * $.ajax([OPTIONS]) URL在配置項中(推薦) * $.get/post/getJSON/getScript() * ...... * 配置項信息 * url:請求的API接口地址 * method:HTTP請求方式,默認GET * data:傳遞給服務器的信息,默認null(能夠是字符串,能夠是對象,並且若是GET系列請求,JQ會自動把信息拼接到URL的末尾,基於問號傳參傳遞給服務器;若是是POST請求,JQ會基於請求主體,把信息傳遞給服務器) * dataType:預設服務器返回的結果格式(服務器返回的通常都是JSON格式的字符串,若是咱們設置了DATA-TYPE,JQ會根據設置的類型,把服務器返回的結果處理爲對應的格式),支持的內容text / json / xml / html / script / jsonp(跨域) =>不影響服務器返回的結果,只是把服務器返回的結果進行二次處理 * async:是否爲異步操做,默認是TRUE,表明異步操做 * cache:緩存處理,只對GET系列請求有做用,默認是TRUE不處理緩存,當咱們設置FALSE後,JQ幫咱們在URL的末尾設置一個隨機數 * contentType:設置傳遞給服務器內容的格式類型 默認是"application/x-www-form-urlencoded" * 客戶端傳遞給服務器信息的格式(類型通常都是字符串),經常使用的: * form-data表單數據:JSON格式 '{"name":"xxx","lx":1}' * x-www-form-urlencoded:name=xxx&lx=1 * raw:純文本格式 * headers:設置請求頭信息,他是一個對象 * timeout:設置超時的時間 * success:回調函數,當數據請求成功執行,方法中的參數就是從服務器獲取的結果 * error:回調函數,數據請求失敗執行,方法中的參數是錯誤信息 */
$.ajax({
	url: 'http://yapi.demo.qunar.com/mock/95100/project/list',
	method: 'POST',
	data: {
		name: 'lanlan',
		lx: 'teacher'
	},
	dataType: 'json',
	async: true,
	cache: false,
	headers: {},
	success: (result, status, xhr) => {
		//=>xhr:是JQ幫咱們處理過的AJAX實例
		console.log(result, status, xhr);
	}
});
複製代碼

倒計時小例子

<div id="box"></div>
	<script>
		//new Date()獲取客戶端本地當前時間(不能拿它作重要依據,由於用戶能夠隨意修改)
		/* * 倒計時搶購須要從服務器獲取當前時間 AJAX * 問題:時間差(從服務器把時間給客戶端,到客戶端獲取到這個信息,中間經歷的時間就是時間差,而時間差是不可避免的,咱們應儘量減小這個偏差) * - 從響應頭獲取時間(AJAX異步) * - 基於HEAD請求(只獲取響應頭信息) */
		let target = new Date('2019/09/14 13:27:00'),
			now = null,
			timer = null;

		//=>從服務器獲取時間:獲取到時間後再作其餘的事情
		function func(callback) {
			let xhr = new XMLHttpRequest;
			xhr.open('HEAD', 'json/data.json', true);
			xhr.onreadystatechange = function () {
				if (!/^(2|3)\d{2}$/.test(xhr.status)) return;
				if (xhr.readyState === 2) {  //從響應頭中拿到時間
					now = new Date(xhr.getResponseHeader('Date'));
					callback && callback();
				}
			}
			xhr.send(null);
		}

		//=>開啓倒計時模式
		function computed() {
			let spanTime = target - now;
			if (spanTime <= 0) {
				//=>到搶購時間:結束定時器
				clearInterval(timer);
				timer = null;
				box.innerHTML = "開搶~~";
				return;
			}
			let hours = Math.floor(spanTime / (60 * 60 * 1000));
			spanTime -= hours * 60 * 60 * 1000;
			let minutes = Math.floor(spanTime / (60 * 1000));
			spanTime -= minutes * 60 * 1000;
			let seconds = Math.floor(spanTime / 1000);
			box.innerHTML =
				`距離搶購還剩 ${hours<10?'0'+hours:hours}:${minutes<10?'0'+minutes:minutes}:${seconds<10?'0'+seconds:seconds}`;

			//=>每一次計算完,咱們須要讓NOW在原來的基礎上加上一秒(第一次從服務器獲取到時間,後期直接基於這個時間本身加便可,不要每隔一秒從新從服務器拿)
			now = new Date(now.getTime() + 1000);
		}
		func(() => {
			//=>已經從服務器獲取時間了
			computed();
			timer = setInterval(computed, 1000);
		});
	</script>
複製代碼
相關文章
相關標籤/搜索