一、什么是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;
});
}
});
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。