Asynchronous JavaScript and XML
(異步的javascript和XML),爲何會有這麼一種技術的出現呢,由於前端時常會有這樣的需求,咱們只要局部刷新,不須要整一個刷新的時候,便催生了這樣的技術。AJAX優勢:javascript
AJAX缺點:php
AJAX應用和傳統Web應用有什麼不一樣?css
基本示例:html
//建立 XMLHttpRequest 對象 var ajax = new XMLHttpRequest(); // 規定請求的類型、URL 以及是否異步處理請求。 ajax.open('GET',url,true); //發送信息至服務器時內容編碼類型 ajax.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); //發送請求 ajax.send(null); //接受服務器響應數據 ajax.onreadystatechange = function () { if (obj.readyState == 4 && (obj.status == 200 || obj.status == 304)) { } };
簡單應用示例:前端
oBtn.onclick = function () { //建立對象 var xhr = getXMLHttpRequest(); //當xhr對象的readyState屬性發生改變的時候觸發 xhr.onreadystatechange = function () { if (xhr.readyState == 4) { //ajax的狀態4表示加載完成 if (xhr.status >= 200 && xhr.status < 300 || xhr.status == 304) {// http的狀態是以上纔算正常 pp.innerHTML = xhr.responseText; } else { throw new Error("文件讀取錯誤"); } } } //open方法表示配置此次請求 xhr.open("get", "test.txt", true); //發送請求 //get請求中,沒有任何的上行主體的,因此寫null xhr.send(null); } //工廠函數(兼容瀏覽器) function getXMLHttpRequest() { if (window.XMLHttpRequest) { //高級瀏覽器,IE7,IE7+ return new XMLHttpRequest(); } else { //老版本瀏覽器,IE6 return new ActiveXObject("Microsoft.XMLHTTP"); } }
open()
方法xhr.open("get","test.txt",true);
java
調用open方法並不會真正發送請求,而只是啓動一個請求以備發送。jquery
它接受三個參數:web
send()
方法若是要發送請求,用send()
方法。ajax
要發送特定的請求,須要調用send()方法。編程
請求主體:HTTP上行請求,有頭部、主體。
一但調用send()方法,HTTP上行請求就將發出。
readyState
屬性表示「就緒狀態」
通常來講,只須要使用4狀態就能夠了
只要這個屬性值發生了變化,就會觸發一個事件onreadystatechange
事件,就可使用xhr.onreadystatechange = function(){}
來捕獲readyState
變化以後作的事情。
ajax 也是使用 http 協議的,因此也須要了解 http協議的狀態。
1XX 100-101 信息提示 2XX 200-206 成功 3XX 300-305 重定向 4XX 400-415 客戶端錯誤 5XX 500-505 服務器錯誤 200 OK 服務器成功處理了請求(這個是咱們見到最多的) 301/302 Moved Permanently(重定向)請求的URL已移走。Response中應該包含一個Location URL, 說明資源如今所處的位置 304 Not Modified(未修改)客戶的緩存資源是最新的, 要客戶端使用緩存 404 Not Found 未找到資源 501 Internal Server Error服務器遇到一個錯誤,使其沒法對請求提供服務
這是比較齊全的狀態表:
$
其實也就是window.$
良好的代碼風格
//=======================屬性=======================
//=======================方法=====================
//=======================內部方法=====================
_
表明內部方法或者屬性,主要是給編程人員看的arguments.length
來實現函數重載(function () { var myAjax = {}; //空對象 //向外暴露這麼一個全局變量 //就是這個函數的命名空間 window.myAjax = myAjax; //=======================屬性======================= myAjax.version = "0.2.0"; //=======================方法======================= myAjax.get = function () { //參數個數 var argLength = arguments.length; var URL, json, callback; if (argLength == 2 && typeof arguments[0] == "string" && typeof arguments[1] == "function") { //兩個參數 URL = arguments[0]; callback = arguments[1]; //傳給咱們的核心函數來發出Ajax請求 myAjax._doAjax("get", URL, null, callback); } else if (argLength == 3 && typeof arguments[0] == "string" && typeof arguments[1] == "object" && typeof arguments[2] == "function") { //3個參數 URL = arguments[0]; json = arguments[1]; callback = arguments[2]; //傳給咱們的核心函數來發出Ajax請求 myAjax._doAjax("get", URL, json, callback); } else { throw new Error("get方法參數錯誤!"); } } myAjax.post = function () { //參數個數 var argLength = arguments.length; if (argLength == 3 && typeof arguments[0] == "string" && typeof arguments[1] == "object" && typeof arguments[2] == "function") { //3個參數 var URL = arguments[0]; var json = arguments[1]; var callback = arguments[2]; //傳給咱們的核心函數來發出Ajax請求 myAjax._doAjax("post", URL, json, callback); } else { throw new Error("post方法參數錯誤!"); } } //post方式提交全部表單 myAjax.postAllForm = function (URL, formId, callback) { //將表單數據轉爲json var json = myAjax._formSerialize(formId); myAjax._doAjax("post", URL, json, callback); } //=======================內部方法===================== //將JSON轉換爲URL查詢參數寫法 //傳入{"id":12,"name":"考拉"} //返回id=12&name=%45%45%ED myAjax._JSONtoURLparams = function (json) { var arrParts = []; //每一個小部分的數組 for (k in json) { //組成參數數組,而後用& 鏈接 arrParts.push(k + "=" + encodeURIComponent(json[k]));//須要uri編碼特殊字符串,例如中文或者符號 } return arrParts.join("&"); } //最核心的發出Ajax請求的方法 myAjax._doAjax = function (method, URL, json, callback) { //Ajax的幾個公式 if (XMLHttpRequest) { var xhr = new XMLHttpRequest(); } else { var xhr = ActiveXObject("Microsoft.XMLHTTP"); } xhr.onreadystatechange = function () { if (xhr.readyState == 4) { if (xhr.status >= 200 && xhr.status < 300 || xhr.status == 304) { callback(null, xhr.responseText); } else { callback("文件沒有找到" + xhr.status, null); } } } //如今要根據請求類型進行判斷 if (method == "get") { //請求類型是get //若是用戶傳輸了json,此時要連字 if (json) { //判斷URL自己是否有?,沒有就須要&鏈接 var combineChar = URL.indexOf("?") == -1 ? "?" : "&"; //將json轉爲url參數後拼接 URL += combineChar + myAjax._JSONtoURLparams(json); } //增長一個隨機數參數,防止緩存 var combineChar = URL.indexOf("?") == -1 ? "?" : "&"; URL += combineChar + Math.random().toString().substr(2); xhr.open("get", URL, true); xhr.send(null); } else if (method == "post") { //增長一個隨機數參數,防止緩存 var combineChar = URL.indexOf("?") == -1 ? "?" : "&"; URL += combineChar + Math.random().toString().substr(2); xhr.open("post", URL, true); //post須要有header xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); xhr.send(myAjax._JSONtoURLparams(json)); } } })();
當Ajax第一次發送請求後,會把請求的URL和返回的響應結果保存在緩存內,當下一次調用Ajax發送相同的請求時,注意,這裏相同的請求指的是URL徹底相同,包括參數,瀏覽器就不會與服務器交互,而是直接從緩存中把數據取出來,這是爲了提升頁面的響應速度和用戶體驗。(服務端也會收到請求響應304)
瀏覽器會自做主張的把全部異步請求來的文件緩存,當下一次請求的URL和以前的同樣,那麼瀏覽器將不會發送這個請求,而是直接把緩存的內容當作xhr.responseText
。
須要注意的是,post 請求方式不會被緩存,只有 get 請求方式會被緩存。
方法1:隨機數
//隨機數,咱們不要0. 只要小數點後面的數字: var random = Math.random().toString().substring(2); //URL上面就拼接一個隨機字符串,保證每次URL不同 myAjax.get("text.txt?" + random,function(err,data){ alert(data); });
方法2:時間戳
從1970年1月1日0:00到這一刻的毫秒數。就叫作時間戳。英語屬於timestamp。
JS裏面時間戳就是
//時間戳: var timestamp = Date.parse(new Date()); //URL上面就拼接一個隨機字符串,保證每次URL不同 myAjax.get("text.txt?" + timestamp,function(err,data){ alert(data); });
總的來講,原理就是經過將 get 請求的 url 作成每次都不同,這樣就不會被瀏覽器緩存了。
判斷返回的 json 數據是否可用,這個只是屬性一些平常使用 ajax 的點而已。
經過JSON.parse
轉換爲json格式,若是沒法轉換,會報錯。
var jsonObj = JSON.parse(str);
hasOwnProperty 這個方法可以判斷對象裏面是否有某個鍵屬性。
var obj = {"a":1,"b":2}; console.log(obj.hasOwnProperty("aaa"));
這個示例比較詳細,而且加入了錯誤以後的處理:
//獲得頁面上的用戶名的文本框、下拉列表 var oUsername = document.getElementById("username"); var oDomain = document.getElementById("domain"); //獲得good、bad、tuijian var oTuijian = document.getElementById("tuijian"); var oBadTip = document.getElementById("badTip"); var oGoodTip = document.getElementById("goodTip"); //獲得4個li(事先給定或者從其餘接口獲取的) var tuijianLis = oTuijian.getElementsByTagName("li"); //失去焦點和改變下拉列表,都是作同一個事情 oUsername.onblur = check; oDomain.onchange = check; function check(){ clearAllTip(); //清除全部提示框 //獲得值 var username = oUsername.value; //文本框 //獲取全部用戶選中的郵箱選項,並放入到domain數組 var domain = (function(){ //獲得全部option var options = oDomain.getElementsByTagName("option"); //遍歷,看看哪一個被selected了 for(var i = 0 ; i < options.length ; i++){ if(options[i].selected){ return options[i].value; } } })(); //若是這個值是空,那麼什麼也不作。 if(!username) { return; } //正則驗證合法性 //6~18個字符,可以使用字母、數字、下劃線,需以字母開頭 var reg = /^[A-Za-z][\w]{5,17}$/; if (!reg.test(username)) { showWrong("6~18個字符,可以使用字母、數字、下劃線,需以字母開頭"); return; //不合法的時候,就返回,不執行下面的語句了 } //這裏請求一個靜態json,實際上要請求後臺php頁面。 myAjax.post("check.json",{"username" : username},function(err,data){ if(err){ showWrong("服務器錯誤,稍後再試"); return; } //轉爲json格式: var dataJSON = JSON.parse(data); //得到result對象(即獲取服務器返回的驗證結果) var result = dataJSON.result; //若是沒有result對象。就創造一個result對象 if(!result){ var result = {}; //由於須要給後續的hasOwnProperty校驗 } //檢測是否可用 if(result.hasOwnProperty(domain)){//服務器驗證結果跟用戶選項一致的時候 showRight("恭喜,可用!"); }else{ //服務器驗證結果跟用戶選項不一致的時候 //就要給用戶顯示推薦的郵箱 oTuijian.style.display = "block"; //顯示推薦框 //咱們要依次查找這些域名是否可用(事先給定或者從其餘接口獲取的) var domainArray = ["163.com","126.com","yeah.net"]; //咱們再寫一個結果數組 var usableArray = []; //遍歷domainArray,把domainArray中的每個項,進行檢測 //檢測result對象中是否是有這個屬性 //直接獲取了判斷的結果的數組 for(var i = 0 ; i < domainArray.length ; i++){ var tOrf = result.hasOwnProperty(domainArray[i]) ? true : false; usableArray.push(tOrf); } console.log(usableArray); //遍歷4個li標籤,根據咱們的結果數組來決定他們 //是否有disable類、裏面的span的內容、b的內容 for(var i = 0 ; i < tuijianLis.length ; i++){ var thisli = tuijianLis[i]; //經過判斷的結果的數組的值來控制是否設置class //決定這個li是否有disable類 thisli.className = usableArray[i] ? "" : "disable"; //往span裏面寫內容 //獲得這惟一一個span var thisspan = thisli.getElementsByTagName("span")[0]; //有時候須要從新解析一些值的格式 if(domainArray[i] == "vip163"){ domainArray[i] = "vip.163.com"; } thisspan.innerHTML = username + "@" + domainArray[i]; //往b裏面寫內容 var thisb = thisli.getElementsByTagName("b")[0]; //經過判斷的結果的數組的值來控制顯示內容 thisb.innerHTML = usableArray[i] ? "可使用" : "已經被佔用"; } } }); } //獲得焦點 oUsername.onfocus = clearAllTip; function clearAllTip(){ //讓全部的提示框消失 oTuijian.style.display = "none"; oBadTip.style.display = "none"; oGoodTip.style.display = "none"; } //顯示錯誤提示框 function showWrong(info){ oBadTip.innerHTML = info; oBadTip.style.display = "block"; } //顯示正確提示框 function showRight(info){ oGoodTip.innerHTML = info; oGoodTip.style.display = "block"; }
已經在另一篇文章裏面說過了,jsonp 是其中一種解決辦法。
//給按鈕添加監聽 oBtn.onclick = function(){ //獲得用戶填寫的手機號 var danhao = odanhao.value; var kuaidigongsi = okuaidigongsi.value; //建立script var script = document.createElement("script"); script.src = "https://sp0.baidu.com/9_Q4sjW91Qh3otqbppnN2DJv/pae/channel/data/asyncqury?cb=xixi&appid=4001&com=" + kuaidigongsi +"&nu=" + danhao +"&vcode=&token=&_=1438916675664" //追加而後刪除 document.body.appendChild(script); document.body.removeChild(script); } function xixi(data){ console.log(data); }
要實現2個地方:
咱們須要知道:
$(document).height();
獲取,視口底部來觸發ajax 獲取下一頁的數據這裏分紅三列瀑布流,組成一個數組管理
圖片的插入次序不是固定的(ajax異步),因此用以前的數組進行管理,每次都對最小值的高度插入值,這樣就能保證每次都往最靠裏面的圖片位置進行放置
瀑布流的數組樣例以下:
// 第一行 // [0,0,0] minIndex: 0 left 0 top 20 [558,0,0 ] // [558,0,0] minIndex: 1 left 300 top 20 [558,386,0] // [558,386,0] minIndex:2 left 600 top 20 [558,386,722] //第二行 //[558,386,722] minIndex:1 left 300 top 406 [558, 943, 722] //[558, 943, 722] minIndex:0 left 0 top 578 [1193, 943, 722] //[1193, 943, 722] minIndex:2 left 600 top 742 [1193, 943, 1128]
<!DOCTYPE html> <html lang="en"> <head> <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"> <title>Document</title> <style type="text/css"> <!--省略樣式--> </style> </head> <body> <!--加載logo,默認隱藏--> <div class="waterfall"> </div> <div class="end"> 到最後了親! </div> <script type="text/javascript" src="js/jquery-1.11.3.min.js"></script> <script type="text/javascript" src="js/underscore.js"></script> <!-- 模板,用underscore解析 --> <script type="type/template" id="feed-template"> <div class="feed-item"> <p> <img src="<%= imgurl %>" alt=""/> </p> <p class="biaoti"> <%= title %> </p> <p class="neirong"> <%= content %> </p> <p class="zuozhe"> <%= author %> </p> </div> </script>
var $waterfall = $(".waterfall"); //獲得模板 var templateString = $("#feed-template").html(); //準備模板函數,經過underscore將模板函數轉爲html模板,全局使用,因此單獨拿出來 var compile = _.template(templateString); //準備總高度數組 var colAllHeight = [0, 0, 0]; //三個表示頁面的瀑布的三列的每個塊的高度 var pagenum = 1; //頁碼 getAndRender(1); //先渲染第一頁的內容 var lock = true; //函數截流 //窗口捲動監聽 //每滾動一次都會觸發 $(window).scroll(function () { //jquery幫咱們作了關於滾動的三個兼容處理:總文檔高度,已經卷動高度,視口高度 var scrollTop = $(window).scrollTop(); var windowHeight = $(window).height(); var documentHeight = $(document).height(); //已經滾動到底部而且已經被lock if (documentHeight - windowHeight - scrollTop < 200 && lock) { lock = false; //解除鎖定 pagenum++; //滾動一次加一次頁數 getAndRender(pagenum); //根據頁數渲染數據,而且裏面會從新鎖定 } }); function getAndRender(pagenum) { //讓加載logo顯示 $waterfall.addClass("loading"); //發出Ajax請求 //這裏的頁數是用簡單的文件的數字編號來代替 $.get("json/json" + pagenum + ".txt", function (data, statusText) { //jq的ajax的get方法 //把字符串轉爲對象 var dataJSON = JSON.parse(data); //news這個數組,仔細想一想,news這個數組裏面裝的是什麼? var dictionaryArray = dataJSON.news; //若是數組爲空,就表示到最後了 if (dictionaryArray.length == 0) { $(".end").show(); $waterfall.removeClass('loading'); return; } //遍歷從接口獲取的數據 for (var i = 0; i < dictionaryArray.length; i++) { var thisDictionary = dictionaryArray[i]; //立刻發出請求這個字典裏面圖片的請求 var image = new Image(); //一旦設置src,上行HTTP請求將發出 image.src = thisDictionary.imgurl; image.index = i; //設置這個image的索引值 //監聽這個圖片是否是加載完畢 $(image).load(function () { //這張圖片加載完畢了 //console.log(this.index + "號圖片加載完畢"); //填充字典 //哪一個圖片已經填充完了,就注入幾號字典 //例如第一個圖片,傳入轉爲html模板的函數 var compiledString = compile(dictionaryArray[this.index]); //獲得這個盒子,變爲jQuery對象 var $box = $(compiledString); //上DOM $waterfall.append($box); //尋找最小列 var min = _.min(colAllHeight); //尋找最小列的索引 var minIndex = _.indexOf(colAllHeight, min); //絕對定位: $box.css("left", 300 * minIndex); $box.css("top", colAllHeight[minIndex] + 20); //將本身的高度,也加到數組的指定列中: colAllHeight[minIndex] += $box.outerHeight() + 20; //淡入 $box.fadeIn(); //讓加載滾動的logo有高度,跟隨移動位置 $waterfall.css("height", _.max(colAllHeight)); $waterfall.removeClass("loading"); lock = true; }); } }); }