提高網頁性能

以前作pc端的時候基本不是特別會特別去關注頁面尤爲是js的性能,即便作也是主要針對圖片,最近作手機端的一個座位圖頁,大概有1000個左右做爲,要根據觸摸滑動來實時改變位置,大小,顏色,等等。在谷歌瀏覽器那叫一個流暢~當在安卓機上實驗時,簡直卡爆了,因而。。優化,花了將近1個星期時間,主要是針對js的優化,由於數據是ajax實時取來生成的。原本打算就此次優化作一次簡單的總結,無心中看到一篇好文章,把我用到的沒用到的基本都總結了,因而特此分享出來。css

固然其實關於js有幾點特別須要注意的是,若是是touch移動,儘可能改變元素的transform而非top,left,具體實際寫個小demo就清楚了,差的時間不是一星半點。還有要儘可能合併相同的功能的語句,在以前的頁面中,爲了簡潔明瞭,我列了以下的js語句;html

$(obj).data("ab","ab");css3

......web

$(obj).data("cd","cd");     ajax

而後修改後變成了相似這樣   $(obj).(attr,{"abc":"abc",....."cd":'cd'});正則表達式

在兩段代碼先後分別console了時間,(儘可能少用alert),不可思議,時間差異將近200ms。數組

儘可能少用全局變量.瀏覽器

若是在手機端,能夠用css3實現的動畫,儘可能不要用js,那樣會更流暢,具體用安卓測試會很明顯。緩存

儘可能減小dom操做,若是必定須要dom操做,儘可能合併,尤爲是修改css的時候,儘可能經過css類的添加來實現,效能會高不少。app

避免屢次重複引用。

好了,仍是上正菜吧。。    

HTML部分

  1. 語義化HTML:好處在於可使代碼簡潔清晰,支持不一樣設備,利於搜索引擎,便於團隊開發;
  2. 減小DOM節點:加速頁面渲染;
  3. 給圖片加上正確的寬高值:這能夠減小頁面重繪,同時防止圖片縮放;
  4. 防止src屬性和link的href屬性爲空:當值爲空時,瀏覽器極可能會把當前頁面當成其屬性值加載;
  5. 正確的閉合標籤:如避免使用<div/>,瀏覽器會多一個將它解析成<div\></div\>的過程;
  6. 連接爲目錄或首頁的地址後面加」/」,如http://www.5icool.org/;
  7. 用LINK而不用@import方式導入樣式;
  8. 樣式放在頁頭,JS放在頁尾;
  9. 縮小favicon.ico並緩存;

CSS部分

  1. 避免使用 CSS Expressions(CSS表達式):如
  2. 避免使用 CSS Filter(CSS濾鏡);
  3. 使用CSS縮寫,減小代碼量;
  4. 經過CSSSprites把同類圖片合成一張,減小圖片請求;
  5. 減小查詢層級:如.header .logo要好過.header .top .logo;
  6. 減小查詢範圍:如.header>li要好過.header li;
  7. 避免TAG標籤與CLASS或ID並存:如a.top、button#submit;
  8. 刪除重複的CSS;

JavaScript 性能調優

JavaScript 語言因爲它的單線程和解釋執行的兩個特色,決定了它自己有不少地方有性能問題,因此可改進的地方有很多。

eval 的問題:

比較下述代碼:

清單 1. eval 的問題
 var reference = {}, props = 「p1」; 
 eval(「reference.」 + props + 「=5」) 

 var reference = {}, props = 「p1」; 
 reference[props] = 5

有「eval」的代碼比沒有「eval」的代碼要慢上 100 倍以上。

主要緣由是:JavaScript 代碼在執行前會進行相似「預編譯」的操做:首先會建立一個當前執行環境下的活動對象,並將那些用 var 申明的變量設置爲活動對象的屬性,可是此時這些變量的賦值都是 undefined,並將那些以 function 定義的函數也添加爲活動對象的屬性,並且它們的值正是函數的定義。可是,若是你使用了「eval」,則「eval」中的代碼(實際上爲字符串)沒法預先識別其上下文,沒法被提早解析和優化,即沒法進行預編譯的操做。因此,其性能也會大幅度下降。

Function 的用法:

比較下述代碼:

清單 2. function 的用法
 var func1 = new Function(「return arguments[0] + arguments[1]」);
 func1(10, 20); 

 var func2 = function(){ return arguments[0] + arguments[1] };
 func2(10, 20);

