本次淘寶首頁改版,雖已再也不支持 IE6 和 IE7 等低版本的古董瀏覽器,但依然存在多個影響首頁性能的因素:css
網頁性能衡量指標有不少,假若可以把握關鍵的幾個,集中優化,性能天然也就上去了。前端
最能反映頁面性能的一個指標是 FPS(frame per second),通常系統設定屏幕的刷新率爲 60fps,當頁面元素動畫、滾動或者漸變時繪製速率小於 60,就會不流暢,小於 24 就會卡頓,小於 12 基本認定卡爆了。git
1 幀的時長約 16ms,除去系統上下文切換開銷,每一幀中只留給咱們 10ms 左右的程序處理時間,若是一段腳本的處理時間超過 10ms,那麼這一幀就能夠被認定爲丟失,若是處理時間超過 26ms,能夠認定連續兩幀丟失,依次類推。咱們不能容忍頁面中屢次出現連續丟失五六幀的狀況,也就是說必須想辦法分拆執行時間超過 80ms 的代碼程序,這個工做並不輕鬆。github
頁面在剛開始載入的時候,須要初始化不少程序,也可能有大量耗時的 DOM 操做,因此前 1s 的必要操做會致使幀率很低,咱們能夠忽略。固然,這是對 PC 而言,Mobile 內容少,不管是 DOM 仍是 JS 腳本量都遠小於 PC,1s 可能就有點長了。web
DOM 加載而且解析完成纔會觸發 DOMContentLoaded 事件,假若源碼輸出的內容過多,客戶端解析 DOM 的時間也會響應加長,不要小看這裏的解析時間,若是 DOM 數量增長 2000 個而且嵌套層級較深,解析時間也會相應增長 50-200ms,這個消耗對大多數頁面來講實際上是不必的,保證首屏輸出便可,後續的內容只保留鉤子,利用 JS 動態渲染。算法
Load 時間能夠用來衡量首屏加載中,客戶端接受的信息總量,若是在首屏中充滿了大尺寸圖片或者客戶端與後端創建鏈接次數較多,Load 時間也會相應被拖長。json
流暢度是對 FPS 的視覺反饋,FPS 值越高,視覺呈現越流暢。爲了保障頁面的加載速度,不少內容不會在頁面打開的時候所有加載到客戶端。這裏提到的流暢度是等待過程當中的視覺緩衝,以下方是 Google Plus 頁面的一個效果圖:後端
牆內訪問 google 的速度不是很快,上面元素中的的不少內容都是經過異步方式加載,而從上圖能夠看出 Google 並無讓用戶產生等待的焦慮感。因爲平臺限制,淘寶首頁面臨一個先天的性能缺陷,首屏的渲染須要從 7 個不一樣的後端取數據,這些數據請求是難以合併的,若是用戶屏幕比較大,則首屏的面積也比較大,對應的後端平臺數據接口就更多。數據是個性化內容或者爲廣告內容,故請求也不能緩存。瀏覽器
不論用戶首屏的面積有多大,保證關鍵模塊優先加載。下面代碼片斷是初始化全部模塊的核心部分:緩存
$('.J_Module').each(function(mod) { var $mod = $(mod); var name = $mod.attr('tms'); var data = $mod.attr('tms-data'); if($mod.hasClass('tb-pass')) { Reporter.send({ msg: "跳過模塊 " + name }); return; } // 保證首屏模塊先加載 if (/promo|tmall|tanx|notice|member/.test(name)) { window.requestNextAnimationFrame(function(){ // 最後一個參數爲 Force, 強制渲染, 不懶加載處理 new Loader($mod, data, /tanx/.test(name)); }); } else { // 剩下的模塊進入懶加載隊列 lazyQueue.push({ $mod: $mod, data: data, force: /fixedtool|decorations|bubble/.test(name) }); } }); 複製代碼
TMS 輸出的模塊都會包含一個 .J_Module 鉤子,而且會預先加載 js 和 css 文件。
對於無 JS 內容的模塊,會預先打上 tb-pass 的標記,初始化的時候跳過此模塊;對於首屏模塊關鍵模塊,會直接進入懶加載監控:
// $box 進入瀏覽器視窗後渲染 // new Loader($box, data) -> datalazyload.addCallback($box, function() { self.loadModule($box, data); }); // $box 當即渲染 // new Loader($box, data, true) -> self.loadModule($box, data); 複製代碼
除必須當即加載的模塊外,關鍵模塊被加到懶加載監控,緣由是,部分用戶進入頁面就可能急速往下拖拽頁面,此時,不必渲染這些首屏模塊。
非關鍵模塊統一送到 lazyQueue 隊列,沒有基於將非關鍵模塊加入到懶加載監控,這裏有兩個緣由:
那麼,何時開始加載非關鍵模塊呢
var __lazyLoaded = false; function runLazyQueue() { if(__lazyLoaded) { return; } __lazyLoaded = true; $(window).detach("mousemove scroll mousedown touchstart touchmove keydown resize onload", runLazyQueue); var module; while (module = lazyQueue.shift()) { ~function(m){ // 保證在瀏覽器空閒時間處理 JS 程序, 保證不阻塞 window.requestNextAnimationFrame(function() { new Loader(m.$mod, m.data, m.force); }); }(module); } } $(window).on("mousemove scroll mousedown touchstart touchmove keydown resize onload", runLazyQueue); // 擔憂未觸發 onload 事件, 5s 以後執行懶加載隊列 window.requestNextAnimationFrame(function() { runLazyQueue(); }, 5E3); 複製代碼
上面的代碼應該十分清晰,兩種請求下會開始將非關鍵模塊加入懶加載監控:
若是說上面的優化叫作懶加載,那麼這裏的優化能夠稱之爲懶執行。
首頁上有幾個模塊是包含交互的,如頭條區域的 tab ,便民服務的浮層和主題市場的浮層,部分用戶進入頁面可能根本不會使用這些功能,因此程序上並無對這些模塊作完全的初始化,而是等到用戶 hover 到這個模塊上再執行所有邏輯。
首屏中有兩個次要請求,一個是主題市場的 hot 標,將用戶最常逛的三個類目打標;第二個是我的中心的背景,不一樣的城市會展現不一樣的背景圖片,這裏須要請求拿到城市信息。
這兩處的渲染策略都是,在程序的 idle(空閒)時期,或者 window.onload 十秒以後去請求,而後將請求的結果緩存到本地,當用戶第二次訪問淘寶首頁時可以看到效果。這是一種更懶的執行,用戶刷新頁面纔看獲得.這種優化是產品可以接受,也是技術上合理的優化手段。
不論圖片連接的來源是運營填寫仍是接口輸出,都難以保證圖片具有恰當的寬高,加上現在 retina 的屏幕愈來愈多,對於這種用戶也要提供優質的視覺體驗,圖片這塊的處理並不輕鬆。
<img src='//g.alicdn.com/s.gif' data-src='//g.alicdn.com/real/path/to/img.png' /> 複製代碼
阿里 CDN 是支持對圖片尺寸作壓縮處理的,以下圖爲 200x200 尺寸的圖片:
加上 _100x100.jpg 的參數後,會變成小尺寸 咱們知道 webp 格式的圖片比對應的 jpg 要小三分之一,如上圖加上 _.webp 參數後: 視覺效果並無什麼折扣,可是圖片體積縮小了三分之一,圖片越大,節省的越明顯。顯然,淘寶首頁的全部圖片都作了如上的限制,針對坑位大小對圖片作壓縮處理,只是這裏須要注意的是,運營填寫的圖片可能已是壓縮過的,如:$img = '//g.alicdn.com/real/path/to/img.png_400x400.jpg'; <img src='{{$img}}_100x100jpg_.webp' /> 複製代碼
上面這種狀況,圖片是不會正確展現的。首頁對全部的圖片的懶加載都作了統一的函數處理:
src = src.replace(/\s/g, ''); var arr; if (/(_\d{2,}x\d{2,}\w*?\.(?:jpg|png)){2,}/.test(src) && src.indexOf('_!!') == -1) { arr = src.split('_'); if (arr[arr.length - 1] == '.webp') { src = [arr[0], arr[arr.length - 2], arr[arr.length - 1]].join('_'); } else { src = [arr[0], arr[arr.length - 1]].join('_'); } } if (src.indexOf('_!!') > -1) { src = src.replace(/((_\d{2,}x\d{2,}[\w\d]*?|_co0)\.(jpg|png))+/, '$1'); } WebP.isSupport(function(isSupportWebp) { // https 協議訪問存在問題 IE8,去 schema if (/^http:/.test(src)) { src = src.slice(5); } // 支持 webp 格式,而且 host 以 taobaocdn 和 alicdn 結尾,而且不是 s.gif 圖片 if (isSupportWebp && /(taobaocdn|alicdn)\.com/.test(src) && (src.indexOf('.jpg') || src.indexOf('.png')) && !/webp/.test(src) && !ignoreWebP && !/\/s\.gif$/.test(src)) { src += '_.webp'; } $img.attr('src', src); }); 複製代碼
TMS 的模塊在輸出的時候會將數據的 id 放在鉤子上:
若是模塊是異步展現的,能夠經過 tms-datakey 找到模塊數據,而首頁的個性化是從幾十上百個模塊中經過算法選出幾個,若是把這些模塊鉤子所有輸出來,雖然說取數據方便了不少,卻存在大量的冗餘,對此的優化策略是:將數據格式相同的模塊單獨拿出來,新建頁面做爲數據頁。因此能夠在源碼中看到好幾段這樣的配置信息:
<textarea class="tb-hide">[{"backup":"false","baseid":"1","mid":"222726","name":"iFashion","per":"false","tid":"3","uid":"1000"},{"backup":"false","baseid":"3","mid":"222728","name":"美妝秀","per":"false","tid":"3","uid":"1001"},{"backup":"false","baseid":"4","mid":"222729","name":"愛逛街","per":"false","tid":"4","uid":"1002"},{"backup":"false","baseid":"2","mid":"222727","name":"全球購","per":"false","tid":"4","uid":"1003"}]</textarea> 複製代碼
減小了大量的源碼以及對 DOM 的解析。
有一些模塊數據是不多被修改的,好比接口的兜底數據、阿里 APP 模塊數據等,能夠經過調整參數,設置模塊的緩存時間,如:
io({ url: URL, dataType: 'jsonp', cache: true, jsonpCallback: 'jsonp' + Math.floor(new Date / (1000 * 60)), success: function() { //... } }); 複製代碼
Math.floor(new Date / (1000 * 60)) 這個數值在一分鐘內是不會發生變化的,也就是說將這個請求在本地緩存一分鐘,對於低頻修改模塊,緩存時間能夠設置爲一天,即:
Math.floor(new Date / (1000 * 60 * 60 * 24))
複製代碼
固然,咱們也能夠採用本地儲存的方式緩存這個模塊數據:
offline.setItem('cache-moduleName', JSON.stringify(data), 1000 * 60 * 60 * 24); 複製代碼
緩存過時時間設置爲 1 天,淘寶首頁主要採用本地緩存的方式。
這方面的優化不是不少,可是也有一點效果,不少模塊的展現並非乾巴巴的 .show(),而是經過動畫效果,緩動呈現,這方面的優化推薦使用 CSS3 屬性去控制,性能消耗會少不少。
頁面優化的切入點不少,咱們不必定可以面面俱到,可是對於一個承載較大流量的頁面來講,下面幾條必須有效執行:
固然,性能優化的切入角度不只僅是上幾個方面,對照 Chrome 的 Timeline 柱狀圖和折線圖,咱們還能夠找到不少優化的點,如:
在優化的過程當中須要更多地思考,如何讓阻塞的腳本分批執行,如何將長時間執行的腳本均勻地分配到時間線上。這些優化都體如今代碼的細節上,宏觀上的處理難以有明顯的效果。固然,在宏觀上,淘寶首頁也有一個明顯的優化:
// https://gist.github.com/miksago/3035015#file-raf-js (function() { var lastTime = 0; var vendors = ['ms', 'moz', 'webkit', 'o']; for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) { window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame']; window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame'] || window[vendors[x]+'CancelRequestAnimationFrame']; } if (!window.requestAnimationFrame) { window.requestAnimationFrame = function(callback, element) { var currTime = new Date().getTime(); var timeToCall = Math.max(0, 16 - (currTime - lastTime)); var id = window.setTimeout(function() { callback(currTime + timeToCall); }, timeToCall); lastTime = currTime + timeToCall; return id; }; } if (!window.cancelAnimationFrame) { window.cancelAnimationFrame = function(id) { clearTimeout(id); }; } })(); 複製代碼
這段代碼基本保證每一個模塊的初始化都是在瀏覽器空閒時期,減小了不少沒必要要的丟幀。這個優化也能夠被應用到每一個模塊的細節代碼之中,不過優化難度會更高。
代碼的性能優化是一個精細活,若是你要在一個龐大的未經優化的頁面上作性能優化,可能會面臨一次重構代碼。本文從淘寶首頁個性化引出的問題出發,從微觀到宏觀講述了頁面的優化實踐,提出了幾條能夠借鑑的優化標準,但願對你有所啓發。優化的細節點描述的不夠完善也不夠全面,可是都是值得去優化的方向。