关于ajax学习笔记

作者: dnaEMx | 来源:发表于2018-08-19 13:11 被阅读8次

    一、什么是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是异步执行的,如图所示,异步执行不会阻塞.
    image image

    二、ajax 的执行过程

    1. 创建XMLHttpRequest对象,也就是创建一个异步调用对象
    2. 创建一个新的HTTP请求,并指定该HTTP请求的方法、URL及验证信息
    3. 设置响应HTTP请求状态变化的函数
    4. 发送HTTP请求
    5. 获取异步调用返回的数据
    6. 使用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服务器遇到一个错误,使其无法对请求提供服务
    

    这是比较齐全的状态表:

    image

    四、关于函数封装(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 是其中一种解决办法。

    微信:地址
    blog:地址

    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个地方:

    1. 滚动到底部判断(包含视口的底部和总的底部)
    2. 瀑布流里面的内容需要错位显示

    8.1 滚动到底部判断

    我们需要知道:

    • 总文档高度
    • 已经滚动的高度
    • 视口高度,通过$(document).height(); 获取,视口底部来触发ajax 获取下一页的数据
    • 总文档高度-已经卷动高度-视口高度 < 200 基本上就是滚动到底了,滚动到文档底部就停止 ajax 请求。
    image
    • scroll事件,一定是要截流的。因为用户滚一个鼠标滚轮的“小咯噔”就触发一次scroll事件;滑动滚动条的时候,是每一像素触发一次这个事件。还有pageDown、下箭头按钮,都能触发scroll事件。
    • 如何判断文章是否到头,说白了前端开发工程师不知道一共有多少页。比如今天又53页,明天就有55页了,所以你的JS里面无法写死一个文章总页数。所以办法就是,请求下去,请求到page.php?pagenum=54的时候,发现终止标记,或者这个页面返回的json是空,就表示到头了。

    8.2 瀑布流里面的内容需要错位显示

    image
    • 这里分成三列瀑布流,组成一个数组管理
      • 这个数组会不断计算三列之中的最小值
      • 然后按照每次的最小值进行高度插入
    • 图片判断是否加载完成需要用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;
                    });
                }
            });
        }
    

    相关文章

      网友评论

        本文标题:关于ajax学习笔记

        本文链接:https://www.haomeiwen.com/subject/mdjciftx.html