這裏相似以前提到的「eval」方法,這裏「func1」的效率會比「func2」的效率差不少,因此推薦使用第二種方式。

函數的做用域鏈(scope chain):

JavaScript 代碼解釋執行,在進入函數內部時,它會預先分析當前的變量,並將這些變量納入不一樣的層級(level),通常狀況下:

局部變量放入層級 1(淺),全局變量放入層級 2(深)。若是進入「with」或「try – catch」代碼塊,則會增長新的層級,即將「with」或「catch」裏的變量放入最淺層(層 1),並將以前的層級依次加深。

參考以下代碼:

清單 3. 函數做用域鏈
 var myObj = … .. 
… .. 
 function process(){ 
 var images = document.getElementsByTagName("img"), 
 widget = document.getElementsByTagName("input"), 
 combination = []; 
 for(var i = 0; i < images.length; i++){ 
 combination.push(combine(images[i], widget[2*i])); 
 } 
 myObj.container.property1 = combination[0]; 
 myObj.container.property2 = combination[combination.length-1]; 
 }

這裏咱們能夠看到,「images」,「widget」,「combination」屬於局部變量,在層 1。「document」,「myObj」屬於全局變量,在層 2。

變量所在的層越淺,訪問(讀取或修改)速度越快,層越深,訪問速度越慢。因此這裏對「images」,「widget」,「combination」的訪問速度比「document」,「myObj」要快一些。因此推薦儘可能使用局部變量,可見以下代碼:

清單 4. 使用局部變量
 var myObj = … .. 
… .. 
 function process(){ 
 var doc = document;
 var images = doc.getElementsByTagName("img"), 
 widget = doc.getElementsByTagName("input"), 
 combination = []; 
 for(var i = 0; i < images.length; i++){ 
 combination.push(combine(images[i], widget[2*i])); 
 } 
 myObj.container.property1 = combination[0]; 
 myObj.container.property2 = combination[combination.length-1]; 
 }

咱們用局部變量「doc」取代全局變量「document」,這樣能夠改進性能,尤爲是對於大量使用全局變量的函數裏面。

再看以下代碼:

清單 5. 慎用 with
 var myObj = … .. 
… .. 
 function process(){ 
 var doc = document; 
    var images = doc.getElementsByTagName("img"), 
 widget = doc.getElementsByTagName("input"), 
 combination = []; 
 for(var i = 0; i < images.length; i++){ 
 combination.push(combine(images[i], widget[2*i])); 
 } 
 with (myObj.container) {
 property1 = combination[0];
 property2 = combination[combination.length-1];
				 }
 }

加上「with」關鍵字,咱們讓代碼更加簡潔清晰了,可是這樣作性能會受影響。正如以前說的,當咱們進入「with」代碼塊時,「combination」便從原來的層 1 變到了層 2,這樣,效率會大打折扣。因此比較一下,仍是使用原來的代碼:

清單 6. 改進 with
 var myObj = … .. 
… .. 
 function process(){ 
 var doc = document; 
 var images = doc.getElementsByTagName("img"), 
 widget = doc.getElementsByTagName("input"), 
 combination = []; 
 for(var i = 0; i < images.length; i++){ 
 combination.push(combine(images[i], widget[2*i])); 
 } 
 myObj.container.property1 = combination[0];
 myObj.container.property2 = combination[combination.length-1];
      }

可是這樣並非最好的方式,JavaScript 有個特色,對於 object 對象來講,其屬性訪問層級越深,效率越低,好比這裏的「myObj」已經訪問到了第 3 層,咱們能夠這樣改進一下:

清單 7. 縮小對象訪問層級
 var myObj = … .. 
… .. 
 function process(){ 
 var doc = document; 
    var images = doc.getElementsByTagName("img"), 
 widget = doc.getElementsByTagName("input"), 
 combination = []; 
 for(var i = 0; i < images.length; i++){ 
 combination.push(combine(images[i], widget[2*i])); 
 } 
 var ctn = myObj.container;
 ctn.property1 = combination[0];
 ctn.property2 = combination[combination.length-1];
      }

咱們用局部變量來代替「myObj」的第 2 層的「container」對象。若是有大量的這種對對象深層屬性的訪問,能夠參照以上方式提升性能。

字符串(String)相關

字符串拼接

常常看到這樣的代碼:

清單 8. 字符串簡單拼接
 str += 「str1」 + 「str2」

這是咱們拼接字符串經常使用的方式,可是這種方式會有一些臨時變量的建立和銷燬,影響性能,因此推薦使用以下方式拼接:

