深入浅出解析AJAX
AJAX完全依赖于XMLHttpRequest对象
GET请求
// 创建xhr对象 var xhr = new XMLHttpRequest(); // 监听xhr对象 xhr.onreadystatechange = function() { if (xhr.readyState == 4) { if (xhr.status >= 200 && xhr.status < 300 || xhr.status == 304) { console.log(xhr.responseText); } } } xhr.open("get", URL) xhr.send(null)
这里监听要写在send之前,先监听后向服务器发起请求,相反如果请求发出在监听则本末倒置,即便在开发上并无区别。
xhr对象一旦开始open,就有了readyState属性,readyState属性一旦发生改变,就能够触发onreadystatechange事件,所以要先监听,这也是所有轮子的标准模板。
在open时http请求并没有发出,直到send后才会发出。
readyState 状态
0: 请求未初始化
1: 服务器连接已建立
2: 请求已接收
3: 请求处理中
4: 请求已完成,且响应已就绪
我们只会关心就绪状态为4时
浏览器执行到Ajax代码,发出了一个HTTP请求,欲请求服务器上的数据服务器的此时开始I/O,所谓的I/O就是磁盘读取,需要花一些时间,所以不会立即产生下行HTTP报文。
由于Ajax是异步的,所以本地的JavaScript程序不会停止运行,页面不会假死,不会傻等下行HTTP报文的出现。后面的JavaScript语句将继续运行。进程不阻塞。
服务器I/O结束,将下行HTTP报文发送到本地。
如果时post请求必须手动设置请求头,模拟form表单提交
xhr.open("post", URL) xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); xhr.send("name=" + encodeURIComponent("夏彬"))
这里尽量将中文转译,防止服务器错乱,后台程序语言,都能够自动处理转译。
每个XMLHttpRequest对象只能监听一条HTTP请求,直到这条请求结束,才能发起下一条,强行发起上一条请求会被注销。
因为同源策略所以XMLHttpRequest对象是不能跨域的,我们可以使用JSONP来跨域,JSON+Padding,用一种逆思维来跨域
fun({ "result" : [ { "name" : "小明", "age" : 12, "sex" : "男" }, { "name" : "小红", "age" : 13, "sex" : "女" }, { "name" : "小绿", "age" : 16, "sex" : "女" } ] });
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <script> function fun(data) { console.log(data) } </script> <script src="jsonp.txt"></script> </body> </html>
我们可以将JSONP封装为轮子以达到正向编程的目的
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <script> jsonp('jsonp.txt', "name=xiabin", "fun", function(data) { console.log(data) }) function jsonp(URL, queryString, callbackName, callback) { // 将JSONP定义函数绑定到window上 window[callbackName] = callback; var dom = document.createElement('script'); dom.src = queryString ? URL + "?m=" + Math.random() + "&" + queryString : URL + "?m=" + Math.random(); document.body.appendChild(dom) document.body.removeChild(dom); } </script> </body> </html>
当然如果一些数据不同源,且返回的不是JSONP数据,那么我们仍然可以使用服务器语言进行偷数据,现在很多网站都防偷。。。
例如:PHP中
<?php header("Content-Type:text/json;charset=gb2312"); $phone = $_GET["phone"]; $a = file_get_contents("http://chongzhi.jd.com/json/order/search_searchPhone.action?mobile=".$phone); print_r($a); ?>
例:AJAX实现瀑布流两种算法
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <style type="text/css"> * { margin: 0; padding: 0; } body { background-color: #ccc; } .waterfall { width: 790px; margin: 0 auto; position: relative; } .grid { position: absolute; width: 230px; background-color: white; padding: 10px; border-radius: 15px; } .grid img { width: 230px; } .grid .title { font-weight: bold; font-size: 18px; line-height: 32px; } .grid .neirong { line-height: 150%; font-size: 14px; margin-bottom: 20px; } .grid .zuozhe { float: right; color: orange; font-size: 12px; } .loading { margin: 0 auto; width: 400px; line-height: 30px; text-align: center; font-size: 14px; background-color: gold; color: white; } </style> </head> <body> <div class="waterfall" id="waterfall"> </div> <div class="loading"> 正在加载... </div> <script type="text/template" id="grid_template"> <div class="grid"> <img src="<%=imgurl%>" alt="" /> <p class="title"> <%=title%> </p> <p class="neirong"> <%=content%> </p> <p class="zuozhe"> <%=author%> </p> </div> </script> <script src="js/jquery-1.12.3.min.js"></script> <script src="js/underscore.js"></script> <script> var container = document.getElementById("waterfall"); var templateStr = document.getElementById("grid_template").innerHTML; var $loading = $(".loading"); // 生成数据绑定函数 var template = _.template(templateStr); gotoPage(1) // AJAX数据请求函数 function gotoPage(num) { $loading.hide(); $.get("json/json" + num + ".txt", function(data) { data = JSON.parse(data); // 没有数据则直接退出 console.log(data.news) if (data.news.length == 0) { $loading.show().html("没有更多了"); return; } _.each(data.news, function(dictionary) { var IMG = new Image(); IMG.src = dictionary.imgurl; // 那张图先加载完渲染那张 $(IMG).load(function() { var templateStr = template(dictionary); var $grid = $(templateStr); var top = 0; $(container).append($grid); // 计算位置 for (var i = $grid.index() - 3; i >= 0; i -= 3) { top += $(".grid").eq(i).outerHeight() + 20; } $grid.css({ "left": $grid.index() % 3 * 270, "top": top }) $(container).height($(document).height()) $loading.hide(); }) }) lock = true; }) } var lock = true; // 页面滚动加载流 var page = 0; $(window).scroll(function() { if (!lock) return; var ratio = $(window).scrollTop() / ($(document).height() - $(window).height()); if (ratio > 0.7) { lock = false; page++ gotoPage(page) } }) </script> </body> </html>
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <style type="text/css"> * { margin: 0; padding: 0; } body { background-color: #ccc; } .waterfall { width: 790px; margin: 0 auto; position: relative; } .grid { position: absolute; width: 230px; background-color: white; padding: 10px; border-radius: 15px; } .grid img { width: 230px; } .grid .title { font-weight: bold; font-size: 18px; line-height: 32px; } .grid .neirong { line-height: 150%; font-size: 14px; margin-bottom: 20px; } .grid .zuozhe { float: right; color: orange; font-size: 12px; } .loading { margin: 0 auto; width: 400px; line-height: 30px; text-align: center; font-size: 14px; background-color: gold; color: white; } </style> </head> <body> <div class="waterfall" id="waterfall"> </div> <div class="loading"> 正在加载... </div> <script type="text/template" id="grid_template"> <div class="grid"> <img src="<%=imgurl%>" alt="" /> <p class="title"> <%=title%> </p> <p class="neirong"> <%=content%> </p> <p class="zuozhe"> <%=author%> </p> </div> </script> <script src="js/jquery-1.12.3.min.js"></script> <script src="js/underscore.js"></script> <script> var container = document.getElementById("waterfall"); var templateStr = document.getElementById("grid_template").innerHTML; var $loading = $(".loading"); // 生成数据绑定函数 var template = _.template(templateStr); // 位置数组 var positionArr = [0, 0, 0]; gotoPage(1) // AJAX数据请求函数 function gotoPage(num) { $loading.hide(); $.get("json/json" + num + ".txt", function(data) { data = JSON.parse(data); // 没有数据则直接退出 console.log(data.news) if (data.news.length == 0) { $loading.show().html("没有更多了"); return; } _.each(data.news, function(dictionary) { var IMG = new Image(); IMG.src = dictionary.imgurl; // 那张图先加载完渲染那张 $(IMG).load(function() { var templateStr = template(dictionary); var $grid = $(templateStr); var minValue = _.min(positionArr); var index = _.indexOf(positionArr, minValue); $(container).append($grid); $grid.css({ "left": index * 270, "top": minValue, }) positionArr[index] += $grid.innerHeight() + 20; $(container).height($(document).height()) $loading.hide(); }) }) lock = true; }) } var lock = true; // 页面滚动加载流 var page = 0; $(window).scroll(function() { if (!lock) return; var ratio = $(window).scrollTop() / ($(document).height() - $(window).height()); if (ratio > 0.7) { lock = false; page++ gotoPage(page) } }) </script> </body> </html>
百度招聘分页查询
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"> <head> <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"> <title>Document</title> <style type="text/css"> * { margin: 0; padding: 0; } body { background-color: #ccc; } .content { background-color: white; width: 1100px; margin: 0 auto; font-size: 12px; padding: 20px; } .content .jobtable { position: relative; } .content .row { overflow: hidden; line-height: 40px; position: relative; } .content .hd { font-weight: bold; } .content .row .cols { overflow: hidden; position: relative; border-bottom: 1px solid #ccc; } .content .row .col { float: left; } .content .row .col1 { width: 30%; } .content .row .col2 { width: 15%; } .content .row .col3 { width: 20%; } .content .row .col4 { width: 20%; } .content .row .col3 { width: 15%; } .content .info { overflow: hidden; font-size: 16px; line-height: 32px; } .content .info h3 { float: left; } .content .info p { float: right; } .content .row_btn { position: absolute; width: 21px; height: 10px; background-color: red; right: 10px; top: 50%; margin-top: -5px; background: url(images/banner-icon.png) -28px -146px; cursor: pointer; } .content .row_btn.up { background-position: -2px -145px; } .content .detail { display: none; border-bottom: 1px solid #ccc; } .content .pageNav { margin-top: 10px; width: 259px; margin: 20px auto; } .content .pageNav ul { list-style: none; overflow: hidden; } .content .pageNav ul li { float: left; width: 30px; height: 30px; line-height: 30px; text-align: center; margin-right: 5px; cursor: pointer; border: 1px solid #ccc; } .content .pageNav ul li.cur { background-color: yellowgreen; } .mask { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: url(images/busy.gif) no-repeat center center; z-index: 999; display: none; } </style> </head> <body> <div class="content"> <div class="info"> <h3>职位信息</h3> <p>一共有444个职位</p> </div> <div class="jobtable" id="jobtable"> <div class="mask"></div> <div class="row hd"> <div class="cols"> <div class="col col1">职位名称</div> <div class="col col2">职位类别</div> <div class="col col3">工作地点</div> <div class="col col4">招聘人数</div> <div class="col col5">更新时间</div> </div> </div> </div> <div class="pageNav"> <ul> <li class="cur"></li> <li></li> <li></li> <li></li> <li></li> <li></li> <li></li> </ul> </div> </div> <script type="text/template" id="row_template"> <div class="row"> <div class="cols"> <div class="col col1"> <a href='<%=link%>'> <%=name%> </a> </div> <div class="col col2"> <%=postType%> </div> <div class="col col3"> <%=workPlace%> </div> <div class="col col4"> <%=recruitNum%> </div> <div class="col col5"> <%=publishDate%> </div> <div class="row_btn"></div> </div> <div class="detail"> <div className="serviceCondition"> <h5>入职资格: </h5> <%=serviceCondition%> </div> <div className="workContent"> <h5>工作内容: </h5> <%=workContent%> </div> </div> </div> </script> <script type="text/javascript" src="js/jquery-1.12.3.min.js"></script> <script type="text/javascript" src="js/underscore.js"></script> <script> // 生成数据绑定函数 var templateDataFun = _.template($("#row_template").html()); // 设置截流开关 var lock = true; // 封装tabRow类 function RowCell(dictionary) { this.dictionary = dictionary; // 修正数据对象 this.dictionary.link = "http://talent.baidu.com/external/baidu/index.html#/jobDetail/2/" + this.dictionary.postId; // 绑定DOM元素 this.$rowDom = $(templateDataFun(dictionary)); this.$btn = this.$rowDom.find(".row_btn"); this.$detail = this.$rowDom.find(".detail"); // 详情页开关状态 this.state = 0; this.bindEvent(); this.render(); // 将每个实例存放入rowList管理 rowList.addRow(this); } // 渲染方法 RowCell.prototype.render = function() { rowList.$dom.append(this.$rowDom) }; // 给按钮绑定事件 RowCell.prototype.bindEvent = function() { var self = this; // 点击事件 this.$btn.click(function() { if (self.state == 0) { self.$btn.addClass("up"); self.$detail.stop(true).slideDown(); self.state = 1; } else { self.$btn.removeClass("up"); self.$detail.stop(true).slideUp(); self.state = 0; } }) }; // 清理DOM RowCell.prototype.kill = function() { this.$rowDom.remove(); }; // 封装rowTable类作为中介者 function RowTable() { this.$dom = $("#jobtable") this.rowArr = []; this.navPage = new NavPage(); // 获取当前页面hash值 if (window.location.hash == "") { window.location.hash = 1; } var pageNum = window.location.hash.substring(1); this.getPage(pageNum); }; // 向服务器发起数据请求 RowTable.prototype.getPage = function(num) { lock = false; var self = this; $.get("JSON/getPostList" + num, function(data) { data = JSON.parse(data); self.navPage.upDate(num, data.totalPage); // 先清理 _.each(self.rowArr, function(v) { v.kill(); }) // 后渲染 _.each(data.postList, function(v, i) { new RowCell(v) }); lock = true; }) }; // 添加实例到数组 RowTable.prototype.addRow = function(obj) { this.rowArr.push(obj); } // 分页导航类 function NavPage(currentPage, totalPage) { this.currentPage = currentPage; this.totalPage = totalPage; this.$pageList = $(".pageNav li"); this.render(); this.bindEvent(); } // 分页导航渲染 NavPage.prototype.render = function() { if (this.currentPage < 4) { this.$pageList.eq(0).html(1); this.$pageList.eq(1).html(2); this.$pageList.eq(2).html(3); this.$pageList.eq(3).html(4); this.$pageList.eq(4).html("..."); this.$pageList.eq(5).html(this.totalPage - 1); this.$pageList.eq(6).html(this.totalPage); this.$pageList.eq(this.currentPage - 1).addClass("cur").siblings().removeClass("cur"); } else if (this.currentPage > this.totalPage - 3) { this.$pageList.eq(0).html(1); this.$pageList.eq(1).html(2); this.$pageList.eq(2).html("..."); this.$pageList.eq(3).html(this.totalPage - 3); this.$pageList.eq(4).html(this.totalPage - 2); this.$pageList.eq(5).html(this.totalPage - 1); this.$pageList.eq(6).html(this.totalPage); this.$pageList.eq(this.currentPage - this.totalPage - 1).addClass("cur").siblings().removeClass("cur"); } else { this.$pageList.eq(0).html(1); this.$pageList.eq(1).html("..."); this.$pageList.eq(2).html(this.currentPage - 1); this.$pageList.eq(3).html(this.currentPage); this.$pageList.eq(4).html(this.currentPage + 1); this.$pageList.eq(5).html("..."); this.$pageList.eq(6).html(this.totalPage); this.$pageList.eq(3).addClass("cur").siblings().removeClass("cur"); } } // 分页导航更新 NavPage.prototype.upDate = function(currentPage, totalPage) { this.currentPage = currentPage; this.totalPage = totalPage; this.render(); } // 分页导航事件绑定 NavPage.prototype.bindEvent = function() { var self = this; this.$pageList.click(function() { if (!lock) return; if ($(this).html() == "...") return; window.location.hash = $(this).html(); self.currentPage = parseInt($(this).html()); self.render(); rowList.getPage(self.currentPage); }) } var rowList = new RowTable(); </script> </body> </html>