按需加载

本文深入探讨了按需加载技术在前端性能优化中的应用,包括按需加载HTML、图片、JS的实现原理及示例代码,以及如何在不同场景下实现分屏展示与懒惰加载。通过实例展示了如何合理选择触发动作,有效减少资源请求,提升用户体验。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

  按需加载是前端性能优化中的一项重要措施,按需加载是如何定义的呢?顾名思义,指的是当用户触发了动作时才加载对应的功能。触发的动作,是要看具体的业务场景而言,包括但不限于以下几个情况:鼠标点击、输入文字、拉动滚动条,鼠标移动、窗口大小更改等。加载的文件,可以是JS、图片、CSS、HTML等。后面将会详细介绍“按需”的理解。

         按需解析HTML


        现在我们很多时候,看到如图所示,采用<script type="text/template"></script>的js模板,以前一直不懂其中的奇淫技巧,其实呀,他就是防止浏览器初始解析,我们通过触发来解析html,达到按需加载,优化页面的效果。

        按需解析HTML,就是页面一开始不解析HTML,根据需要来解析HTML。解析HTML都是需要一定时间,特别是HTML中包含有img标签、引用了背景图片时,如果一开始就解析,那么势必会增加请求数。常见的有对话框、拉菜单、多标签的内容展示等,这些一开始是不需要解析,可以按需解析。实现按需解析,首先用 html 这个标签来对忽略对HTML的解析 然后根据触发的动作,把script里面的HTML获取出来,填充到对应的节点中。
        
        示例代码如下:

        我们一起来看下demo,当运行 demo 并抓包发现:当页面加载结束时,并没有看到图片的请求;当点“点我展示HTML”按钮时,通过抓包发现有图片请求。
