关于ajax学习笔记
一、什么是AJAX,为什么要使用Ajax(请谈一下你对Ajax的认识)
- ajax全称
Asynchronous JavaScript and XML
(异步的javascript和XML),为什么会有这么一种技术的出现呢,因为前端时常会有这样的需求,我们只要局部刷新,不需要整一个刷新的时候,便催生了这样的技术。 - 在 Ajax应用中信息是通过XML数据或者字符串在浏览器和服务器之间传递的(json字符串居多)
- 在浏览器端通过XMLHttpRequest对象的responseXMl属性,得到服务器端响应的XML数据。
AJAX优点:
- 最大的一点是页面无刷新,用户的体验非常好。
- 使用异步方式与服务器通信,具有更加迅速的响应能力。
- 可以把以前一些服务器负担的工作转嫁到客户端,利用客户端闲置的能力来处理,减轻服务器和带宽的负担,节约空间和宽带租用成本。并且减轻服务器的负担,ajax的原则是“按需取数据”,可以最大程度的减少冗余请求,和响应对服务器造成的负担。
- 基于标准化的并被广泛支持的技术,不需要下载插件或者小程序。
AJAX缺点:
- ajax不支持浏览器back按钮。
- 安全问题 AJAX暴露了与服务器交互的细节。
- 对搜索引擎的支持比较弱。
- 破坏了程序的异常机制。
- 不容易调试。
AJAX应用和传统Web应用有什么不同?
- 传统的web前端与后端的交互中,浏览器直接访问Tomcat的Servlet来获取数据。Servlet通过转发把数据发送给浏览器。
- 当我们使用AJAX之后,浏览器是先把请求发送到XMLHttpRequest异步对象之中,异步对象对请求进行封装,然后再与发送给服务器。服务器并不是以转发的方式响应,而是以流的方式把数据返回给浏览器
- XMLHttpRequest异步对象会不停监听服务器状态的变化,得到服务器返回的数据,就写到浏览器上【因为不是转发的方式,所以是无刷新就能够获取服务器端的数据】
- AJAX是异步执行的,如图所示,异步执行不会阻塞.
二、ajax 的执行过程
- 创建XMLHttpRequest对象,也就是创建一个异步调用对象
- 创建一个新的HTTP请求,并指定该HTTP请求的方法、URL及验证信息
- 设置响应HTTP请求状态变化的函数
- 发送HTTP请求
- 获取异步调用返回的数据
- 使用JavaScript和DOM实现局部刷新
基本示例:
//创建 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"); } }
2.1 open()
方法
xhr.open("get","test.txt",true);
调用open方法并不会真正发送请求,而只是启动一个请求以备发送。
它接受三个参数:
- 要发送的请求的类型
- 请求的URL
- 表示是否异步的布尔值。
2.2 send()
方法
如果要发送请求,用send()
方法。
要发送特定的请求,需要调用send()方法。
- 它接受一个参数:请求主体发送的数据。
- 如果不需要通过请求主体发送数据,则必须传入null,不能留空。
请求主体:HTTP上行请求,有头部、主体。
- 一般来说,GET请求是只有头部,没有主体
- 而POST请求有请求主体。
一但调用send()方法,HTTP上行请求就将发出。
2.3 readyState
属性
表示“就绪状态”
- 0 (uninitialized) 未初始化
- 1 (loading) XMLHttpRequest对象正在加载
- 2 (loaded) XMLHttpRequest对象加载完毕
- 3 (interactive) 正在传输数据
- 4 (complete) 全部完成
一般来说,只需要使用4状态就可以了
只要这个属性值发生了变化,就会触发一个事件onreadystatechange
事件,就可以使用xhr.onreadystatechange = function(){}
来捕获readyState
变化之后做的事情。
三、关于http的状态
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服务器遇到一个错误,使其无法对请求提供服务
这是比较齐全的状态表:
四、关于函数封装(ajax封装)
- 变量、函数的作用域,是定义这个变量、函数时,包裹它的最近父函数。
- 没有在任何function中定义的变量,称为全局变量。全局变量都是window对象的属性。所以,如果想在函数内,向全局暴露顶层变量,只需要把顶层变量设置为window对象的属性。
- 越是大的项目,越需要让全局变量越少越好。这是为了防止不同工程师之间的程序,命名冲突。所以,每一个功能包,只能向全局暴露唯一的顶层变量,就是这个功能包自己的命名空间。
- jQuery、YUI、underscore都是这样的做法。
- 向外暴露全局变量,设置window的变量(也是这个函数的命名空间),类似jquery的
$
其实也就是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缓存问题
当Ajax第一次发送请求后,会把请求的URL和返回的响应结果保存在缓存内,当下一次调用Ajax发送相同的请求时,注意,这里相同的请求指的是URL完全相同,包括参数,浏览器就不会与服务器交互,而是直接从缓存中把数据取出来,这是为了提高页面的响应速度和用户体验。(服务端也会收到请求响应304)
浏览器会自作主张的把所有异步请求来的文件缓存,当下一次请求的URL和之前的一样,那么浏览器将不会发送这个请求,而是直接把缓存的内容当做xhr.responseText
。
需要注意的是,post 请求方式不会被缓存,只有 get 请求方式会被缓存。
5.1 如何避免 ajax 缓存问题
方法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检测
判断返回的 json 数据是否可用,这个只是属性一些日常使用 ajax 的点而已。
6.1 使用 JSON.parse
通过JSON.parse
转换为json格式,如果无法转换,会报错。
var jsonObj = JSON.parse(str);
6.2 用hasOwnProperty进行判断
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 是其中一种解决办法。
7.1 使用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); }
八、关于ajax的示例:瀑布流
要实现2个地方:
- 滚动到底部判断(包含视口的底部和总的底部)
- 瀑布流里面的内容需要错位显示
8.1 滚动到底部判断
我们需要知道:
- 总文档高度
- 已经滚动的高度
- 视口高度,通过
$(document).height();
获取,视口底部来触发ajax 获取下一页的数据 - 总文档高度-已经卷动高度-视口高度 < 200 基本上就是滚动到底了,滚动到文档底部就停止 ajax 请求。
- scroll事件,一定是要截流的。因为用户滚一个鼠标滚轮的“小咯噔”就触发一次scroll事件;滑动滚动条的时候,是每一像素触发一次这个事件。还有pageDown、下箭头按钮,都能触发scroll事件。
- 如何判断文章是否到头,说白了前端开发工程师不知道一共有多少页。比如今天又53页,明天就有55页了,所以你的JS里面无法写死一个文章总页数。所以办法就是,请求下去,请求到page.php?pagenum=54的时候,发现终止标记,或者这个页面返回的json是空,就表示到头了。
8.2 瀑布流里面的内容需要错位显示
这里分成三列瀑布流,组成一个数组管理
- 这个数组会不断计算三列之中的最小值
- 然后按照每次的最小值进行高度插入
- 图片判断是否加载完成需要用load方法,并且图片需要先new image才能加载方法
图片的插入次序不是固定的(ajax异步),所以用之前的数组进行管理,每次都对最小值的高度插入值,这样就能保证每次都往最靠里面的图片位置进行放置
- 并且需要使用绝对位置值,因为css里面,需要使用绝对值撑开位置(left 和top)
瀑布流的数组样例如下:
// 第一行 // [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]
8.3 整个代码
<!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; }); } }); }
相关推荐
结束数据方法的参数,该如何定义?-- 集合为自定义实体类中的结合属性,有几个实体类,改变下标就行了。<input id="add" type="button" value="新增visitor&quo