清單 9. 字符串數組方式拼接
 var str_array = []; 
 str_array.push(「str1」); 
 str_array.push(「str2」); 
 str = str_array.join(「」);

這裏咱們利用數組(array)的「join」方法實現字符串的拼接,尤爲是程序的老版本的 Internet Explore(IE6)上運行時,會有很是明顯的性能上的改進。

固然,最新的瀏覽器(如火狐 Firefox3+,IE8+ 等等)對字符串的拼接作了優化,咱們也能夠這樣寫:

清單 10. 字符串快速拼接
 str +=「str1」
 str +=「str2」

新的瀏覽器對「+=」作了優化,性能略快於數組的「join」方法。在不久的未來更新版本瀏覽器可能對「+」也會作優化,因此那時咱們能夠直接寫:str += 「str1」 + 「str2」。

隱式類型轉換

參考以下代碼:

清單 11. 隱式類型轉換
 var str = 「12345678」, arr = []; 
 for(var i = 0; i <= s.length; i++){ 
 arr.push( str.charAt(i)); 
 }

這裏咱們在每一個循環時都會調用字符串的「charAt」方法,可是因爲咱們是將常量「12345678」賦值給「str」,因此「str」這裏事實上並非一個字符串對象,當它每次調用「charAt」函數時,都會臨時構造值爲「12345678」的字符串對象,而後調用「charAt」方法,最後再釋放這個字符串臨時對象。咱們能夠作一些改進:

清單 12. 避免隱式類型轉換
 var str = new Stirng(「12345678」), arr = []; 
 for(var i = 0; i <= s.length; i++){ 
 arr.push( str.charAt(i)); 
 }

這樣一來,變量「str」做爲一個字符串對象,就不會有這種隱式類型轉換的過程了,這樣一來,效率會顯著提升。

字符串匹配

JavaScript 有 RegExp 對象,支持對字符串的正則表達式匹配。是一個很好的工具,可是它的性能並非很是理想。相反,字符串對象(String)自己的一些基本方法的效率是很是高的,好比「substring」,「indexOf」,「charAt」等等,在咱們須要用正則表達式匹配字符串時,能夠考慮一下:

  1. 是否可以經過字符串對象自己支持的基本方法解決問題。
  2. 是否能夠經過「substring」來縮小須要用正則表達式的範圍。

這些方式都可以有效的提升程序的效率。

關於正則表達式對象,還有一點須要注意,參考以下代碼:

清單 13. 正則表達式
 for(var i = 0; i <= str_array.length; i++){ 
 if(str_array[i].match(/^s*extra\s/)){ 
……………………
 } 
 }

這裏,咱們往「match」方法傳入「/^s*extra\s/」是會影響效率的,它會構建臨時值爲「/^s*extra\s/」的正則表達式對象,執行「match」方法,而後銷燬臨時的正則表達式對象。咱們能夠這樣作:

清單 14. 利用變量
 var sExpr = /^s*extra\s/;
 for(var i = 0; i <= str_array.length; i++){ 
 if(str_array[i].match(sExpr)){ 
……………………
 } 
 }

這樣就不會有臨時對象了。

setTimeout 和 setInterval

「setTimeout」和「setInterval」這兩個函數能夠接受字符串變量,可是會帶來和以前談到的「eval」相似的性能問題,因此建議仍是直接傳入函數對象自己。

利用提早退出

參考以下兩段代碼:

清單 15. 利用提早退出
  // 代碼 1
 var name = … .; 
 var source = …… ; 
 if(source.match(/ …… /)){ 
……………………………
 } 


 // 代碼 2
 var name = … .; 
 var source = …… ; 
 if(name.indexOf( … ) &&source.match(/ …… /)){ 
……………………………
 }

代碼 2 多了一個對「name.indexOf( … )」的判斷,這使得程序每次走到這一段時會先執行「indexOf」的判斷,再執行後面的「match」,在「indexOf」比「match」效率高不少的前提下,這樣作會減小「match」的執行次數,從而必定程度的提升效率。

 

DOM 操做性能調優

JavaScript 的開發離不開 DOM 的操做,因此對 DOM 操做的性能調優在 Web 開發中也是很是重要的。

Repaint 和 Reflow

Repaint 也叫 Redraw,它指的是一種不會影響當前 DOM 的結構和佈局的一種重繪動做。以下動做會產生 Repaint 動做:

  1. 不可見到可見(visibility 樣式屬性)
  2. 顏色或圖片變化(background, border-color, color 樣式屬性)
  3. 不改變頁面元素大小,形狀和位置,但改變其外觀的變化

