大流量網站性能優化:一步一步打造一個適合本身的BigRender插件(轉)

BigRender

當一個網站愈來愈龐大,加載速度愈來愈慢的時候,開發者們不得不對其進行優化,誰願意訪問一個須要等待 10 秒,20 秒才能出現的網頁呢?javascript

常見的也是相對簡單易行的一個優化方案是 圖片的延遲加載。一個龐大的頁面,有時咱們並不會滾動去看下面的內容,這樣就浪費了非首屏部分的渲染,而這些無用的渲染,不只包括圖片,還包括其餘的 DOM 元素,甚至一些 js/css(某些js/css 是根據模塊請求的,好比一些 ajax),理論上,每增長一個 DOM,都會增長渲染的時間。有沒有辦法能使得 HTML、js、css 都能按需加載呢?答案是確定的,這就是本文要講的 BigRender。php

業界有不少 BigRender 在生產環境中的案例,好比 新浪美團途牛旅行網360網址導航淘寶商品詳情頁 等等。查看它們的源代碼(ctrl+u),ctrl+f 搜索 textarea 關鍵字,很容易能夠看到一些被 textarea 標籤包裹的 HTML 代碼。css

好比途牛:html

而這些被 textarea 標籤包裹的 HTML 代碼,只是 textarea 的 value 值,並無渲染到 DOM 樹上。沒錯,BigRender 一般就是用 textarea 標籤包裹 HTML 代碼(js/css),看成其 value 值,等到合適的時機(一般當 textarea 標籤出現或者即將出如今用戶視野時)將 textarea 中的 HTML 代碼取出,用 innerHTML 動態插入到 DOM 樹中,若有必要,取出 js/css 代碼(正則),動態執行它們。(是否是和圖片的延遲加載很類似?)前端

玉伯指出java

頁面下載完畢後,要通過 Tokenization — Tree Construction — Rendering. 要讓首屏儘快出來,得給瀏覽器減輕渲染首屏的工做量。能夠從兩方面入手:

  1. 減小 DOM 節點數。節點數越少,意味着 Tokenization, Rendering 等操做耗費的時間越少。(對於典型的淘寶商品詳情頁,經測試發現,每增長一個 DOM 節點,會致使首屏渲染時間延遲約 0.5ms.)

  2. 減小腳本執行時間。腳本執行和 UI Update 共享一個 thread, 腳本耗的時間越少,UI Update 就能愈加提早。

爲何是用 textarea 標籤存放大塊 HTML 內容?仍是能夠看下玉伯的 這篇文章。淘寶的 kissy 就內置了 DataLazyload 組件。(插播:美團詳情頁還有用到 script 標籤作 BigRender 優化,詳情請見下面的 "其餘" 一節)git

接下去就來一步一步實現一個適合本身的 BigRender 插件,我但願能夠延遲加載 HTML 元素、js 以及 css。github

T.datalazyload

仿照 jQuery 的寫法我定義了一個全局對象 T,將延遲加載的實現代碼封裝在了 T.datalazyload 對象中,將須要延遲加載的代碼 "包裹" 在 textarea 標籤中,設置其 visibility 屬性爲 hidden,並賦予該標籤一個特殊的類名(爲了作事件監聽),好比叫作 "datalazyload"。爲了方便,我規定每一個作 bigrender 優化的 textarea 的父節點都只有一個子孩子(即該 textarea 元素),這一點很是重要必須遵照,由於後面代碼有針對此的特殊處理。(注意要設置好父節點的高度寬度,和 dom 渲染後的高度寬度保持一致)ajax

一些 HTML/js/css 代碼均可以包裹在 textarea 標籤中,例如:sql

<textarea class="datalazyload" style="visibility: hidden;"> <script type="text/javascript"> alert("I am lazyload zone!"); </script> <style type="text/css"> .main {margin: 0 auto; text-align: center; padding-top: 200px; width:1000px; height:1000px; border:5px black dashed;} .second {margin: 0 auto; width:1000px; height:200px; border: 5px purple dotted; padding-top: 100px; text-align: center;} </style> <div class="second"> <h1>我是延遲加載的部分!</h1> </div> </textarea>

init

給 T.datalazyload 對象定義一個 init() 方法,初始化頁面時監聽 scroll、resize 以及移動端的 touchmove 事件,當觸發這些事件時,回調函數內判斷延遲加載部分是否已經出如今視口。

init: function(config) { var cls = config.cls; this.threshold = config.threshold ? config.threshold : 0; this.els = Array.prototype.slice.call(T.getElementsByClassName(cls)); this.fn = this.pollTextareas.bind(this); this.fn(); T.addEvent(window, "scroll", this.fn); T.addEvent(window, "resize", this.fn); T.addEvent(doc.body, "touchMove", this.fn); }

config 是配置參數,其 cls 屬性表示須要延遲加載的 textarea 的類名,threshold 爲閾值,單位 px,表示當 textarea 距離視口多少像素時,進行預加載。

將須要延遲加載的元素存入一個數組(this.els),(某 textarea 元素)後續一旦完成加載隨即在數組中刪除該元素。事件監聽的回調函數爲 pollTextarea() 方法。

pollTextarea

