AJAX(Async Javascript and Xml):在AJAX中的異步不是異步編程中的異步,而是泛指“局部刷新”,但是在AJAX請求中盡可能使用異步獲取數(shù)據(jù)(因?yàn)楫惒綌?shù)據(jù)獲取不會(huì)阻塞下面代碼的執(zhí)行)
AJAX的操作
//=>創(chuàng)建AJAX實(shí)例:IE6中是不兼容的,使用的是new ActiveXObject來實(shí)現(xiàn)的
let xhr = new XMLHttpRequest();
//=>打開請求:發(fā)送請求之前的一些配置項(xiàng)
//1.HTTP METHOD 請求方式
//2.URL 向服務(wù)器端發(fā)送請求的API接口地址
//3.ASYNC 設(shè)置AJAX請求的同步異步,默認(rèn)是異步(寫TRUE也是異步),F(xiàn)ALSE是同步,項(xiàng)目中都使用異步編程,防止阻塞后續(xù)代碼執(zhí)行
//4.USER-NAME/USER-PASS:用戶名密碼,一般不用
xhr.open([HTTP METHOD],[URL],[ASYNC],[USER-NAME],[USER-PASS]);
//=>事件監(jiān)聽:一般監(jiān)聽的都是 READY-STATE-CHANGE 事件(AJAX狀態(tài)改變事件),基于這個(gè)事件可以獲取服務(wù)器返回的響應(yīng)頭/響應(yīng)主體內(nèi)容
xhr.onreadystatechange= () => {
if(xhr.readyState === 4 && xhr.status === 200){
xhr.responseText;
}
};
//=>發(fā)送AJAX請求:從這步開始,當(dāng)前AJAX任務(wù)開始,如果AJAX是同步的,后續(xù)代碼不會(huì)執(zhí)行,要等到AJAX狀態(tài)成功后在執(zhí)行,反之異步不會(huì)
xhr.send([body]);
AJAX狀態(tài)(READY-STATE)
0 => UNSENT 剛開始創(chuàng)建XHR,還沒有發(fā)送
1 => OPENED 已經(jīng)執(zhí)行了OPEN操作
2 => HEADERS_RECEIVED 已經(jīng)發(fā)送AJAX請求(AJAX任務(wù)開始),響應(yīng)頭信息已經(jīng)被客戶端接收了(響應(yīng)頭中包含了:服務(wù)器的時(shí)間、返回的HTTP狀態(tài)碼...)
3 => LOADING 響應(yīng)主體內(nèi)容正在返回
4 => DONE 響應(yīng)主體內(nèi)容已經(jīng)被客戶端接收
關(guān)于XHR的屬性和方法
-
xhr.response
響應(yīng)主體內(nèi)容 -
xhr.responseText
響應(yīng)主體的內(nèi)容是字符串(JSON或者XML格式字符串都可以) -
xhr.responseXML
響應(yīng)主體的內(nèi)容是XML文檔 -
xhr.status
返回的HTTP狀態(tài)碼 -
xhr.statusText
狀態(tài)碼的描述 -
xhr.timeout
設(shè)置請求超時(shí)的時(shí)間 -
xhr.withCredentials
是否允許跨域 -
xhr.abort()
強(qiáng)制中斷AJAX請求 -
xhr.getAllResponseHeaders()
獲取所有響應(yīng)頭信息 -
xhr.getResponseHeader([key])
獲取KEY對應(yīng)的響應(yīng)頭信息,例如:xhr.getResponseHeader('date')就是在獲取響應(yīng)有中的服務(wù)器時(shí)間 -
xhr.open()
打開URL請求 -
xhr.overrideMimeType()
重寫MIME類型 -
xhr.send()
發(fā)送AJAX請求 -
xhr.setRequestHeader()
設(shè)置請求頭
let xhr = new XMLHttpRequest();
xhr.open('GET', 'temp.json');
xhr.setRequestHeader('leonard', '@@@');//=>設(shè)置的請求頭信息不能出現(xiàn)中文而且必須在OPEND之后才可以設(shè)置成功
/* xhr.timeout = 200;
xhr.ontimeout = () => {
console.log('請求超時(shí),請稍后重試');
};
*/
xhr.onreadystatechange = () => {
if (!/^(2|3)\d{2}$/.test(xhr.status)) return;
if (xhr.readyState === 2) {
let time = xhr.getResponseHeader('date');
console.log(time, new Date(time));
}
if (xhr.readyState === 4) {
console.log(xhr.responseText);
}
};
xhr.send(null);
AJAX中的同步異步
- 異步情況
let xhr=new XMLHttpRequest(); // readyState 0 xhr.open('GET','/temp/list',true); // readyState 1 xhr.onreadystatechange=()=>{ if(xhr.readyState===2){console.log(1);} if(xhr.readyState===4){console.log(2);} }; xhr.send(); console.log(3); //=> 3 1 2
- 同步情況:同步狀態(tài)下主任務(wù)隊(duì)列在
readyState
未變成4之前一直被占用,所以即使觸發(fā)了onreadystatechange
,也無法執(zhí)行//=>注意send位置不同結(jié)果是不一樣的 let xhr=new XMLHttpRequest(); xhr.open('GET','/temp/list',true); xhr.onreadystatechange=()=>{ if(xhr.readyState===2){console.log(1);} if(xhr.readyState===4){console.log(2);} }; xhr.send(); console.log(3); //=> 2 3 let xhr=new XMLHttpRequest(); xhr.open('GET','/temp/list',true); xhr.send(); xhr.onreadystatechange=()=>{ if(xhr.readyState===2){console.log(1);} if(xhr.readyState===4){console.log(2);} }; console.log(3); //=> 3
JQUERY中的AJAX
???????我們發(fā)現(xiàn),每次寫AJAX請求都要經(jīng)歷“經(jīng)典四步”,寫起來特別麻煩,所以需要借助一些已經(jīng)封裝好了的AJAX(當(dāng)然,你也可以自己封裝2333)來進(jìn)行數(shù)據(jù)請求,這樣顯得方便快捷
$.ajax([URL],[OPTIONS]) / $.ajax([OPTIONS])
-
$.get / $.post / $.getJSON / $.getScript
這些方法都是基于$.ajax
構(gòu)建出來的快捷方法
//=>$.ajax中OPTIONS常用的參數(shù)
/*
* URL:請求的API接口地址
* METHOD:請求的方式
* DATA:傳遞給服務(wù)器的信息可以放到DATA中
* 如果是GET請求是基于問號(hào)傳參傳遞過去的
* 如果是POST請求是基于請求主體傳遞過去的
*
* DATA的值可以是對象也可以是字符串(一般常用對象)
* 如果是對象類型,JQ會(huì)把對象轉(zhuǎn)換為 xxx=xxx&xxx=xxx 的模式(x-www-form-urlencoded)
* 如果是字符串,我們寫的是什么就傳遞什么
*
* DATA-TYPE:預(yù)設(shè)置獲取結(jié)果的數(shù)據(jù)格式 TEXT/JSON/JSONP/HTML/SCRIPT/XML...(服務(wù)器返回給客戶端的響應(yīng)主體中的內(nèi)容一般都是字符串[JSON格式居多]),而設(shè)置DATA-TYPE='JSON',JQ會(huì)內(nèi)部把獲取的字符串轉(zhuǎn)為JSON格式的對象 =>“他不會(huì)影響服務(wù)返回的結(jié)果,只是把返回的結(jié)果進(jìn)行了二次處理”
* ASYNC:設(shè)置同步或者異步(TRUE->異步 FALSE->同步)
* CACHE:設(shè)置GET請求下是否建立緩存(默認(rèn)TRUE->建立緩存 FALSE->不建立緩存),當(dāng)我們設(shè)置FALSE,并且當(dāng)前請求是GET請求,JQ會(huì)在請求的URL地址末尾追加隨機(jī)數(shù)(時(shí)間輟)
*
* SUCCESS:回調(diào)函數(shù),當(dāng)AJAX請求成功執(zhí)行,JQ執(zhí)行回調(diào)函數(shù)的時(shí)候會(huì)把從響應(yīng)主體中獲取的結(jié)果(可能二次處理了)當(dāng)做參數(shù)傳遞給回調(diào)函數(shù)
* ERROR:請求失敗后執(zhí)行的回調(diào)函數(shù)
*/
$.ajax({
url: '/temp/list',
method: 'GET',
data: {
name: 'Leonard',
age: 18
},
dataType: 'json',
async: true,
cache: false,
success: (result, textStatus, xhr) => {
console.log(result);
console.log(textStatus);
console.log(xhr.getResponseHeader('date'));//=>jq重構(gòu)的XHR
},
error: () => {}
});
???????心動(dòng)不如行動(dòng),接下來模仿JQ封裝一個(gè)簡易版的AJAX
~(function(window) {
function AJAX(options) {
return new init(options);
}
let init = function init(options = {}) {
//=>初始化參數(shù)
let {
url,
method = 'GET',
data = null,
dataType = 'JSON',
async = true,
cache = true,
success,
error
} = options;
//=>把配置項(xiàng)掛載到實(shí)例上
['url', 'method', 'data', 'dataType', 'async', 'cache', 'success', 'error'].forEach(item => {
this[item] = eval(item);
});
//=>發(fā)送AJAX請求
this.sendAjax();
};
AJAX.prototype = {
constructor: AJAX,
init,
//=>發(fā)送AJAX請求
sendAjax() {
this.handleData();
this.handleCache();
//=>SEND
let {method, url, async, error, success, data} = this,
xhr = new XMLHttpRequest;
xhr.open(method, url, async);
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
//=>ERROR
if (!/^(2|3)\d{2}$/.test(xhr.status)) {
error && error(xhr.statusText, xhr);
return;
}
//=>SUCCESS
let result = this.handlDataType(xhr);
success && success(result, xhr);
}
};
xhr.send(data);
},
//=>處理DATA-TYPE
handlDataType(xhr) {
let dataType = this.dataType.toUpperCase(),
result = xhr.responseText;
switch (dataType) {
case 'TEXT':
break;
case 'JSON':
result = JSON.parse(result);
break;
case 'XML':
result = xhr.responseXML;
break;
}
return result;
},
//=>處理CACHE
handleCache() {
let {url, method, cache} = this;
if (/^GET$/i.test(method) && cache === false) {
//=>URL末尾追加時(shí)間輟
url += `${this.check()}_=${+(new Date())}`;
this.url = url;
}
},
//=>處理DATA
handleData() {
let {data, method} = this;
if (!data) return;
//=>如果是個(gè)OBJECT對象,我們把它轉(zhuǎn)換為x-www-form-urlencoded這種模式,方便后期傳遞給服務(wù)器
if (typeof data === 'object') {
let str = ``;
for (let key in data) {
if (data.hasOwnProperty(key)) {
str += `${key}=${data[key]}&`;
}
}
data = str.substring(0, str.length - 1);
}
//=>根據(jù)請求方式不一樣,傳遞給服務(wù)器的方式也不同
if (/^(GET|DELETE|HEAD|TRACE|OPTIONS)$/i.test(method)) {
this.url += `${this.check()}${data}`;
this.data = null;
return;
}
this.data = data;
},
//=>檢測URL中是否存在問號(hào)
check() {
return this.url.indexOf('?') > -1 ? '&' : '?';
}
};
init.prototype = AJAX.prototype;
window.ajax = AJAX;
})(window);
???????不過問題又來了,我們發(fā)現(xiàn)JQ中的AJAX在處理多個(gè)請求中會(huì)出現(xiàn)“回調(diào)地獄”的尷尬情形,那不行,雖然我的代碼沒那么美觀,但也不能容忍它這么丑吧,得想想法子
//=>回調(diào)地獄出現(xiàn)
$.ajax({
url: '/temp/list',
success: result => {
$.ajax({
url: '/temp/info',
success: result => {
$.ajax({
url: '/temp/add',
method:'POST',
success: result => {
...
}
});
}
});
}
});
//=>解決方案1:發(fā)布訂閱
let $planA = $.Callbacks(),
$planB = $.Callbacks();
$.ajax({
url: '/temp/list',
success: result => {
$planA.fire(result);
}
});
$planA.add(result => {
$.ajax({
url: '/temp/list',
success: result => {
$planB.fire(result);
}
});
});
$planB.add(result => {
...
});
//=>解決方案2:Promise
let queryA = function queryA() {
return new Promise(resolve => {
$.ajax({
url: '/temp/list',
success: resolve
});
});
};
let queryB = function queryB() {
return new Promise(resolve => {
$.ajax({
url: '/temp/info',
success: resolve
});
});
};
let queryC = function queryC() {
return new Promise(resolve => {
$.ajax({
url: '/temp/add',
method: 'POST',
success: resolve
});
});
};
let promise = queryA();
promise.then(result => {
console.log('A', result);
return queryB();
}).then(result => {
console.log('B', result);
return queryC();
}).then(result => {
console.log('C', result);
});
AXIOS
???????那么既然promise
這么香,為何不用一個(gè)基于promise
來管理的AJAX庫呢?AXIOS就是這樣一個(gè)寶藏,它是一個(gè)基于promise
管理的類庫
- 發(fā)送請求
// axios.get(url[,config])
axios.get('/temp/info', {
params: {
name: 'Leonard',
age: 18
}
});
//=>配置項(xiàng)中傳遞的內(nèi)容都相當(dāng)于基于請求主體專遞給服務(wù)器
//=>但是傳遞給服務(wù)器的內(nèi)容格式是RAW(JSON格式的字符串)
//=>不是X-WWW-FORM-URLENCODED
// axios.post(url[,data[,config]])
axios.post('/temp/add', {
name: 'Leonard',
age: 18
});
//=>一次并發(fā)多個(gè)請求
let sendAry = [
axios.get('/temp/list'),
axios.get('/temp/info'),
axios.get('/temp/add')
]
axios.all(sendAry).then(result => {
console.log(result); //=>是一個(gè)數(shù)組,分別存儲(chǔ)著每一個(gè)請求的結(jié)果
})
//=> 也可以寫成
axios.all(sendAry).then(axios.spread((resA,resB,resC) => {...})
- 請求返回信息
let promise = axios.get('/package.json', {
params: {
lx: 12
}
});
promise.then(result => {
console.log(result); //=>獲取的結(jié)果是一個(gè)對象
/*
* data:從服務(wù)器獲取的響應(yīng)主體內(nèi)容
* headers:從服務(wù)器獲取的響應(yīng)的頭信息
* request:創(chuàng)建的AJAX實(shí)例
* status:狀態(tài)碼
* statusText:狀態(tài)碼的描述
* config:基于AXIOS發(fā)送請求的時(shí)候做的配置項(xiàng)
*/
}).catch(msg => {
console.log(msg); //=>請求失敗的原因
});
- 初始化一些常用的配置項(xiàng)
//=>設(shè)置公共地址
axios.defaults.baseURL = 'https://www.easy-mock.com/mock/5b0412beda8a195fb0978627/temp';
//=>響應(yīng)攔截處理,可以直接取出響應(yīng)主體內(nèi)容以便后續(xù)使用
axios.interceptors.response.use(result => result.data);
//=>自定義校驗(yàn)規(guī)則
axios.defaults.validateStatus = status => /^(2|3)\d{2}$/.test(status);
//=>設(shè)置在POST請求中基于請求主體向服務(wù)器發(fā)送內(nèi)容的格式,默認(rèn)是RAW,項(xiàng)目中常用的是URL-ENCODEED格式
axios.defaults.headers['Content-Type'] = 'appliction/x-www-form-urlencoded';
//=>對請求內(nèi)容進(jìn)行處理
axios.defaults.transformRequest = data => {
//=>DATA:就是請求主體中需要傳遞給服務(wù)器的內(nèi)容(對象)
let str = ``;
for (let attr in data) {
if (data.hasOwnProperty(attr)) {
str += `${attr}=${data[attr]}&`;
}
}
return str.substring(0, str.length - 1);
};
- 更詳細(xì)地進(jìn)行學(xué)習(xí)可以參考axios
- 最后,我們基于PROMISE再封裝一款簡易版AJAX庫
~(function(window) {
//=>設(shè)置默認(rèn)的參數(shù)配置項(xiàng)
let _default = {
method: 'GET',
url: '',
baseURL: '',
headers: {},
dataType: 'JSON',
data: null,
params: null,
cache: true
};
//=>基于PROMISE設(shè)計(jì)模式管理AJAX請求
let ajaxPromise = function ajaxPromise(options) {
let {url, baseURL, method, data, dataType, headers, cache, params} = options;
//=>把傳遞的參數(shù)進(jìn)一步進(jìn)行處理
if (/^(GET|DELETE|HEAD|OPTIONS)$/i.test(method)) {
//=>GET系列
if (params) {
url += `${ajaxPromise.check(url)}${ajaxPromise.formatData(params)}`;
}
if (cache === false) {
url += `${ajaxPromise.check(url)}_=${+(new Date())}`;
}
data = null;//=>請求主體清空
} else {
//=>POST系列
if (data) {
data = ajaxPromise.formatData(data);
}
}
//=>基于PROMISE發(fā)送AJAX
return new Promise((resolve, reject) => {
let xhr = new XMLHttpRequest;
xhr.open(method, `${baseURL}${url}`);
//=>如果HEADERS存在,我們需要設(shè)置請求頭
if (headers !== null && typeof headers === 'object') {
for (let attr in headers) {
if (headers.hasOwnProperty(attr)) {
let val = headers[attr];
if (/[\u4e00-\u9fa5]/.test(val)) {
//=>VAL中包含中文:我們把它進(jìn)行編碼
val = encodeURIComponent(val);
}
xhr.setRequestHeader(attr, val);
}
}
}
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
if (/^(2|3)\d{2}$/.test(xhr.status)) {
let result = xhr.responseText;
dataType = dataType.toUpperCase();
dataType === 'JSON' ? result = JSON.parse(result) : (dataType === 'XML' ? result = xhr.responseXML : null);
resolve(result);
return;
}
reject(xhr.statusText);
}
};
xhr.send(data);
});
};
//=>把默認(rèn)配置暴露出去,后期用戶在使用的時(shí)候可以自己設(shè)置一些基礎(chǔ)的默認(rèn)值(發(fā)送AJAX請求的時(shí)候按照用戶配置的信息進(jìn)行處理)
ajaxPromise.defaults = _default;
//=>把對象轉(zhuǎn)換為URLENCODED格式的字符串
ajaxPromise.formatData = function formatData(obj) {
let str = ``;
for (let attr in obj) {
if (obj.hasOwnProperty(attr)) {
str += `${attr}=${obj[attr]}&`;
}
}
return str.substring(0, str.length - 1);
};
ajaxPromise.check = function check(url) {
return url.indexOf('?') > -1 ? '&' : '?';
};
['get', 'delete', 'head', 'options'].forEach(item => {
ajaxPromise[item] = function(url, options = {}) {
options = {
..._default,//=>默認(rèn)值或者基于defaults修改的值
...options,//=>用戶調(diào)取方法傳遞的配置項(xiàng)
url: url,//=>請求的URL地址(第一個(gè)參數(shù):默認(rèn)配置項(xiàng)和傳遞的配置項(xiàng)中都不會(huì)出現(xiàn)URL,只能這樣獲取)
method: item.toUpperCase()
};
return ajaxPromise(options);
};
});
['post', 'put', 'patch'].forEach(item => {
ajaxPromise[item] = function anonymous(url, data = {}, options = {}) {
options = {
..._default,
...options,
url: url,
method: item.toUpperCase(),
data: data
};
return ajaxPromise(options);
};
});
window.ajaxPromise = ajaxPromise;
})(window);