运行代码 复制代码 保存代码 提示:您可以先修改部分代码再运行! power by W3Cfuns.com

        曾经做个demo并经过测试发现,如果是直接解析HTML(不包含有请求CSS图片和img标签),耗费的时间要比用<script type=”text x-template”="">html大约慢1-2倍,如果是还包括请求有CSS图片、img标签,请求连接数将会更多,可见按需解析HTML,对性能提升还是有一定效果。

         按需加载图片

        按需加载图片,就是让图片默认开始不加载,而且在接近可视区域范围时,再进行加载。也称之为懒惰加载。大家都知道,图片一下子全部都加载,请求的次数将会增加,势必影响性能。

        先来看下懒惰加载的实现原理。 它的触发动作是:当滚动条拉动到某个位置时,即将进入可视范围的图片需要加载 。实现的过程分为下面几个步骤:

        ① 生成<img data-src=”url”>标签时,用data-src来保存图片地址;
        ②记录的图片data-src都保存到数组里;
        ③对滚动条进行事件绑定,假设绑定的函数为function lazyload(){};
        ④在函数lazyload中,按照下面思路实现:计算图片的Y坐标,并计算可视区域的高度height,当Y小于等于(height+ scrollTop)时,图片的src的值用data-src的来替换,从而来实现图片的按需加载;

        下面看一个示例代码:
  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
  5. <title>图片按需加载</title>
  6. </head>
  7. <body>
  8.     <style>
  9.         li {float:left;width:200px;}
  10.     </style>
  11.     <div style="widh:100%;height:1200px;border:1px solid #000">这里是空白的内容,高度为900像素,目的是方便出现滚动条</div>
  12.     <ul style="width:600px;">
  13.         <li> <img width="158" height="158" data-src="http://pic6.nipic.com/20100423/4537140_133035029164_2.jpg"  /> </li>
  14.         <li> <img width="158" height="158" data-src="http://www.jiujiuba.com/xxpict2/picnews/62223245.jpg"  /> </li>
  15.         <li> <img width="158" height="158" data-src="http://www.bz55.com/uploads/allimg/100729/095KS455-0.jpg"  /> </li>
  16.         <li> <img width="158" height="158" data-src="http://www.hyrc.cn/upfile/3/200611/1123539053c7e.jpg"/> </li>
  17.         <li> <img width="158" height="158" data-src="http://www.mjjq.com/blog/photos/Image/mjjq-photos-903.jpg" /> </li>
  18.         <li> <img width="158" height="158" data-src="http://c.cncnimg.cn/000/954/1416_2_b.jpg"  /> </li>
  19.         <li> <img width="158" height="158" data-src="http://www.jiujiuba.com/xxpict2/picnews/62223231.jpg" /> </li>
  20.         <li> <img width="158" height="158" data-src="http://www.mjjq.com/pic/20070530/20070530043314558.jpg" /> </li>
  21.     </ul>
  22.     <script>
  23.         var API = {   
  24.             /**
  25.               * 兼容Mozilla(attachEvent)和IE(addEventListener)的on事件
  26.               * @param {String} element DOM对象 例如:window,li等
  27.               * @param {String} type on事件类型,例如:onclick,onscroll等
  28.               * @param {Function} handler 回调事件
  29.               */
  30.             on: function(element, type, handler) {
  31.                 return element.addEventListener ? element.addEventListener(type, handler, false) : element.attachEvent('on' + type, handler)
  32.             },   
  33.             /**
  34.               * 为对象绑定事件
  35.               * @param {Object} object 对象
  36.               * @param {Function} handler 回调事件
  37.               */
  38.             bind: function(object, handler) {
  39.                 return function() {
  40.                     return handler.apply(object, arguments)
  41.                 }
  42.             },   
  43.             /**
  44.               * 元素在页面中X轴的位置
  45.               * @param {String} element DOM对象 例如:window,li等
  46.               */
  47.             pageX: function(El) {
  48.                 var left = 0;
  49.                  do {
  50.                     left += El.offsetLeft;

  51.                 } while(El.offsetParent && (El = El.offsetParent).nodeName.toUpperCase() != 'BODY');
  52.                 return left;

  53.             },   
  54.             /**
  55.               * 元素在页面中Y轴的位置
  56.               * @param {String} element DOM对象 例如:window,li等
  57.               */
  58.             pageY: function(El) {
  59.                 var top = 0;
  60.                  do {
  61.                     top += El.offsetTop;

  62.                 } while(El.offsetParent && (El = El.offsetParent).nodeName.toUpperCase() != 'BODY');
  63.                 return top;

  64.             },   
  65.             /**
  66.               * 判断图片是否已加载
  67.               * @param {String} element DOM对象 例如:window,li等
  68.               * @param {String} className 样式名称
  69.               * @return {Boolean} 布尔值
  70.               */
  71.             hasClass: function(element, className) {
  72.                 return new RegExp('(^|\\s)' + className + '(<a href="$)').test(element.className">\\s|$)').test(element.className</a>)
  73.             },   
  74.             /**
  75.               * 获取或者设置当前元素的属性值
  76.               * @param {String} element DOM对象 例如:window,li等
  77.               * @param {String} attr 属性
  78.               * @param {String} (value) 属性的值,此参数如果没有那么就是获取属性值,此参数如果存在那么就是设置属性值
  79.               */
  80.             attr: function(element, attr, value) {
  81.                  if (arguments.length == 2) {
  82.                     return element.attributes[attr] ? element.attributes[attr].nodeValue : undefined
  83.                 }
  84.                 else if (arguments.length == 3) {
  85.                     element.setAttribute(attr, value)
  86.                 }
  87.             }
  88.         };

  89.         /**
  90.           * 按需加载
  91.           * @param {String} obj 图片区域元素ID
  92.           */
  93.         function lazyload(obj) {
  94.             this.lazy = typeof obj === 'string' ? document.getElementById(obj) : document.getElementsByTagName('body')[0];
  95.             this.aImg = this.lazy.getElementsByTagName('img');
  96.             this.fnLoad = API.bind(this, this.load);
  97.             this.load();
  98.             API.on(window, 'scroll', this.fnLoad);
  99.             API.on(window, 'resize', this.fnLoad);

  100.         }
  101.         lazyload.prototype = {

  102.             /**
  103.               * 执行按需加载图片,并将已加载的图片标记为已加载
  104.               * @return 无
  105.               */
  106.             load: function() {
  107.                 var iScrollTop = document.documentElement.scrollTop || document.body.scrollTop;
  108.                 // 屏幕上边缘
  109.                 var iClientHeight = document.documentElement.clientHeight + iScrollTop;
  110.                 // 屏幕下边缘
  111.                 var i = 0;
  112.                 var aParent = [];
  113.                 var oParent = null;
  114.                 var iTop = 0;
  115.                 var iBottom = 0;
  116.                 var aNotLoaded = this.loaded(0);
  117.                  if (this.loaded(1).length != this.aImg.length) {
  118.                     var notLoadedLen = aNotLoaded.length;
  119.                      for (i = 0; i < notLoadedLen; i++) {
  120.                         iTop = API.pageY(aNotLoaded[i]) - 200;
  121.                         iBottom = API.pageY(aNotLoaded[i]) + aNotLoaded[i].offsetHeight + 200;
  122.                         var isTopArea = (iTop > iScrollTop && iTop < iClientHeight) ? true : false;
  123.                         var isBottomArea = (iBottom > iScrollTop && iBottom < iClientHeight) ? true : false;
  124.                          if (isTopArea || isBottomArea) {                   
  125.                             // 把预存在自定义属性中的真实图片地址赋给src
  126.                             aNotLoaded[i].src = API.attr(aNotLoaded[i], 'data-src') || aNotLoaded[i].src;
  127.                              if (!API.hasClass(aNotLoaded[i], 'loaded')) {
  128.                                  if ('' != aNotLoaded[i].className) {
  129.                                     aNotLoaded[i].className = aNotLoaded[i].className.concat(" loaded");

  130.                                 }
  131.                                 else {
  132.                                     aNotLoaded[i].className = 'loaded';

  133.                                 }
  134.                             }
  135.                         }
  136.                     }
  137.                 }
  138.             },

  139.             /**
  140.               * 已加载或者未加载的图片数组
  141.               * @param {Number} status 图片是否已加载的状态,0代表未加载,1代表已加载
  142.               * @return Array 返回已加载或者未加载的图片数组
  143.               */
  144.             loaded: function(status) {
  145.                 var array = [];
  146.                 var i = 0;
  147.                  for (i = 0; i < this.aImg.length; i++) {
  148.                     var hasClass = API.hasClass(this.aImg[i], 'loaded');
  149.                      if (!status) {
  150.                         if (!hasClass)
  151.                             array.push(this.aImg[i])
  152.                     }
  153.                     if (status) {
  154.                         if (hasClass)
  155.                             array.push(this.aImg[i])
  156.                     }
  157.                 }
  158.                 return array;       
  159.             }
  160.         };
  161.         // 按需加载初始化
  162.         API.on(window, 'load', function () {new lazyload()});
  163.     </script>
  164. </body>
  165. </html>
复制代码

        运行上述的示例代码,并抓包会发现:一开始并没有看到图片的请求,但当拉动滚动条到页面下面时,将会看到图片发送请求。目前很多框架都已经支持图片的懒惰加载,平时在开发中,大家可以对图片实现懒惰加载,这是有效提升性能的一个方法,特别是网页图片比较多时,更加应该使用该方法。

        按需加载除了上述场景外,还有更多的场景。如下图:

        

        页面一开始,加载的是“全部”标签里面的内容,但在点击“指定商品折扣券”标签时,才去加载对应的图片。实现思路如下:

        ①生成<img data-src=”url”>标签时,用data-src来保存图片地址;
        ②在点击标签事件时,获取所有图片,图片的src的值用data-src的来替换;

        示例代码如下:
  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
  5. <title>标签按需加载</title>
  6. </head>

  7. <body>
  8.     <style>
  9.         ul li {width:200px;height:30px;float:left; list-style:none; text-align:center;}
  10.         .on{border-top:1px solid #3FF;border-left:1px solid #3FF;border-right:1px solid #3FF; }
  11.         .hide{display:none;}
  12.     </style>
  13.     <ul>
  14.         <li>全部</li>
  15.         <li id="viewTsb1" onclick="showTabContent()">指定商品折扣券</li>
  16.     </ul>
  17.     <div style="width:800px;height:500px;clear:both;">
  18.         <div id="tab1" style="height:25px; line-height:25px; margin:50px 0 0 40px">全部标签应该展示所有内容</div>
  19.         <div id="tab2">
  20.              <img width="158" height="158" data-src="http://img2.114msn.com/jindian/20081071153761308.jpg"  />
  21.              <img width="158" height="158" data-src="http://www.mjjq.com/blog/photos/Image/mjjq-photos-900.jpg"  />
  22.          </div>
  23.     </div>
  24.     <script>
  25.         var isLoadedImg = false;
  26.         function showTabContent(){ 
  27.             if(isLoadedImg){
  28.                 return;
  29.             }
  30.             var elem = document.getElementById("tab2");
  31.             document.getElementById("tab1").className="hide";
  32.             elem.className="";
  33.             var arrImage = elem.getElementsByTagName("img");
  34.             var l = arrImage.length;
  35.             for(var i=0;i<l;i++){
  36.                 arrImage[i].src = arrImage[i].getAttribute('data-src');
  37.             }
  38.             isLoadedImg = true;
  39.             //todo 更改标签状态
  40.         }
  41.     </script>
  42. </body>
  43. </html>
复制代码

        运行上述代码并抓包并发现:一开始没有看到有图片的请求,但点击“指定商品折扣券”标签时,看到有图片的请求发送。需要注意的是,为了确保体验,首屏的图片不建议懒惰加载,而应该直接展示出来;避免一开始用户就无法看到图片,在IE下看到一个虚线框,这样体验反而不好。

         按需执行JS

        按需执行JS和懒惰加载图片比较类似。当打开网页时,如果等所有JS都加载并执行完毕,再把界面呈现给用户,这样整体上性能会比较慢,体验也不友好。就是当某个动作触发后,再执行相应的JS,以便来渲染界面。按需执行JS,可以应用在下列场景: 执行一些耗时比较久的JS代码,或执行JS后,需要加载比较多图片、加载iframe、加载广告等 。在一些webapp的应用中,或比较复杂的页面时,更加应该使用这种方法。

        实现思路和按需加载比较类似:

        ① 对滚动条进行事件绑定,假设绑定的函数为function lazyExecuteJS(){};
        ②在函数lazyExecuteJS中,按照下面思路实现:选择一个元素作为参照物,当滚动条即将靠近时该元素位置,开始执行对应的JS,从而实现对界面的渲染;

        示例代码如下(以YUI3框架为例):
        
        首先下载最近封装的异步滚动条加载组件: Y.asyncScrollLoader ,然后运行下面的代码(需要把页面和Y.asyncScrollLoader.js 放在同一个目录):
  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
  5. <title>按需执行JS</title>
  6. </head>
  7. <script src="http://yui.yahooapis.com/3.2.0/build/yui/yui-debug.js"></script>
  8. <body>
  9.     <div style="height:1500px"><span style="">向下拖动滚动条,拉到广告条的位置,将会自动加载广告的内容!</span></div>
  10.     <div id="ADContent">这里是广告的内容,将会用JS进行填充</div>
  11.     <script>
  12.         YUI({modules:{'asyncScrollLoader': { 
  13.             fullpath: 'Y.asyncScrollLoader.js', 
  14.                 type: 'js', 
  15.                 requires:['widget'] 
  16.             } 
  17.         }}).use('widget','asyncScrollLoader', "node", function(Y){
  18.             var  loadAD = function(){
  19.                 //这里可以用简单的代码代替,实际项目中,可以执行任何JS代码,如请求CGI,或者广告数据,然后再填充到页面上
  20.                 var html = '<div><img src="http://t2.baidu.com/it/u=2027994158,3223530939&fm=23&gp=0.jpg" alt="" /><span>哈哈,我也是动态加载进来的,可以从html结构或抓包看出\效果哈!</span><\/div>'
  21.                 Y.one('#ADContent').set('innerHTML',html);
  22.             }

  23.             var cfg = { 
  24.                 'elementName' : 'div', 
  25.                 'className'     : 'lazy-load', 
  26.                 'contentAttribute' : '' ,
  27.                 'foldDistance': 10,
  28.                 'obCallback' : {
  29.                     'funName' : loadAD,
  30.                     'argument' : [],
  31.                     'context' : this
  32.                 }
  33.             };

  34.            new Y.asyncScrollLoader(cfg).renderer(); 
  35.         });      
  36.     </script>
  37. </body>
  38. </html>
复制代码


        运行上述代码并抓包发现:打开页面时,是不没有看到有对应的图片请求,但当滚动条拉到一定位置时,loadAD的函数被执行。

         按需加载JS

        JavaScript无非就是script标签引入页面,但当项目越来越大的时候,单页面引入N个js显然不行,合并为单个文件减少了请求数,但请求的文件体积却很大。这时候比较合理的做法就是按需加载。按需加载和按需执行JS比较类似,只不过要执行的JS变成了固定的“实现加载JS”的代码。按需加载实现的思路如下:

        ①对滚动条进行事件绑定,假设绑定的函数为function lazyLoadJS(){};

        ②在函数lazyLoadJS中,按照下面思路实现:选择一个元素作为参照物,当滚动条即将靠近时该元素位置,开始执行加载对应JS;

        ③在JS加载完毕后,开始执行相应的函数来渲染界面;

        ④在实际项目中,可以根据需要设置一个目标距离,比如还有200像素该元素即将进入可视区域;按需加载JS和按需执行JS比较类似,这里就不再单独提供示例代码了;大家可以在按需执行JS的中示例中,把loadAD函数更改为动态加载JS即可;

        分屏展示

        当一个网页比较长,有好几个屏幕,而且加载了大量的图片、广告等资源文件时,分屏展示,可提升页面性能和用户体验。其实分屏展示也可以从按需加载的的角度来看待,默认是加载第一屏幕的内容,当滚动条拉动即将到达下一个屏幕时,再开始渲染下个屏的内容。换言之,是把图片、背景图片、HTML一起按需加载,一开始不对HTML进行解析,那么背景图、img图片也不会进行加载。

        分屏展示的思路如下:

        ①根据具体业务情况,收集主流最大的分辨率的高度;假设这里是用960px;

        ②按照这个高度进行分屏,依次把下一个屏幕内的HTML用<textarea>HTML</’textarea> 来表示;

        ③为了让页面的高度不变,需要让textarea占据一定的页面空间,也就是让页面出现对应的滚动条;因此需要指定样式visility:hidden,并指定它的高度和宽度。

        ④利用上述讲的按需执行JS,把<textarea>HTML</’textarea>里面的HTML代码提取出来,重新填充到textarea的父节点上,便可实现解析对应HTML,从而实现分屏展示。

        示例代码如下(需要把页面和Y.asyncScrollLoader.js 放在同一个目录):

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
  5. <title>网页分屏展示</title>
  6. </head>
  7. <style>
  8.     .class2 {
  9.         visibility:hidden;
  10.         widh:100%;
  11.         height:300px;
  12.     }
  13. </style>
  14. <script src="http://yui.yahooapis.com/3.2.0/build/yui/yui-debug.js"></script>
  15. <body>
  16. <div style="height:1500px"><span style="">向下拖动滚动条,美图在下面!</span></div>
  17. <textarea>
  18.     <div>
  19.         <img src="http://travel.shangdu.com/liu/uploads/allimg/090407/1521221.jpg" alt="" /><span style="font-size:16px;color:red;">我是动态加载进来的,可以从html结构或抓包看出效果哈!</span>
  20.     </div>
  21. </textarea>
  22. <div style="height:800px"><span style="">下面继续上美图!</span></div>
  23. <textarea>
  24.     <div>
  25.         <img src="http://download.99sucai.com/upload/images/201006042009245.jpg"  alt="" /><span>哈哈,我也是动态加载进来的,可以从html结构或抓包看出效果哈!</span>
  26.     </div>
  27. </textarea>
  28. <script>
  29. YUI({modules:{'asyncScrollLoader': { 
  30.      fullpath: 'Y.asyncScrollLoader.js', 
  31.      type: 'js', 
  32.      requires:['widget'] 
  33.      } 
  34. }}).use('widget','asyncScrollLoader', "node", function(Y){
  35.      var cfg = { 
  36.         'elementName' : 'textarea', 
  37.         'className'     : 'class2', 
  38.         'contentAttribute' : 'value' ,
  39.         'foldDistance': 10           
  40.     };        
  41.     new Y.asyncScrollLoader(cfg).renderer(); 
  42. });      
  43. </script>
  44. </body>
  45. </html>
复制代码


        运行上面代码并抓包发现:在默认首屏,并没有去解析textarea里面的代码,但当拉动滚动条到一定位置时,textarea里面的HTML依次被解析,从而实现了网页分屏展示。上述的示例代码,可以在这里下载来查看: 示例代码

        使用“按需加载”进行性能优化时,需要合理选择触发的动作。“按需加载”的最大优势在于减少了不必要的资源请求,节省流量,真正实现“按需所取”。但是“按需加载”本身如果使用不当也会影响用户体验,因为“按需加载”的时机在用户触发某动作之后,如果用户的网速比较慢的话,加载脚本或执行脚本可能需要等候较长的时间,而用户则不得不为此付出代价。因此,如果要使用“按需加载”则需要选择正确的触发动作,如果是根据滚动条来触发,可考虑一个目标距离,假设目标距离还有200像素即将进入可视区域,则就开始加载,而不是等到进入了可视区域才加载。以上所讲的各种“按需加载”类型,都可以封装成相应的组件,然后就可以在项目中进行应用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值