美文网首页
前端性能优化实践:Ajax请求优化新体验

前端性能优化实践:Ajax请求优化新体验

作者: 我是云小梦 | 来源:发表于2020-06-18 09:31 被阅读0次

    说起来ajax,相信诸位都不会陌生 —— 这可是当下前端最火的技术之一。

    ajax请求数据,将数据拿到前端页面上,通过一定手段展示给用户,造成“不必刷新页面”的局部数据刷新。这是ajax最主要的功能。
    一直以来使用ajax都是“横冲直撞”:不管三七二十一,调用就完了。拿到数据后也是直接放到页面区域上

    ——一般来说没有什么问题,直到遇见了分页:一个大数据量重复请求的场景。

    于是,我们对ajax的使用做了优化:
    (本文所说皆基于“切换分页”场景)


    fg

    ajax分页缓存

    这个可是个“明星人物”。它基于这样一个背景:当你点击某一页时,网站会想后台发一个请求,接收到返回数据后跳过去(局部刷新)。这时,如果没有设置缓存,那么原来页面的数据就不会被浏览器“记住”,当你返回这个页面时浏览器会再一次发送请求,甚至当你中途退出后再进来,又回到原来的下标了。这大大加重了浏览器和服务器的负担!

    对如下场景:

    <div class="wrap flex_column">
        <div class="content flex_column"></div>
        <div class="page">
            <ul class="flex_row">
                <!-- <li></li> --> <!-- 这里实际是后端动态渲染列表 -->
            </ul>
        </div>
    </div>
    

    即是要缓存,我们在获取元素时就要设置【缓存对象】:

    var oContent=document.querySelector('.content');
    var oPages=document.querySelector('.page ul li');
    
    var cache={};
    changePage();   //运行“检查数据”函数
    

    检查数据函数的代码如下:

    function changePage(){
        for(let i=0,len=oPages.length;i<len;i++){  //这里实际要换成从后端ajax过来的长度
            if(oPages[i] in cache){
                console.log('已经存在了数据');
                //...
            }else{
                console.log('数据还没有,正在加载中...');
                //...
            }
            console.log(cache);
        }
    }
    

    可以看到,我们在函数中先进行判断:当前列表项是否已经在缓存中:如果在缓存中了,那我们就不必再发送ajax请求数据 —— 那样会增加服务器的压力,而是直接从缓存中拿到数据:
    在上面代码if中,增加:addDom(cache[i]);

    function addDom(result){
        var dataList=result;
        var dataLength=dataList.length;
        var str='';
        for(var i=0;i<dataLength;i++){
            str+=`
                <a href="${dataList[i].url}" class="items flex_row">
                    <div class="img">
                        <img src="xxx" alt="" />
                    </div>
                    <div class="bd">
                        <p class="label">${dataList[i].title}</p>
                    </div>
                </a>`
        }
        oContent.innerHTML=str;
    }
    

    反之,我们就要发送ajax从服务器拿到数据:
    在else中增加代码 goTo(page);

    function goTo(page){
        Ajax({
            url:'xxxx',
            method:'GET',
            data:{
                //...
                page
            },
            success:function(res){
                var result=JSON.parse(res);
                var dataList=result.showapi_res_body.newslist;
                //先获取到我们的数据数组
                addDom(dataList);
                cache[page]=dataList;
            }
        });
    }
    

    这个思路在“切换分页”这个场景下用的非常多 —— 每次切换时都要将当前页的数据存到缓存中,每次切换时都先去看一下要去的“上一页”或“下一页”的数据是否在缓存中(之前是否切换到此也过),做不同的处理。

    fg

    使用H5的history改善Ajax列表请求

    既然HTML5的众多API都是基于浏览器原生的,那么我们从原生的角度来看一下请求数据加载:
    当前在点击“下一页”时,大部分网页都采用了动态请求的方式,避免页面刷新。虽然大家都是ajax,但是从一些小的细节还是可以看出其优劣:比如是否支持浏览器“后退”和“前进”键(左上角的两个按钮)。

    数据分页显示,早起的做法是在网址后面加个page的参数:在点击“下一页”时,让网页重定向到page+1的新地址。例如之前新浪的新闻网就是这么做的。但是这个列表并不是页面的主体部分!或者说页面的其他部分也有很多图片等丰富的元素,例如导航slider —— 再使用这样的方式,整个页面会闪烁的很厉害,并且很多资源得重新渲染。
    但是话说回来,普通的动态请求不会使网址发生变化。用户点击了下一页,或者点击了第几页,想返回到上一个页面时,可能去点浏览器的返回键,这样就导致了返回的不是原先查看的页面信息,而是上一个网址了,或者想刷新一下,发现回到了第一页。
    下面以笔者所做的一个案例进行分析:

    var pageIndex=0;
    function makePage(pageIndex){
        var request=new XMLHttpRequest();
        request.open("GET","/getBook?page="+pageIndex+"&limit=8",true);
        request.send(null);
        request.onreadystatechange=statechange;
        
        function statechange(){
            if(this.readyState == 4 && this.status == 200){
                var books=JSON.parse(request.responseText);
                renderPage(books);   //渲染数据
            }
        }
    }
    

    拿到数据后进行渲染:

    function renderPage(books){
        var bookHtml=`<table>`;
        for(let i in books){
            bookHtml+=`<tr><td>${books[i].name}</td><td>${books[i].author}</td></tr>`;
        }
        bookHtml+=`</table>`;
        bookHtml+=`<button>上一页</button>
                    <button onclick="nextPage()">下一页</button>`;
        var section=document.createElement("section");
        section.innerHTML=bookHtml;
        document.getElementById("book").appendChild(section);
    }
    

    这样一个基本的ajax请求就搭起来了,然后就是我们分析的重点:“下一页”按钮:

    function nextPage(){
        pageIndex++;
        makeRequest(pageIndex);
    }
    

    到此,如果不做任何处理的话,就不能够发挥浏览器返回、前进按钮的作用。
    如果能够检测到用户点了后退、前进按钮的话,就可以做些文章:HTML5就有这么一个事件 window.onpopstate ,当用户单击前进后退按钮就会触发这个事件 —— 但是光这样还不够,还得传些参数,因为这个事件只能检测到你手动添加上去的url,而且我们返回到之前那个页面时得知道那个页面的pageIndex。HTML5里也有这样一个函数pushState:

    window.history.pushState(state,title,url);
    

    其中state是一个object,为当前页面的数据。title没有什么用。url为当前页面的url —— 一旦更改了这个url,浏览器地址栏的地址也会跟着变化(但页面不会刷新)。

    还有就是页面load事件未触发之前,点击后退也不会触发popstate事件

    于是我们这样做:

    function nextPage(){
        pageIndex++;
        makeRequest(pageIndex);
        window.history.pushState({page:pageIndex},null,window.location.href);
    }
    

    然后去监听popstate事件:

    window.addEventListener("popstate",function(event){
        var page=0;
        //state数据通过event传进来,这样就可以得到pageIndex
        if(event.state !== null){
            page=event.state.page;
        }
        makeRequest(page);
        pageIndex=page;
    })
    

    这样就会发现,按钮被激活了。

    到这里就基本完工了。但是我们发现:在第二页点击刷新的话,首先会出现第一页,点击下一页,出现第二页。然后点返回按钮,发现还是第二页,再次点击时才会回到第一页。

    打开控制台,会发现:点第一次返回时获取到的pageIndex仍然是1,即第二页。
    我们可以理解为:对history,浏览器有一个队列,用来存放访问的记录,包括每个访问的网址还有state数据。一开始打开页面,队列的首指针指向page=0的位置,点下一页时,执行了pushState,在这个队列插入了一个元素,队首指针移向了page=1的位置。同时通过pushState操作记录了这个元素的url和state数据。
    从这里可以看出,pushState最重要的作用还是给history队列插入元素,这样浏览器的后退按钮才不是置灰的状态,其次才是上面所说的存放数据。

    那么当前我们最重要的就是“及时保存(缓存)数据 & 更换pageIndex”:
    我们整合一下所用到的函数:

    var pageIndex=window.localStorage.pageIndex || 0;
    
    function nextPage(){
        window.localStorage,pageIndex=++pageIndex;
        makeRequest(pageIndex);
        window.history.pushState({page:pageIndex},null,window.location.href);
    }
    
    window.addEventListener("popstate",function(event){
        var page=0;
        //state数据通过event传进来,这样就可以得到pageIndex
        if(event.state !== null){
            page=event.state.page;
        }
        makeRequest(page);
        window.localStorage,pageIndex=page;
    })
    

    在改变pageIndex的同时,将其放到localStorage里,这样刷新页面时就可以获取到当前页的pageIndex。

    还有一种方法是将其放到url参数里:通过改变当前网址。pageIndex从网址里面取:

    var pageData=window.location.search.match(/page=([^&#]+)/);
    
    var pageIndex=pageData ? +pageData[1] : 0;
    function nextPage(){
        ++pageIndex;
        makeRequest(pageIndex);
        window.history.pushState({page:pageIndex},null,"?page="+pageIndex);
    }
    

    这样的好处在于,链接会跟着变,带着pageIndex参数的链接无论是前端渲染还是服务端渲染都可以实现。并且分享给别人时,打开页面也会直接看到同步的数据(比如跳转到第几页的数据页面)。

    使用localStorage,唯一担心的不过就是用户开启了隐身/无痕模式(对localStorage的禁用)。


    本文笔者原发于csdn: https://yunxiaomeng.blog.csdn.net/article/details/106747078
    转载请注明出处!

    相关文章

      网友评论

          本文标题:前端性能优化实践:Ajax请求优化新体验

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