Reflow 比起 Repaint 來說就是一種更加顯著的變化了。它主要發生在 DOM 樹被操做的時候,任何改變 DOM 的結構和佈局都會產生 Reflow。但一個元素的 Reflow 操做發生時,它的全部父元素和子元素都會放生 Reflow,最後 Reflow 必然會致使 Repaint 的產生。舉例說明,以下動做會產生 Repaint 動做:

  1. 瀏覽器窗口的變化
  2. DOM 節點的添加刪除操做
  3. 一些改變頁面元素大小,形狀和位置的操做的觸發

減小 Reflow

經過 Reflow 和 Repaint 的介紹可知,每次 Reflow 比其 Repaint 會帶來更多的資源消耗,咱們應該儘可能減小 Reflow 的發生,或者將其轉化爲只會觸發 Repaint 操做的代碼。

參考以下代碼:

清單 16. reflow 介紹
 var pDiv = document.createElement(「div」); 
 document.body.appendChild(pDiv);----- reflow
 var cDiv1 = document.createElement(「div」); 
 var cDiv2 = document.createElement(「div」); 
 pDiv.appendChild(cDiv1);----- reflow
 pDiv.appendChild(cDiv2);----- reflow

這是咱們常常接觸的代碼了,可是這段代碼會產生 3 次 reflow。再看以下代碼:

清單 17. 減小 reflow
 var pDiv = document.createElement(「div」); 
 var cDiv1 = document.createElement(「div」); 
 var cDiv2 = document.createElement(「div」); 
 pDiv.appendChild(cDiv1); 
 pDiv.appendChild(cDiv2); 
 document.body.appendChild(pDiv);----- reflow

這裏便只有一次 reflow,因此咱們推薦這種 DOM 節點操做的方式。

關於上述較少 Reflow 操做的解決方案,還有一種能夠參考的模式:

清單 18. 利用 display 減小 reflow
 var pDiv = document.getElementById(「parent」); 
 pDiv.style.display = 「none」----- reflow

 pDiv.appendChild(cDiv1); 
 pDiv.appendChild(cDiv2); 
 pDiv.appendChild(cDiv3); 
 pDiv.appendChild(cDiv4); 
 pDiv.appendChild(cDiv5); 
 pDiv.style.width = 「100px」; 
 pDiv.style.height = 「100px」; 

 pDiv.style.display = 「block」----- reflow

先隱藏 pDiv,再顯示,這樣,隱藏和顯示之間的操做便不會產生任何的 Reflow,提升了效率。

特殊測量屬性和方法

DOM 元素裏面有一些特殊的測量屬性的訪問和方法的調用,也會觸發 Reflow,比較典型的就是「offsetWidth」屬性和「getComputedStyle」方法。

圖 1. 特殊測量屬性和方法

圖 1. 特殊測量屬性和方法

這些測量屬性和方法大體有這些:

  • offsetLeft
  • offsetTop
  • offsetHeight
  • offsetWidth
  • scrollTop/Left/Width/Height
  • clientTop/Left/Width/Height
  • getComputedStyle()
  • currentStyle(in IE))

這些屬性和方法的訪問和調用,都會觸發 Reflow 的產生,咱們應該儘可能減小對這些屬性和方法的訪問和調用,參考以下代碼:

清單 19. 特殊測量屬性
 var pe = document.getElementById(「pos_element」); 
 var result = document.getElementById(「result_element」); 
 var pOffsetWidth = pe.offsetWidth;
 result.children[0].style.width  = pOffsetWidth; 
 result.children[1].style.width  = pOffsetWidth; 
 result.children[2].style.width  = pOffsetWidth; 
…………其餘修改…………

這裏咱們能夠用臨時變量將「offsetWidth」的值緩存起來,這樣就不用每次訪問「offsetWidth」屬性。這種方式在循環裏面很是適用,能夠極大地提升性能。

樣式相關

咱們確定常常見到以下的代碼:

清單 20. 樣式相關
 var sElement = document.getElementById(「pos_element」); 
 sElement.style.border = ‘ 1px solid red ’
 sElement.style.backgroundColor = ‘ silver ’
 sElement.style.padding = ‘ 2px 3px ’
 sElement.style.marginLeft = ‘ 5px ’

可是能夠看到,這裏的每個樣式的改變,都會產生 Reflow。須要減小這種狀況的發生,咱們能夠這樣作:

解決方案 1:

