按需加載是前端性能優化中的一項重要措施,按需加載是如何定義的呢?顧名思義,指的是當用戶觸發了動做時才加載對應的功能。觸發的動做,是要看具體的業務場景而言,包括但不限於如下幾個狀況:鼠標點擊、輸入文字、拉動滾動條,鼠標移動、窗口大小更改等。加載的文件,能夠是JS、圖片、CSS、HTML等。後面將會詳細介紹「按需」的理解。html
按需解析HTML前端
按需解析HTML,就是頁面一開始不解析HTML,根據須要來解析HTML。解析HTML都是須要必定時間,特別是HTML中包含有img標籤、引用了背景圖片時,若是一開始就解析,那麼勢必會增長請求數。常見的有對話框、拉菜單、多標籤的內容展現等,這些一開始是不須要解析,能夠按需解析。實現按需解析,首先用<script type=」text/x-template」>html</sccript> 這個標籤來對忽略對HTML的解析。而後根據觸發的動做,把script裏面的HTML獲取出來,填充到對應的節點中。node
示例代碼以下:web
<!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>按需解析HTML</title> </head> <body> <script type="text/x-template" id="suc_subscription"> <!--假設這裏的樣式box-dytz 引用了一張背景圖---> <div> <!--這裏暫且用這張圖片做爲測試,實際中,你們能夠替換爲任何圖片--> <img src="http://tid.tenpay.com/wp-content/uploads/2012/12/按需加載.jpg" /> </div> </script> <div id="success_dilog"></div> <input type="button" value="點我展現HTML" onclick="showHTML()" /> <script> function showHTML(){ document.getElementById('success_dilog').innerHTML = document.getElementById('suc_subscription').innerHTML; } </script> </body> </html>
咱們一塊兒來看下demo,當運行demo並抓包發現:當頁面加載結束時,並無看到圖片的請求;當點「點我展現HTML」按鈕時,經過抓包發現有圖片請求。api
曾經作個demo並通過測試發現,若是是直接解析HTML(不包含有請求CSS圖片和img標籤),耗費的時間要比用<script type=」text/x-template」>html</script>大約慢1-2倍,若是是還包括請求有CSS圖片、img標籤,請求鏈接數將會更多,可見按需解析HTML,對性能提高仍是有必定效果。數組
按需加載圖片性能優化
按需加載圖片,就是讓圖片默認開始不加載,並且在接近可視區域範圍時,再進行加載。也稱之爲懶惰加載。你們都知道,圖片一會兒所有都加載,請求的次數將會增長,勢必影響性能。併發
先來看下懶惰加載的實現原理。它的觸發動做是:當滾動條拉動到某個位置時,即將進入可視範圍的圖片須要加載。實現的過程分爲下面幾個步驟:app
下面看一個示例代碼:框架
<!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>圖片按需加載</title> </head> <body> <style> li {float:left;width:200px;} </style> <div style="widh:100%;height:1200px;border:1px solid #000">這裏是空白的內容,高度爲900像素,目的是方便出現滾動條</div> <ul style="width:600px;"> <li> <img width="158" height="158" data-src="http://pic6.nipic.com/20100423/4537140_133035029164_2.jpg" /> </li> <li> <img width="158" height="158" data-src="http://www.jiujiuba.com/xxpict2/picnews/62223245.jpg" /> </li> <li> <img width="158" height="158" data-src="http://www.bz55.com/uploads/allimg/100729/095KS455-0.jpg" /> </li> <li> <img width="158" height="158" data-src="http://www.hyrc.cn/upfile/3/200611/1123539053c7e.jpg"/> </li> <li> <img width="158" height="158" data-src="http://www.mjjq.com/blog/photos/Image/mjjq-photos-903.jpg" /> </li> <li> <img width="158" height="158" data-src="http://c.cncnimg.cn/000/954/1416_2_b.jpg" /> </li> <li> <img width="158" height="158" data-src="http://www.jiujiuba.com/xxpict2/picnews/62223231.jpg" /> </li> <li> <img width="158" height="158" data-src="http://www.mjjq.com/pic/20070530/20070530043314558.jpg" /> </li> </ul> <script> var API = { /** * 兼容Mozilla(attachEvent)和IE(addEventListener)的on事件 * @param {String} element DOM對象 例如:window,li等 * @param {String} type on事件類型,例如:onclick,onscroll等 * @param {Function} handler 回調事件 */ on: function(element, type, handler) { return element.addEventListener ? element.addEventListener(type, handler, false) : element.attachEvent('on' + type, handler) }, /** * 爲對象綁定事件 * @param {Object} object 對象 * @param {Function} handler 回調事件 */ bind: function(object, handler) { return function() { return handler.apply(object, arguments) } }, /** * 元素在頁面中X軸的位置 * @param {String} element DOM對象 例如:window,li等 */ pageX: function(El) { var left = 0; do { left += El.offsetLeft; } while(El.offsetParent && (El = El.offsetParent).nodeName.toUpperCase() != 'BODY'); return left; }, /** * 元素在頁面中Y軸的位置 * @param {String} element DOM對象 例如:window,li等 */ pageY: function(El) { var top = 0; do { top += El.offsetTop; } while(El.offsetParent && (El = El.offsetParent).nodeName.toUpperCase() != 'BODY'); return top; }, /** * 判斷圖片是否已加載 * @param {String} element DOM對象 例如:window,li等 * @param {String} className 樣式名稱 * @return {Boolean} 布爾值 */ hasClass: function(element, className) { return new RegExp('(^|\\s)' + className + '(\\s|$)').test(element.className) }, /** * 獲取或者設置當前元素的屬性值 * @param {String} element DOM對象 例如:window,li等 * @param {String} attr 屬性 * @param {String} (value) 屬性的值,此參數若是沒有那麼就是獲取屬性值,此參數若是存在那麼就是設置屬性值 */ attr: function(element, attr, value) { if (arguments.length == 2) { return element.attributes[attr] ? element.attributes[attr].nodeValue : undefined } else if (arguments.length == 3) { element.setAttribute(attr, value) } } }; /** * 按需加載 * @param {String} obj 圖片區域元素ID */ function lazyload(obj) { this.lazy = typeof obj === 'string' ? document.getElementById(obj) : document.getElementsByTagName('body')[0]; this.aImg = this.lazy.getElementsByTagName('img'); this.fnLoad = API.bind(this, this.load); this.load(); API.on(window, 'scroll', this.fnLoad); API.on(window, 'resize', this.fnLoad); } lazyload.prototype = { /** * 執行按需加載圖片,並將已加載的圖片標記爲已加載 * @return 無 */ load: function() { var iScrollTop = document.documentElement.scrollTop || document.body.scrollTop; // 屏幕上邊緣 var iClientHeight = document.documentElement.clientHeight + iScrollTop; // 屏幕下邊緣 var i = 0; var aParent = []; var oParent = null; var iTop = 0; var iBottom = 0; var aNotLoaded = this.loaded(0); if (this.loaded(1).length != this.aImg.length) { var notLoadedLen = aNotLoaded.length; for (i = 0; i < notLoadedLen; i++) { iTop = API.pageY(aNotLoaded[i]) - 200; iBottom = API.pageY(aNotLoaded[i]) + aNotLoaded[i].offsetHeight + 200; var isTopArea = (iTop > iScrollTop && iTop < iClientHeight) ? true : false; var isBottomArea = (iBottom > iScrollTop && iBottom < iClientHeight) ? true : false; if (isTopArea || isBottomArea) { // 把預存在自定義屬性中的真實圖片地址賦給src aNotLoaded[i].src = API.attr(aNotLoaded[i], 'data-src') || aNotLoaded[i].src; if (!API.hasClass(aNotLoaded[i], 'loaded')) { if ('' != aNotLoaded[i].className) { aNotLoaded[i].className = aNotLoaded[i].className.concat(" loaded"); } else { aNotLoaded[i].className = 'loaded'; } } } } } }, /** * 已加載或者未加載的圖片數組 * @param {Number} status 圖片是否已加載的狀態,0表明未加載,1表明已加載 * @return Array 返回已加載或者未加載的圖片數組 */ loaded: function(status) { var array = []; var i = 0; for (i = 0; i < this.aImg.length; i++) { var hasClass = API.hasClass(this.aImg[i], 'loaded'); if (!status) { if (!hasClass) array.push(this.aImg[i]) } if (status) { if (hasClass) array.push(this.aImg[i]) } } return array; } }; // 按需加載初始化 API.on(window, 'load', function () {new lazyload()}); </script> </body> </html>
運行上述的示例代碼,並抓包會發現:一開始並無看到圖片的請求,但當拉動滾動條到頁面下面時,將會看到圖片發送請求。目前不少框架都已經支持圖片的懶惰加載,平時在開發中,你們能夠對圖片實現懶惰加載,這是有效提高性能的一個方法,特別是網頁圖片比較多時,更加應該使用該方法。
按需加載除了上述場景外,還有更多的場景。以下圖:
頁面一開始,加載的是「所有」標籤裏面的內容,但在點擊「指定商品折扣券」標籤時,纔去加載對應的圖片。實現思路以下:
示例代碼以下:
<!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>標籤按需加載</title> </head> <body> <style> ul li {width:200px;height:30px;float:left; list-style:none; text-align:center;} .on{border-top:1px solid #3FF;border-left:1px solid #3FF;border-right:1px solid #3FF; } .hide{display:none;} </style> <ul> <li>所有</li> <li id="viewTsb1" onclick="showTabContent()">指定商品折扣券</li> </ul> <div style="width:800px;height:500px;clear:both;"> <div id="tab1" style="height:25px; line-height:25px; margin:50px 0 0 40px">所有標籤應該展現全部內容</div> <div id="tab2"> <img width="158" height="158" data-src="http://img2.114msn.com/jindian/20081071153761308.jpg" /> <img width="158" height="158" data-src="http://www.mjjq.com/blog/photos/Image/mjjq-photos-900.jpg" /> </div> </div> <script> var isLoadedImg = false; function showTabContent(){ if(isLoadedImg){ return; } var elem = document.getElementById("tab2"); document.getElementById("tab1").className="hide"; elem.className=""; var arrImage = elem.getElementsByTagName("img"); var l = arrImage.length; for(var i=0;i<l;i++){ arrImage[i].src = arrImage[i].getAttribute('data-src'); } isLoadedImg = true; //todo 更改標籤狀態 } </script> </body> </html>
運行上述代碼並抓包並發現:一開始沒有看到有圖片的請求,但點擊「指定商品折扣券」標籤時,看到有圖片的請求發送。須要注意的是,爲了確保體驗,首屏的圖片不建議懶惰加載,而應該直接展現出來;避免一開始用戶就沒法看到圖片,在IE下看到一個虛線框,這樣體驗反而很差。
按需執行JS
按需執行JS和懶惰加載圖片比較相似。當打開網頁時,若是等全部JS都加載並執行完畢,再把界面呈現給用戶,這樣總體上性能會比較慢,體驗也不友好。就是當某個動做觸發後,再執行相應的JS,以便來渲染界面。按需執行JS,能夠應用在下列場景:執行一些耗時比較久的JS代碼,或執行JS後,須要加載比較多圖片、加載iframe、加載廣告等。在一些webapp的應用中,或比較複雜的頁面時,更加應該使用這種方法。
實現思路和按需加載比較相似:
示例代碼以下(以YUI3框架爲例):
首先下載最近封裝的異步滾動條加載組件:Y.asyncScrollLoader,而後運行下面的代碼(須要把頁面和Y.asyncScrollLoader.js 放在同一個目錄):
<!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>按需執行JS</title> </head> <script src="http://yui.yahooapis.com/3.2.0/build/yui/yui-debug.js"></script> <body> <div style="height:1500px"><span style="">向下拖動滾動條,拉到廣告條的位置,將會自動加載廣告的內容!</span></div> <div id="ADContent">這裏是廣告的內容,將會用JS進行填充</div> <script> YUI({modules:{'asyncScrollLoader': { fullpath: 'Y.asyncScrollLoader.js', type: 'js', requires:['widget'] } }}).use('widget','asyncScrollLoader', "node", function(Y){ var loadAD = function(){ //這裏能夠用簡單的代碼代替,實際項目中,能夠執行任何JS代碼,如請求CGI,或者廣告數據,而後再填充到頁面上 var html = '<div><img src="/imgr?src=http%3A%2F%2Ft2.baidu.com%2Fit%2Fu%3D2027994158%2C3223530939%26amp%3Bfm%3D23%26amp%3Bgp%3D0.jpg&r=http%3A%2F%2Ftid.tenpay.com%2F%3Fp%3D4085" alt="" /><span>哈哈,我也是動態加載進來的,能夠從html結構或抓包看出\效果哈!</span><\/div>' Y.one('#ADContent').set('innerHTML',html); } var cfg = { 'elementName' : 'div', 'className' : 'lazy-load', 'contentAttribute' : '' , 'foldDistance': 10, 'obCallback' : { 'funName' : loadAD, 'argument' : [], 'context' : this } }; new Y.asyncScrollLoader(cfg).renderer(); }); </script> </body> </html>
運行上述代碼並抓包發現:打開頁面時,是不沒有看到有對應的圖片請求,但當滾動條拉到必定位置時,loadAD的函數被執行。
按需加載JS
JavaScript無非就是script標籤引入頁面,但當項目愈來愈大的時候,單頁面引入N個js顯然不行,合併爲單個文件減小了請求數,但請求的文件體積卻很大。這時候比較合理的作法就是按需加載。按需加載和按需執行JS比較相似,只不過要執行的JS變成了固定的「實現加載JS」的代碼。按需加載實現的思路以下:
分屏展現
當一個網頁比較長,有好幾個屏幕,並且加載了大量的圖片、廣告等資源文件時,分屏展現,可提高頁面性能和用戶體驗。其實分屏展現也能夠從按需加載的的角度來看待,默認是加載第一屏幕的內容,當滾動條拉動即將到達下一個屏幕時,再開始渲染下個屏的內容。換言之,是把圖片、背景圖片、HTML一塊兒按需加載,一開始不對HTML進行解析,那麼背景圖、img圖片也不會進行加載。
分屏展現的思路以下:
示例代碼以下(須要把頁面和Y.asyncScrollLoader.js 放在同一個目錄):
<!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>網頁分屏展現</title> </head> <style> .class2 { visibility:hidden; widh:100%; height:300px; } </style> <script src="http://yui.yahooapis.com/3.2.0/build/yui/yui-debug.js"></script> <body> <div style="height:1500px"><span style="">向下拖動滾動條,美圖在下面!</span></div> <textarea> <div> <img src="http://travel.shangdu.com/liu/uploads/allimg/090407/1521221.jpg" alt="" /><span style="font-size:16px;color:red;">我是動態加載進來的,能夠從html結構或抓包看出效果哈!</span> </div> </textarea> <div style="height:800px"><span style="">下面繼續上美圖!</span></div> <textarea> <div> <img src="http://download.99sucai.com/upload/images/201006042009245.jpg" alt="" /><span>哈哈,我也是動態加載進來的,能夠從html結構或抓包看出效果哈!</span> </div> </textarea> <script> YUI({modules:{'asyncScrollLoader': { fullpath: 'Y.asyncScrollLoader.js', type: 'js', requires:['widget'] } }}).use('widget','asyncScrollLoader', "node", function(Y){ var cfg = { 'elementName' : 'textarea', 'className' : 'class2', 'contentAttribute' : 'value' , 'foldDistance': 10 }; new Y.asyncScrollLoader(cfg).renderer(); }); </script> </body> </html>
運行上面代碼並抓包發現:在默認首屏,並無去解析textarea裏面的代碼,但當拉動滾動條到必定位置時,textarea裏面的HTML依次被解析,從而實現了網頁分屏展現。上述的示例代碼,能夠在這裏下載來查看: 示例代碼
使用「按需加載」進行性能優化時,須要合理選擇觸發的動做。「按需加載」的最大優點在於減小了沒必要要的資源請求,節省流量,真正實現「按需所取」。可是「按需加載」自己若是使用不當也會影響用戶體驗,由於「按需加載」的時機在用戶觸發某動做以後,若是用戶的網速比較慢的話,加載腳本或執行腳本可能須要等候較長的時間,而用戶則不得不爲此付出代價。所以,若是要使用「按需加載」則須要選擇正確的觸發動做,若是是根據滾動條來觸發,可考慮一個目標距離,假設目標距離還有200像素即將進入可視區域,則就開始加載,而不是等到進入了可視區域才加載。以上所講的各類「按需加載」類型,均可以封裝成相應的組件,而後就能夠在項目中進行應用。
注:轉自「按需加載」的應用, 感謝原做者分享。
博主其餘方式推薦:lazyload