本來這是個老生常談的問題,上週自成又分享了一些效能優化的建議,我這裡再做一個全面的Tips整理,謹作為查閱型的文件,不妥之處,還請指正;
一、 Yahoo的規則條例:
謹記:80%-90%的終端響應時間是花費在下載頁面中的圖片,樣式表,指令碼,flash等;
詳細的解釋來這裡查:http://developer.yahoo.com/performance/rules.html
也可以直接firebug上一項項比對,如下圖:
簡單翻譯解釋下:
1、儘量減少HTTP請求個數——須權衡
合併圖片(如css sprites,內建圖片使用資料)、合併CSS、JS,這一點很重要,但是要考慮合併後的檔案體積。
2、使用CDN(內容分發網路)
這裡可以關注CDN的三類實現:映象、快取記憶體、專線,以及智慧路由器和負載均衡;
3、為檔案頭指定Expires或Cache-Control,使內容具有快取性。
區分靜態內容和動態內容,避免以後頁面訪問中不必要的HTTP請求。
4、避免空的src和href
留意具有這兩個屬性的標籤如link,script,img,iframe等;
5、使用gzip壓縮內容
Gzip壓縮所有可能的檔案型別以來減少檔案體積
6、把CSS放到頂部
實現頁面有秩序地載入,這對於擁有較多內容的頁面和網速較慢的使用者來說更為重要,同時,HTML規範清楚指出樣式表要放包含在頁面的區域內;
7、把JS放到底部
HTTP/1.1 規範建議,瀏覽器每個主機名的並行下載內容不超過兩個,而問題在於指令碼阻止了頁面的平行下載,即便是主機名不相同
8、避免使用CSS表示式
頁面顯示和縮放,滾動、乃至移動滑鼠時,CSS表示式的計算頻率是我們要關注的。可以考慮一次性的表示式或者使用事件控制程式碼來代替CSS表示式。
9、將CSS和JS放到外部檔案中
我們需要權衡內建程式碼帶來的HTTP請求減少與通過使用外部檔案進行快取帶來的好處的折中點。
10、減少DNS查詢次數
我們需要權衡減少 DNS查詢次數和保持較高程度並行下載兩者之間的關係。
11、精簡CSS和JS
目的就是減少下載的檔案體積,可考慮壓縮工具JSMin和YUI Compressor。
12、避免跳轉
為了確保“後退”按鈕可以正確地使用,使用標準的 3XXHTTP狀態程式碼;同域中注意避免反斜槓 “/” 的跳轉;
跨域使用 Alias或者 mod_rewirte建立 CNAME(儲存一個域名和另外一個域名之間關係的DNS記錄)
13、剔除重複的JS和CSS
重複呼叫指令碼,除了增加額外的HTTP請求外,多次運算也會浪費時間。在IE和Firefox中不管指令碼是否可快取,它們都存在重複運算JavaScript的問題。
14、配置ETags
Entity tags(ETags)(實體標籤)是web伺服器和瀏覽器用於判斷瀏覽器快取中的內容和伺服器中的原始內容是否匹配的一種機制(“實體”就是所說的“內 容”,包括圖片、指令碼、樣式表等),是比last-modified date更更加靈活的機制,單位時間內檔案被修過多次,Etag可以綜合Inode(檔案的索引節點(inode)數),MTime(修改時間)和 Size來精準的進行判斷,避開UNIX記錄MTime只能精確到秒的問題。 伺服器叢集使用,可取後兩個引數。使用ETags減少Web應用頻寬和負載。
15、使AJAX可快取
利用時間戳,更精巧的實現響應可快取與伺服器資料同步更新。
16、儘早重新整理輸出緩衝
尤其對於css,js檔案的並行下載更有意義
17、使用GET來完成AJAX請求
當使用XMLHttpRequest時,瀏覽器中的POST方法是一個“兩步走”的過程:首先傳送檔案頭,然後才傳送資料。在url小於2K時使用GET獲取資料時更加有意義。
18、延遲載入
確定頁面執行正常後,再載入指令碼來實現如拖放和動畫,或者是隱藏部分的內容以及摺疊內容等。
19、預載入
關注下無條件載入,有條件載入和有預期的載入。
20、減少DOM元素個數
使用更適合或者在語意是更貼切的標籤,要考慮大量DOM元素中迴圈的效能開銷。
21、根據域名劃分頁面內容
很顯然, 是最大限度地實現平行下載
22、儘量減少iframe的個數
考慮即使內容為空,載入也需要時間,會阻止頁面載入,沒有語意,注意iframe相對於其他DOM元素高出1-2個數量級的開銷,它會在典型方式下阻塞onload事件,IE和Firefox中主頁面樣式表會阻塞它的下載。
23、避免404
HTTP請求時間消耗是很大的,有些站點把404錯誤響應頁面改為“你是不是要找***”,這雖然改進了使用者體驗但是同樣也會浪費伺服器資源(如數 據庫等)。最糟糕的情況是指向外部 JavaScript的連結出現問題並返回404程式碼。首先,這種載入會破壞並行載入;其次瀏覽器會把試圖在返回的404響應內容中找到可能有用的部分當 作JavaScript程式碼來執行。
24、減少Cookie的大小
- 去除不必要的coockie
- 使coockie體積儘量小以減少對使用者響應的影響
- 注意在適應級別的域名上設定coockie以便使子域名不受影響
- 設定合理的過期時間。較早地Expire時間和不要過早去清除coockie,都會改善使用者的響應時間。
25、使用無cookie的域
確定對於靜態內容的請求是無coockie的請求。建立一個子域名並用他來存放所有靜態內容。
26、減少DOM訪問
- 快取已經訪問過的有關元素
- 線下更新完節點之後再將它們新增到文件樹中
- 避免使用JavaScript來修改頁面佈局
27、開發智慧事件處理程式
有時候我們會感覺到頁面反應遲鈍,這是因為DOM樹元素中附加了過多的事件控制程式碼並且些事件句病被頻繁地觸發。這就是為什麼說使用event delegation(事件代理)是一種好方法了。如果你在一個div中有10個按鈕,你只需要在div上附加一次事件控制程式碼就可以了,而不用去為每一個按 鈕增加一個控制程式碼。事件冒泡時你可以捕捉到事件並判斷出是哪個事件發出的。
你同樣也不用為了操作DOM樹而等待onload事件的發生。你需要做的就是等待樹結構中你要訪問的元素出現。你也不用等待所有影像都載入完畢。
你可能會希望用DOMContentLoaded事件來代替 事件應用程式中的onAvailable方法。
28、用 代替@import
在IE中,頁面底部@import和使用作用是一樣的,因此最好不要使用它。
29、避免使用濾鏡
完全避免使用AlphaImageLoader的最好方法就是使用PNG8格式來代替,這種格式能在IE中很好地工作。如果你確實需要使用 AlphaImageLoader,請使用下劃線_filter又使之對IE7以上版本的使用者無效。
30、優化影像
嘗試把GIF格式轉換成PNG格式,看看是否節省空間。在所有的PNG圖片上執行pngcrush(或者其它PNG優化工具)
31、優化CSS Spirite
在Spirite中水平排列你的圖片,垂直排列會稍稍增加檔案大小;
Spirite中把顏色較近的組合在一起可以降低顏色數,理想狀況是低於256色以便適用PNG8格式;
便於移動,不要在Spirite的影像中間留有較大空隙。這雖然不大會增加檔案大小但對於使用者代理來說它需要更少的記憶體來把圖片解壓為畫素地圖。 100×100的圖片為1萬畫素,而1000×1000就是100萬畫素。
32、不要在HTML中縮放影像——須權衡
不要為了在HTML中設定長寬而使用比實際需要大的圖片。如果你需要:
1 |
<img width=”100″ height=”100″ src=”mycat.jpg” alt=”My Cat” /> |
那麼你的圖片(mycat.jpg)就應該是100×100畫素而不是把一個500×500畫素的圖片縮小使用。這裡在下文有更有趣的分析。
33、favicon.ico要小而且可快取
favicon.ico是位於伺服器根目錄下的一個圖片檔案。它是必定存在的,因為即使你不關心它是否有用,瀏覽器也會對它發出請求,因此最好不要 返回一 個404 Not Found的響應。由於是在同一臺伺服器上,它每被請求一次coockie就會被髮送一次。這個圖片檔案還會影響下載順序,例如在IE中當你在 onload中請求額外的檔案時,favicon會在這些額外內容被載入前下載。
因此,為了減少favicon.ico帶來的弊端,要做到:
檔案儘量地小,最好小於1K
在適當的時候(也就是你不要打算再換favicon.ico的時候,因為更換新檔案時不能對它進行重新命名)為它設定Expires檔案頭。你可以很安全地 把Expires檔案頭設定為未來的幾個月。你可以通過核對當前favicon.ico的上次編輯時間來作出判斷。
Imagemagick可以幫你建立小巧的favicon。
34、保持單個內容小於25K
因為iPhone不能快取大於25K的檔案。注意這裡指的是解壓縮後的大小。由於單純gizp壓縮可能達不要求,因此精簡檔案就顯得十分重要。
35、打包元件成複合文字
頁面內容打包成複合文字就如同帶有多附件的Email,它能夠使你在一個HTTP請求中取得多個元件(切記:HTTP請求是很奢侈的)。當你使用這條規 則時,首先要確定使用者代理是否支援(iPhone就不支援)。
二、Yahoo軍規之外的場景?
1、 使用json作為資料的交換格式
Json在瀏覽器解析的效率至少高於XML一個數量級,高階瀏覽器中內建的有生成和解析json的方法,IE6中要用額外的方法(http://json.org ),不要用eval,容易引發效能和安全問題。
2、 儘可能對images和table設定寬高值
針對Yslow的不要在HTML中縮放影像——第33條,有人會誤解為不要對圖片加寬高值,其實這條建議本身的意思是不要為了獲取一個特定大小的圖片,而去強行通過設定寬高值拉伸或者壓縮一個既有的圖片。建議是另存一張符合尺寸的圖片替代。
對圖片和table是設定寬高,是考慮到如果瀏覽器能立刻知道圖片或者tables的寬高,它就能夠直接呈現頁面而不需要通過計算元素大小後重繪,而且即便是圖片損毀而沒有展現,也不會進而破壞了頁面本來的佈局。
有一些應用場景需要注意:
- a、批量圖片,圖片源可控同時頁面圖片寬高值不可變,比如資料庫有100張100*100的圖片要在頁面中全部展示,那麼建議是都寫上
1<img width=”100″ height=”120″ src=”" alt=”" /> - b、批量圖片,圖片源不可控同時頁面圖片寬高值不可變,比如資料庫有100張圖片,而已知圖片有的尺寸是97*100,有的100*105,而又 不可能去一張張修改另存。這裡視情況而定,根據圖片尺寸與要求尺寸的偏離度,在保證圖片不拉伸變形同時不影響頁面佈局的情況下,可以對圖片單獨設定寬度 100,同時對其包裹的容器設定100*100的寬高來隱藏多出來的部分,注意不能同時設定寬高以防止變形。
- c、批量圖片,圖片源不可控,頁面圖片寬高值不定,比如資料庫有100張各種尺寸偏差較大的,此時可不對圖片設定寬高;
其他情況不一一羅列,原則是在最大程度保證圖片不變形與圖片最大面積展現的前提下,儘可能為圖片設定寬高值,總之就是權衡。
Tables的寬高值同圖片,儘可能設定。
3、 拆離內容塊
儘量用div取代tables,或者將tables打破成巢狀層次深的結構;
避免用這樣的巢狀
1 2 3 4 5 6 7 |
<table> <table> <table> ... </table> </table> </table> |
採用下面的或者div重構:
1 2 3 |
<table></table> <table></table> <table></table> |
4、 高效的CSS書寫規則
眾所周知,CSS選擇符是從右向左進行匹配的。
通常一個圖片列表的的小模組
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<div id="box"> <div class="hd"> <h3>我的旅途</h3> </div> <div class="bd"> <h4>旅途1</h4> <ul id="pics"> <li> <a href="#pic" title=""><img src="" alt="" /> </a> <p>這是在<strong>圖片1</strong></p> </li> </ul> </div> </div> |
為了程式碼上縮排後內層的整潔性,我們html有可能這樣寫之外,更喜歡看這樣的css寫法:
1 2 3 4 5 6 7 8 9 10 11 |
.box{border:1px solid #ccc } .box .hd{border-bottom:1px solid #ccc } .box .hd h3{color:#515151} .box .bd{color:#404040 } .box .bd ul{margin-left:10px} .box .bd ul li{border-bottom:1px dashed #f1f1f1} .box .bd ul li a{text-decoration:none} .box .bd ul li a:hover{text-decoration:underline} .box .bd ul li a img{border:1px solid #ccc} .box .bd ul li p{text-align:left;} .box .bd ul li p strong{color:#ff6600} |
其實寫到這裡,問題已經顯而易見了。深達五層抑或六層的巢狀,同時右邊的選擇符都是採用標籤,在滿足我們視覺平整與程式碼結構系統化的時候,付出的是效能的代價。
不做進一步的程式碼書寫方式的探討,受個人習慣與應用場景影響。這裡對css選擇符按照開銷從小到大的順序梳理一下:
- ID選擇符 #box
- 類選擇符 .box
- 型別選擇符 div
- 相鄰兄弟選擇符 h4 + #pics
- 子選擇符 #pics li
- 後代選擇符 .box a{}
- 通配選擇符 *
- 屬性選擇符 [href=”#pic”]
- 偽類和偽元素 a:hover
參考《高效能網站建設-進階指南》,有如下建議:
- 避免使用統配規則;
- 不要限定ID選擇符;
- 不要限定類選擇符;
- 讓規則越具體越好;
- 避免使用後代選擇符;
- 避免使用標籤-子選擇符;
- 質疑子選擇符的所有用途;
- 依靠繼承;
還要注意到,即便是頁面載入後,當頁面被觸發引起迴流(reflow)的時候,低效的選擇符依然會引發更高的開銷,顯然這對於使用者是不佳的體驗。
4、Javascript 的效能優化點
- a、慎用Eval 謹記:有“eval”的程式碼比沒有“eval”的程式碼要慢上 100 倍以上。主要原因是:JavaScript 程式碼在執行前會進行類似“預編譯”的操作:首先會建立一個當前執行環境下的活動物件,並將那些用 var 申明的變數設定為活動物件的屬性,但是此時這些變數的賦值都是 undefined,並將那些以 function 定義的函式也新增為活動物件的屬性,而且它們的值正是函式的定義。但是,如果你使用了“eval”,則“eval”中的程式碼(實際上為字串)無法預先識 別其上下文,無法被提前解析和優化,即無法進行預編譯的操作。所以,其效能也會大幅度降低。
- b、推薦儘量使用區域性變數 JavaScript 程式碼解釋執行,在進入函式內部時,它會預先分析當前的變數,並將這些變數歸入不同的層級(level),一般情況下:
區域性變數放入層級 1(淺),全域性變數放入層級 2(深)。如果進入“with”或“try – catch”程式碼塊,則會增加新的層級,即將“with”或“catch”裡的變數放入最淺層(層 1),並將之前的層級依次加深。變數所在的層越淺,訪問(讀取或修改)速度越快,尤其是對於大量使用全域性變數的函式裡面。 - c、字串陣列方式拼接避免在IE6下的開銷
1var tips = 'tip1'+'tip2';
這是我們拼接字串常用的方式,但是這種方式會有一些臨時變數的建立和銷燬,影響效能,尤其是在IE6下,所以推薦使用如下方式拼接:
1234var tip_array = [],tips;tip_array.push('tip1');tip_array.push('tip2');tips = tip_array.join('');
當然,最新的瀏覽器(如火狐 Firefox3+,IE8+ 等等)對字串的拼接做了優化,效能略快於陣列的“join”方法。 - 以上僅列出三種常見的優化方法,僅拋磚以引玉石,更多的javascript優化點,比如避免隱式型別轉換, 縮小物件訪問層級,利用變數優化字串匹配等大家可以繼續深入挖掘;
5、DOM 操作優化
首先澄清兩個概念——Repaint 和 Reflow:Repaint 也叫 Redraw,它指的是一種不會影響當前 DOM 的結構和佈局的一種重繪動作。如下動作會產生 Repaint 動作:
- 不可見到可見(visibility 樣式屬性);
- 顏色或圖片變化(background, border-color, color 樣式屬性);
- 不改變頁面元素大小,形狀和位置,但改變其外觀的變化
Reflow 比起 Repaint 來講就是一種更加顯著的變化了。它主要發生在 DOM 樹被操作的時候,任何改變 DOM 的結構和佈局都會產生 Reflow。但一個元素的 Reflow 操作發生時,它的所有父元素和子元素都會放生 Reflow,最後 Reflow 必然會導致 Repaint 的產生。舉例說明,如下動作會產生 Reflow 動作:
- 瀏覽器視窗的變化;
- DOM 節點的新增刪除操作
- 一些改變頁面元素大小,形狀和位置的操作的觸發通過 Reflow 和 Repaint 的介紹可知,每次 Reflow 比其 Repaint 會帶來更多的資源消耗,因此,我們應該儘量減少 Reflow 的發生,或者將其轉化為只會觸發 Repaint 操作的程式碼。
123456var tipBox = document.createElement('div');document.body.appendChild('tipBox');//reflowvar tip1 = document.createElement('div');var tip2 = document.createElement('div');tipBox.appendChild(tip1);//reflowtipBox.appendChild(tip2);//reflow如上的程式碼,會產生三次reflow,優化後的程式碼如下:
123456var tipBox = document.createElement('div');tip1 = document.createElement('div');tip2 = document.createElement('div');tipBox.appendChild(tip1);tipBox.appendChild(tip2);document.body.appendChild('tipBox');//reflow當然還可以利用 display 來減少reflow次數
12345678910var tipBox = document.getElementById('tipBox');tipBox.style.display = 'none';//reflowtipBox.appendChild(tip1);tipBox.appendChild(tip2);tipBox.appendChild(tip3);tipBox.appendChild(tip4);tipBox.appendChild(tip5);tipBox.style.width = 120;tipBox.style.height = 60;tipBox.style.display = 'block';//reflowDOM元素測量屬性和方法也會觸發reflow,如下:
123var tipWidth = tipBox.offsetWidth;//reflowtipScrollLeft = tipBox.scrollLeft;//reflowdisplay = window.getComputedStyle(div,'').getPropertyValue('display');//reflow觸發reflow的屬性和方法大概有這些:
- offsetLeft
- offsetTop
- offsetHeight
- offsetWidth
- scrollTop/Left/Width/Height
- clientTop/Left/Width/Height
- getComputedStyle()
- currentStyle(in IE))
我們可以用臨時變數將“offsetWidth”的值快取起來,這樣就不用每次訪問“offsetWidth”屬性。這種方式在迴圈裡面非常適用,可以極大地提高效能。
如果有批量的樣式屬性需要修改,建議通過替換className的方式來降低reflow的次數,曾經有這樣一個場景:有三個intput,分別對 應下面三個圖片和三個內容區域,第二input選中的時候,第二圖片顯示,其他圖片隱藏,第二塊內容顯示,其他內容隱藏,直接操作DOM節點的程式碼如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
var input = []; pics = []; contents = []; ...... inputFrame.onclick =function(e){ var _e,_target; _e = e ? window.event : null; if(!_e){ return; }else{ _target = _e.srcElement || _e.target ; _index = getIndex(_target);//reflow兩次 show(_target,_index);//reflow兩次 } } function show(target,j){ for(var i = 0,i<3;i++){ target[i].style.display = 'none';//reflow } target[j].style.display = 'block';//reflow } function getIndex(targer){ if(target){ .....//獲取當前的元素索引 return index; } } |
如果是通過css預先定義元素的隱藏和顯示,通過對父級的className進行操縱,將會把reflow的次數減少到1次
1 2 3 4 |
.pbox .pic,.pbox content{display:none} .J_pbox_0 .pic0,.J_pbox_0 .content0{diplay:block} .J_pbox_1 .pic1,.J_pbox_1 .content1{diplay:block} .J_pbox_2 .pic2,.J_pbox_2 .content2{diplay:block} |
1 2 3 4 5 6 7 8 |
var input = [], parentBox = document.getELementById('J_Pbox'); ...... for(var i = 0;i<3;i++){ input[i].onclick = function(e){ parentBox.className = 'pbox J_pbox_'+i;//reflow一次 } } |
三、Yahoo軍規再度挖掘會怎樣?
在網站效能優化的路上,是不會有終點的,這也是前端工程師永不會妥協的地方。
想看到更牛P的優化建議麼,請移步這裡來關注李牧童鞋的分享:
- 使用combo合併靜態資源
- Bigpipe技術合併動態資料
- Comet:基於http的服務端推技術
- 使用DataURI減少圖片請求
- 使用良好的JS,CSS版本管理方案
- 嘗試僅作必要的JS更新
- 利用本地儲存做快取
- 關於最小化HTML
- 進一步討論Gzip
- 進一步討論域名劃分
- 開啟keep-alive,重用HTTP連線
- 使用JSON進行資料交換
- 保障頁面可互動性
- 縮短最快可互動時間
- 非同步無阻指令碼下載
- 優化記憶體使用,防止記憶體洩露
- 高效的JavaScript
- 第三方程式碼效能問題
- Inline指令碼不要與CSS穿插使用
- 使用高效的CSS選擇器
- 進一步討論及早Flush
- 關於視覺和心理學
原文:alimama ued