pollTextareas: function() { // 需延遲加載的元素已經所有加載完 if (!this.els.length) { T.removeEvent(window, "scroll", this.fn); T.removeEvent(window, "resize", this.fn); T.removeEvent(doc.body, "touchMove", this.fn); return; } // 判斷是否須要加載 for (var i = this.els.length; i--; ) { var ele = this.els[i]; if (!this.inView(ele)) continue; this.insert(ele); this.els.splice(i, 1); } }

這個方法的做用是判斷須要延遲加載的元素是否已經在視口,若是是,則進行加載(觸發 insert 方法),而且在數組中刪除該元素;若是數組爲空,則代表須要延遲加載的部分都已經加載完,移除事件監聽,整個延遲加載結束。

insert

接下去看 insert 方法。inert 方法的參數是須要延遲加載的 textarea 元素,很顯然,咱們須要解析的代碼全在 textarea.innerHTML 中。咱們用 extractCode 方法取出其中的 js/css 代碼,而後將 js/css 過濾掉,這樣剩下的就全是 HTML 代碼了,將其插入 DOM 中(這正是前文說的 "每一個 textarea 的父節點都只有一個子孩子" 的緣由,能夠直接用父節點 innerHTML 操做),若是有 loading 效果,通常在父節點加個 loading 類,移除便可。最後再動態執行 js 腳本,插入 css 樣式。

insert: function(ele) { var parent = ele.parentNode , txt = this.decodeHTML(ele.innerHTML) , matchStyles = this.extractCode(txt, true) , matchScripts = this.extractCode(txt); parent.innerHTML = txt .replace(new RegExp("<script[^>]*>([\\S\\s]*?)</script\\s*>", "img"), "") .replace(new RegExp("<style[^>]*>([\\S\\s]*?)</style\\s*>", "img"), ""); if (matchStyles.length) for (var i = matchStyles.length; i --;) this.evalStyles(matchStyles[i]); // 若是延遲部分須要作 loading 效果 parent.className = parent.className.replace("loading", ""); if (matchScripts.length) for (var i = 0, len = matchScripts.length; i < len; i++) this.evalScripts(matchScripts[i]); },

extractCode

咱們經過正則將 js 和 css 標籤部分取出:

extractCode: function(str, isStyle) { var cata = isStyle ? "style" : "script" , scriptFragment = "<" + cata + "[^>]*>([\\S\\s]*?)</" + cata + "\\s*>" , matchAll = new RegExp(scriptFragment, "img") , matchOne = new RegExp(scriptFragment, "im") , matchResults = str.match(matchAll) || [] , ret = []; for (var i = 0, len = matchResults.length; i < len; i++) { var temp = (matchResults[i].match(matchOne) || [ "", "" ])[1]; temp && ret.push(temp); } return ret; }

成功地將 script 以及 style 標籤內的內容提取了出來,巧妙地用了正則中的子表達式。

evalScripts/evalStyles

腳本執行,樣式渲染。

evalScripts: function(code) { var head = doc.getElementsByTagName("head")[0] , js = doc.createElement("script"); js.text = code; head.insertBefore(js, head.firstChild); head.removeChild(js); }, evalStyles: function(code) { var head = doc.getElementsByTagName("head")[0] , css = doc.createElement("style"); css.type = "text/css"; try { css.appendChild(doc.createTextNode(code)); } catch (e) { css.styleSheet.cssText = code; } head.appendChild(css); }

優缺點 & 適用場景

簡單講講 BigRender 優化的優缺點,以及適用場景。

優勢很明顯,由於減小了首屏 DOM 的渲染,因此能加快首屏加載的速度,而且能分塊加載 js/css,很是適用於一些模塊區分度很高的網站(我的以爲大型網站的模塊區分度廣泛愈來愈高了)。

缺點是須要更改 DOM 結構(DOM 節點的替換和渲染),可能會引發一些重排和重繪。一些沒有開啓 js 功能的用戶將看不到延遲加載的內容(能夠用 noscript 標籤給出一個善意提醒)。最大的缺點多是不利於 SEO,一些依賴於 SEO 的網站可能須要在 SEO 上下點功夫了,好比美團。

關於 SEO,能夠看下 http://www.seoqx.com/lynx 這個網站,能模擬搜索引擎蜘蛛對網站的爬取狀況。美團對於 BigRender 以及 SEO 解決方案 [美團網案例]改善BigRender技術致使的SEO問題

bigrender 經過減小 DOM 節點,加快首屏的渲染,可是,它也是有額外的性能損耗的,渲染前textarea 裏面的 html 代碼,在服務端把 html 代碼保存在隱藏的 textarea 裏面,因此在服務端會把 html 代碼轉義:尖括號等都被轉義了,這個會增長服務器的壓力;並且,這個改造只是前端的渲染,服務器依舊是一次計算全部的數據,輸出全部的數據,這一點沒有獲得提升。

通常來講,使用都是後端拼接成 html 字符串,而後塞入 textarea 標籤中,吐給前端。

demo

若是要作一個完整的 BigRender demo,可能比較複雜,還要涉及到後端。

以前學習 lazyload 時作過一個圖片的延遲加載 demo,see http://hanzichi.github.io/cnblogs/2016/Feb/picture-lazyload/。由於 BigRender 是 lazyload 的增強版,因此簡單地作了個 BigRender 版本的圖片延遲加載 http://hanzichi.github.io/cnblogs/2016/Mar/bigrender/,實現的具體代碼能夠 check bigrender.js。求 star,求 fork~

其餘

除了首頁部分用了 textarea 作 BigRender 優化外,美團還用到了 script 標籤作優化。好比 這個商品詳情頁

給 script 標籤設置個非 "text/javascript" 的 type,能夠下載這段 js,但不執行,這種作法似曾相識,在 labjs 中看到過。

更多能夠參考 前端優化三續:用script存放html代碼來減小DOM節點數

Read More

http://www.cnblogs.com/zichi/p/5252629.html

相關文章
相關標籤/搜索