認識AJAX
掃盲1:
AJAX: Asynchronous Javascript And XML
html: 超文本標記語言数焊,W3C指定了很多具有語義化的標記標簽疯搅,用以搭建頁面結構(目前多用V4/V5版本)
xhtml: 更加嚴謹?shù)膆tml
dhtml: 頁面中的數(shù)據是動態(tài)綁定的
xml: 可擴展的標記標簽語言表制,它里面使用的標簽都是自己定義的健爬,用來存儲數(shù)據,結構清晰么介,以前用的比較多浑劳,不過其二次解析的過程相對JSON比較麻煩,目前前端數(shù)據多用JSON
<root>
<student>
<name>唐玄奘</name>
<age>18</age>
</student>
</root>
wxml: 微信小程序的頁面就是.wxml夭拌,小程序中使用的標簽都是小程序自己定義的(微信XML)
掃盲2:
這里的異步不能理解為JS中的同步異步編程魔熏,這里面的異步指的是‘局部刷新’,如果用六個字概述AJAX的作用鸽扁,那就是:實現(xiàn)局部刷新
在web1.0和web 2.0時代=>整體刷新
那時前端頁面的功能和數(shù)據綁定大部分都是后臺開發(fā)者使用后臺語言來完成的蒜绽,瀏覽器只有一個作用,就是把后臺實現(xiàn)好的功能的頁面呈現(xiàn)即可桶现,所有操作的都是后臺服務器完成躲雅,這樣若前端頁面需要改變,必須后臺把最新的內容重新返回骡和,前端頁面需要重新刷新
作用:
- 如果客戶端獲取的是資源文件相赁,瀏覽器會自動幫我們向服務器端發(fā)送請求,并接收服務器返回的內容進行渲染:
- 在地址欄輸入網址
- 利用link
- 利用script
- 利用iframe
- ...
- 但如果我們需要請求的是數(shù)據慰于,就需要AJAX等技術發(fā)送請求(AJAX是JS中的一個核心知識點)
AJAX獲取數(shù)據四部曲:
//創(chuàng)建ajax對象xhr
var xhr = new XMLHttpRequest();//ie6及以下不兼容,用new ActiveXObject
var data = null;
//打開一個url钮科,配置請求的基本參數(shù)信息
xhr.open('get','temp.xml?name=zf',true);
//[請求方式]:get post put delete head
//[請求的url地址]
//[同步或者異步]:默認是true(異步),false(同步)
//設置請求的用戶名和密碼:[userName],[userPass]提供安全權限的驗證(一般不用)
xhr.setRequestHeader('key','value');//
//監(jiān)聽ajax狀態(tài)改變婆赠,獲取服務器返回數(shù)據
xhr.onreadystatechange = function () {
if(xhr.readyState === 4 && xhr.status === 200){
//xhr.readyState ajax狀態(tài)碼 0 1 2 3 4
//xhr.status 服務器返回的http網絡狀態(tài)碼200 301 302 304 400 401 404 500 503...
data = JSON.PARSE(xhr.responseText) 服務器端響應主體內容绵脯,獲取到的是字符串
//xhr.responseXML 服務器端響應主體內容,獲取到的是XML格式數(shù)據
//獲取響應頭的兩種方式
//xhr.getResponseHeader('key')
//xhr.getAllResponseHeaders()
}
};
xhr.send(null);
//發(fā)送請求,括號內的傳遞的內容是請求主體
AJAX知識分解
1休里、創(chuàng)建AJAX對象
var xhr = new XMLHttpRequest();//ie6及以下不兼容,用new ActiveXObject
2蛆挫、打來一個請求的url地址
例:xhr.open('get','temp.xml?name=zf',true);
xhr.open([method],[request url],[true/false],[username],[userpass]);
//[method]:get post put delete head
//[request url]
//[true/false]:默認是true(異步),false(同步)妙黍,項目中為防止出現(xiàn)請求阻塞的問題悴侵,大多采用異步
//[userName],[userPass]:設置請求的用戶名和密碼,提供安全權限的驗證(一般不用)
- method:請求方式
get:從服務器上獲取數(shù)據(給的少,拿得多拭嫁,最常用的方式)
post:向服務器推送數(shù)據(給得多可免,拿得少)
put:給服務器增加資源文件(上傳圖片等)
delete:從服務器刪除文件
head:只獲取服務器響應頭信息
注意:無論那種請求方式筒繁,都可以向服務器發(fā)送和接收數(shù)據,且從本質意義上來講他們沒有任何區(qū)別巴元,只是約定俗成的規(guī)則毡咏,在不同的情況下使用不同的請求方式
get與post的區(qū)別:
核心:get通過問號傳參的方式給服務器傳遞內容,而post通過請求主體給服務器傳遞內容
xhr.open('get','temp.json?name=wang&age=18')//get方式逮刨,問號傳參傳遞
xhr.send('{"name":"wang","age":18}'//post方式呕缭,請求主體傳遞
一般get、delete修己、head等用問號傳參的方式給服務器傳遞內容恢总,post、put等用請求主體的方式給服務器傳遞內容
請求方式get系列與post系列之間存在的問題:
大小問題:get傳給服務器內容有大小限制睬愤,post理論上沒有限制:
因為不同瀏覽器對url長度存在限制片仿,chrome:8k,firefox:7k尤辱,ie:2k砂豌,超出限制部分會被瀏覽器截取,請求主體大小實際上為了保證傳輸?shù)乃俣裙舛剑瑫拗苽鬟f內容大小阳距。緩存問題:因為get使用問號傳參的方式,如果重復向同一個地址發(fā)送請求结借,瀏覽器會默認做緩存(這個緩存不可控)筐摘,所以我們一般項目中使用get請求時要把緩存清除:
xhr.open('get','temp.json?_=' + Math.random())
//這里的問號傳參中屬性名使用的是下劃線_,是因為除了傳隨機數(shù)外,我們可能還會傳遞其他的信息船老,這樣的話隨機數(shù)的屬性名可能會與其他信息的屬性名沖突咖熟,所以用_做屬性名防止沖突
- 安全問題:get請求相對于post來說不安全
有一種黑客技術叫url劫持,被劫持后柳畔,問號后面的參數(shù)值會被獲取或修改馍管,導致不安全
面試題:在你以前的項目中服務器返回的數(shù)據一般是什么格式的
- 返回的是字符串格式的
- 普通的字符串
- JSON格式字符串(最常用的)
- 二進制或者文件流編碼格式的字符串(一般請求的是圖片,服務器返回的都是二進制編碼字符串)
- 返回的是XML格式的
3荸镊、監(jiān)聽狀態(tài)改變
-
xhr.readyState:AJAX狀態(tài)碼
- 0 :UNSENT: 未發(fā)送,剛開始創(chuàng)建完成AJAX對象,默認的狀態(tài)就是0
- 1 :OPENED: 已打開,執(zhí)行了xhr.open之后狀態(tài)變?yōu)?
- 2 :HEADERS_RECEIVED: 響應頭信息已接收
- 3 :LOADING: 服務器正在處理咽斧,響應主體內容正在加載
- 4 :DONE: 響應主體內容已成功返回
-
xhr.status:服務器返回的HTTP狀態(tài)碼
- 200 :響應主體成功返回
- 301 :永久重定向(永久轉移) 域名更換的時候會做301永久重定向堪置,如京東的360buy
- 302 :臨時重定向(臨時轉移) ->307(臨時重定向) 服務器負載均衡:比如一臺服務器最高并發(fā)數(shù)為1000躬存,當?shù)?001個人訪問時,當前服務器不能進行有效處理了舀锨,此時需要把此客戶端的請求臨時轉移到另外一臺服務器上岭洲,這叫做負載均衡
- 304 :緩存數(shù)據,在真實項目中坎匿,產品一旦上線盾剩,資源圖片雷激、js、css告私、等內容是不輕易改變的屎暇,此時我們最好做一下304緩存,在第一次訪問服務器時驻粟,把加載的資源文件進行緩存根悼,第二次再加載時直接讀取緩存中的數(shù)據,減少服務器壓力蜀撑。瀏覽器強制無緩存刷新快捷鍵:ctrl+f5
- 400 :客戶端給服務器端請求參數(shù)錯誤
- 401 :無權限訪問
- 404 :訪問地址不存在
- 500 :未知服務器錯誤
- 503 :服務器超負荷挤巡,未做負載均衡
面試題:你之前的項目中做過倒計時之類的東西嗎?從服務器讀取時間你是怎么解決時間差的酷麦?
由于服務器返回給客戶端的時間信息在響應頭信息的Date屬性中矿卑,所以當我們只需要獲取服務器的時間信息時,可以用head的傳遞方式沃饶,且在監(jiān)聽xhr狀態(tài)時母廷,在readyState狀態(tài)碼為2的時候,就已經獲取到響應頭信息了糊肤,此時就可以獲取到服務器的Date數(shù)據了徘意,就沒必要在狀態(tài)碼為4的時候去獲取信息了,這樣請求的效率更高,代碼如下:
xhr.onreadystatechange = function () {
if (xhr.status !== 200) return;//if后沒有大括號則只會對if判斷后的第一條語句生效轩褐;
if (xhr.readyState === 2) {//->我們只需要響應頭信息返回就可以獲取到服務器的時間,如果等到4的時候,雖然也可以獲取到,但是間隔的時間更長了,導致時間差也會變大(真實時間和服務器獲取的時間差值)
var time = xhr.getResponseHeader('Date');//->獲取到的時間是格林尼治時間(GMT),我們還需要把這個時間變?yōu)楸本r間(GMT+0800)
time = new Date(time);//轉換為本地客戶端時區(qū)的時間
console.log(time);
}
};
4椎咧、發(fā)送ajax請求給服務器
xhr.send(null);
//get系列請求傳遞一般是null,post系列請求把請求主體放在send參數(shù)里
AJAX一次任務的開始和結束標志:
- 開始:xhr.send(…)
- 結束:xhr.readyState===4
AJAX方法封裝(仿JQ,基礎版)
jQuery中的ajax方法
下面是jq中的ajax方法調用把介,jq中的ajax有30多個參數(shù)勤讽,一下列舉了部分常用參數(shù)
$.ajax({
url: 'temp.json',
method: 'get',//->type:'get' 和這個屬性是一樣的功能的,定義請求方式
dataType: 'json',//->預設服務器返回的數(shù)據內容的格式json(默認)、text拗踢、xml...
data: null,//->設置請求主體的內容,如果是get請求,jQ會把這些內容放到請求地址的末尾,通過問號傳參的方式傳遞給服務器,post請求才是放在請求主體中
cache: true,//->是否保留get請求的緩存,true是保留get緩存,設置成為false是清除緩存(在URL末尾加隨機數(shù)),此參數(shù)對于post請求無效
async: true,//->設置同步異步,默認是TRUE代表異步
//timeout:3000,//->設置請求超時的時間,如果超過3000ms,當前請求自動中斷(一般不用)
success: function (result) {
//->當數(shù)據請求成功后執(zhí)行的回調函數(shù),result就是從服務器獲取的結果
console.log(result);
},
error: function (msg) {
//->當數(shù)據請求失敗指定的回調函數(shù),msg就是失敗的原因
}
});
以下是仿jQ的ajax基礎版方法源碼(注釋版)
(function () {
//->驗證當前的URL中是否包含問號,包含返回&,否則返回?
function check(url) {
return url.indexOf('?') > -1 ? '&' : '?';
}
//->把對象轉換為字符串
function formatData(obj) {
var str = '';
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
str += key + '=' + obj[key] + '&';
}
}
str = str.substring(0, str.length - 1);
return str;
}
function ajax(options) {
//1脚牍、設置參數(shù)的默認值
var _default = {//設置參數(shù)默認值,default是關鍵字 所以要加下劃線
url: null,
method: 'get',
data: null,
dataType: 'json',//->text巢墅、xml...
async: true,
cache: true,
success: null,
error: null
};
//2诸狭、使用傳遞進來的參數(shù)配置值把默認值進行替換
for (var key in options) {
if (options.hasOwnProperty(key)) {
//for in循環(huán)可以遍歷到__proto__公有屬性中自己拓展的方法,所以先判斷一下是不是私有屬性
if (key === 'type') {
_default['method'] = options['type'];
continue;
}
_default[key] = options[key];
}
}
//3君纫、發(fā)送AJAX請求
var xhr = new XMLHttpRequest;
var regGET = /^(get|delete|head)$/i;
//->3.3:處理DATA
//對象和字符串的區(qū)分,如果是對象需要轉換為字符串:xxx=xxx&xxx=xxx...
if (_default.data !== null) {
if (typeof _default.data === 'object') {
_default.data = formatData(_default.data);
}
//GET和POST的區(qū)分,GET是把內容放在問號后面
if (regGET.test(_default.method)) {
char = check(_default.url);
_default.url += char + _default.data;
_default.data = null;//->在發(fā)送的時候GET請求的請求主體是null
}
}
//->3.2:處理CACHE,如果當前的請求是GET系列的,并且CACHE等于FALSE,我們清除緩存
if (regGET.test(_default.method) && _default.cache === false) {
var char = check(_default.url);
_default.url += char + '_=' + Math.random();
}
xhr.open(_default.method, _default.url, _default.async);
xhr.onreadystatechange = function () {
if (/^(2|3)\d{2}$/.test(xhr.status)) {
if (xhr.readyState === 4) {
//->3.1:我們從服務器端獲取的數(shù)據一般都是字符串格式的,但是我們傳遞進來的參數(shù)值中的dataType預設了最后數(shù)據結果的類型驯遇,所以我們還需要自己進行數(shù)據的二次加工,把從服務器端獲取的字符串轉換為預設的結果
var result = xhr.responseText;
switch (_default.dataType.toUpperCase()) {
case 'JSON':
result = 'JSON' in window ? JSON.parse(result) : eval('(' + result + ')');
break;
case 'XML':
result = xhr.responseXML;
break;
}
_default.success && _default.success.call(xhr, result);
}
//->問題:JQ中的dataType是否影響了服務器的返回結果? 不影響,服務器端會根據產品需求給我們返回字符串或者XML數(shù)據,而JQ會根據我們設置的dataType值,把返回的結果二次解析成需要的類型
return;
}
//->失敗:執(zhí)行ERROR的回調函數(shù),讓函數(shù)中的THIS指向當前實例XHR,并且把錯誤信息傳遞給回調函數(shù)
//typeof _default.error === 'function' ? _default.error.call(xhr, xhr.responseText) : null;
_default.error && _default.error.call(xhr, xhr.responseText);//->這種寫法和上面的相同,都是在判斷只有傳遞了回調函數(shù)的情況下才會執(zhí)行(項目中一般都這么寫)
};
xhr.send(_default.data);
}
window.ajax = ajax;
}())();
以下是仿jQ的ajax基礎版方法源碼(無注釋版)
~function () {
function check(url) {
return url.indexOf('?') > -1 ? '&' : '?';
}
function formatData(obj) {
var str = '';
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
str += key + '=' + obj[key] + '&';
}
}
str = str.substring(0, str.length - 1);
return str;
}
function ajax(options) {
var _default = {
url: null,
method: 'get',
data: null,
dataType: 'json',
async: true,
cache: true,
success: null,
error: null
};
for (var key in options) {
if (options.hasOwnProperty(key)) {
if (key === 'type') {
_default['method'] = options['type'];
continue;
}
_default[key] = options[key];
}
}
var xhr = new XMLHttpRequest;
var regGET = /^(get|delete|head)$/i;
if (_default.data !== null) {
if (typeof _default.data === 'object') {
_default.data = formatData(_default.data);
}
if (regGET.test(_default.method)) {
char = check(_default.url);
_default.url += char + _default.data;
_default.data = null;
}
}
if (regGET.test(_default.method) && _default.cache === false) {
var char = check(_default.url);
_default.url += char + '_=' + Math.random();
}
xhr.open(_default.method, _default.url, _default.async);
xhr.onreadystatechange = function () {
if (/^(2|3)\d{2}$/.test(xhr.status)) {
if (xhr.readyState === 4) {
var result = xhr.responseText;
switch (_default.dataType.toUpperCase()) {
case 'JSON':
result = 'JSON' in window ? JSON.parse(result) : eval('(' + result + ')');
break;
case 'XML':
result = xhr.responseXML;
break;
}
_default.success && _default.success.call(xhr, result);
}
return;
}
_default.error && _default.error.call(xhr, xhr.responseText);
};
xhr.send(_default.data);
}
window.ajax = ajax;
}();