清單 21. className 解決方案
 .class1 { 
 border: ‘ 1px solid red ’
 background-color: ‘ silver ’
 padding: ‘ 2px 3px ’
 margin-left: ‘ 5px ’
 } 
 document.getElementById(「pos_element」).className = ‘class1’ ;

用 class 替代 style,能夠將原有的全部 Reflow 或 Repaint 的次數都縮減到一個。

解決方案 2:

清單 22. cssText 解決方案
 var sElement = document.getElementById(「pos_element」); 
 var newStyle = ‘ border: 1px solid red; ’ + ‘ background-color: silver; ’ + 
                                 ‘ padding: 2px 3px; ’ + 「margin-left: 5px;」
 sElement.style.cssText += newStyle;

一次性設置全部樣式,也是減小 Reflow 提升性能的方法。

XPath

一個頁面上每每包含 1000 多頁面元素,在定位具體元素的時候,每每須要必定的時間。若是用 id 或 name 定位可能效率不會太慢,若是用元素的一些其餘屬性(好比 className 等等)定位,可能效率有不理想了。有的可能只能經過遍歷全部元素(getElementsByTagName)而後過濾才能找到相應元素,這就更加低效了,這裏咱們推薦使用 XPath 查找元素,這是不少瀏覽器自己支持的功能。

清單 23. XPath 解決方案
 if(document.evaluate){ 
 var tblHeaders = document.evaluate(「//body/div/table//th」);
 var result = tblHeaders.iterateNext(); 
 while(result) { 
 result.style.border = 「1px dotted blue」; 
 result ………………
 result = xpathResult.iterateNext(); 
 } 
 } else{ //getElementsByTagName() ……
 // 處理瀏覽器不支持 XPath 的狀況
………………………………
 }

瀏覽器 XPath 的搜索引擎會優化搜索效率,大大縮短結果返回時間。

HTMLCollection 對象

這是一類特殊的對象,它們有點像數組,但不徹底是數組。下述方法的返回值通常都是 HTMLCollection 對象:

  • document.images, document.forms
  • getElementsByTagName()
  • getElementsByClassName()

這些 HTMLCollection 對象並非一個固定的值,而是一個動態的結果。它們是一些比較特殊的查詢的返回值,在以下狀況下,它們會從新執行以前的查詢而獲得新的返回值(查詢結果),雖然多數狀況下會和前一次或幾回的返回值都同樣:

  • Length 屬性
  • 具體的某個成員

因此,HTMLCollection 對象對這些屬性和成員的訪問,比起數組來要慢不少。固然也有例外,Opera 和 Safari 對這種狀況就處理的很好,不會有太大性能問題。

參考以下代碼:

清單 24. HTMLConnection 對象
 var items = [「test1」, 「test2」, 「test3」, ……………… ];
 for(var i = 0; i < items.length; i++){ 
………………………………
 } 

 var items = document.getElementsByTagName(「div」);
 for(var i = 0; i < items.length; i++){ 
…………………………………… . 
 }

上述兩端代碼,下面的效率比起上面一段要慢不少,由於每個循環都會有「items.length」的觸發,也就會致使「document.getElementsByTagName(..)」方法的再次調用,這即是效率便會大幅度降低的緣由。咱們能夠這樣解決:

清單 25. HTMLConnection 對象解決方案
 var items = document.getElementsByTagName(「div」); 
 var len = items.length
 for(var i = 0; i < len; i++){ 
…………………………………… . 
 }

這樣一來,效率基本與普通數組同樣。

動態建立 script 標籤

加載並執行一段 JavaScript 腳本是須要必定時間的,在咱們的程序中,有時候有些 JavaScript 腳本被加載後基本沒有被使用過 (好比:腳本里的函數歷來沒有被調用等等)。加載這些腳本只會佔用 CPU 時間和增長內存消耗,下降 Web 應用的性能。因此推薦動態的加載 JavaScript 腳本文件,尤爲是那些內容較多,消耗資源較大的腳本文件。

清單 26. 建立 script 標籤
 if(needXHR){ 
 document.write(「<script type= ’ test\/JavaScript ’ src= 'dojo_xhr.js' >」); 
 } 
 if(dojo.isIE){ 
 document.write(「<script type= ’ test\/JavaScript ’ src= 'vml.js' >」); 
 }
 
參考連接:http://www.5icool.org/a/201306/a1924.html
                 http://www.ibm.com/developerworks/cn/web/1107_zhouxiang_tunejs/
相關文章
相關標籤/搜索