Web前端性能優(yōu)化進(jìn)階——完結(jié)篇

前言

在之前的文章 如何優(yōu)化網(wǎng)站性能碎赢,提高頁面加載速度 中低剔,我們簡單介紹了網(wǎng)站性能優(yōu)化的重要性以及幾種網(wǎng)站性能優(yōu)化的方法(沒有看過的可以狂戳 鏈接 移步過去看一下),那么今天我們深入討論如何進(jìn)一步優(yōu)化網(wǎng)站性能肮塞。

一襟齿、拆分初始化負(fù)載

拆分初始化負(fù)載——聽名字覺得高大上,其實(shí)不然枕赵,土一點(diǎn)將講就是將頁面加載時(shí)需要的一堆JavaScript文件蕊唐,分成兩部分:渲染頁面所必需的(頁面出來,沒他不行)和剩下的烁设。頁面初始化時(shí)替梨,只加載必須的,其余的等會(huì)加載装黑。

其實(shí)在現(xiàn)實(shí)生產(chǎn)環(huán)境中副瀑,對于大部分網(wǎng)站:頁面加載完畢(window.onload觸發(fā))時(shí),已經(jīng)執(zhí)行的JavaScript函數(shù)只占到全部加載量的少部分恋谭,譬如10%到20%或者更少糠睡。

注意:這里所說的頁面加載完畢是指window.onload觸發(fā)。window.onload什么時(shí)候出發(fā)疚颊?當(dāng)頁面中的內(nèi)容(包括圖片狈孔、樣式、腳本)全部加載到瀏覽器時(shí)材义,才會(huì)觸發(fā)window.onload均抽,請與jQuery中$(document).ready作區(qū)分。

上面我們可以看到大部分JavaScript函數(shù)下載之后并未執(zhí)行其掂,這就造成了浪費(fèi)油挥。因此,如果我們能夠使用某種方式來延遲這部分未使用的代碼的加載款熬,那想必可以極大的縮減頁面初始化時(shí)候的下載量深寥。

**拆分文件 **

我們可以將原來的代碼文件拆分成兩部分:渲染頁面所必需的(頁面出來,沒他不行)和剩下的贤牛;頁面加載時(shí)只加載必須的惋鹅,剩余的JavaScript代碼在頁面加載完成之后采用無阻塞下載技術(shù)立即下載。

需要注意的問題:

1. 我們可以通過某些工具(譬如:Firebug)來獲得頁面加載時(shí)執(zhí)行的函數(shù)殉簸,從而將這些代碼拆分成一個(gè)單獨(dú)的文件闰集。那么問題來了沽讹,有些代碼在頁面加載的時(shí)候不會(huì)執(zhí)行,但是確實(shí)必須的返十,譬如條件判斷代碼或者錯(cuò)誤處理的代碼妥泉。另外JavaScript的作用域問題是相對比較奇葩的椭微,這些都給拆分造成了很大的困難

2. 關(guān)于未定義標(biāo)識符的錯(cuò)誤洞坑,譬如已加載的JavaScript代碼在執(zhí)行時(shí),引用了一個(gè)被我們拆分延遲加載的JavaScript代碼中的變量蝇率,就會(huì)造成錯(cuò)誤迟杂。舉個(gè)栗子:

頁面加載完成時(shí)用戶點(diǎn)擊了某個(gè)按鈕(此時(shí)原JavaScript文件被拆分,只下載了頁面加載所必需的的代碼)本慕,而監(jiān)聽此按鈕的代碼還沒有被下載(因?yàn)檫@不是頁面加載所必需的排拷,所以在拆分時(shí)被降級了),所以點(diǎn)擊就沒有響應(yīng)或者直接報(bào)錯(cuò)(找不到事件處理函數(shù))锅尘。

解決方案:

1. 在低優(yōu)先級的代碼被加載完成時(shí)监氢,按鈕處于不可用狀態(tài)(可附帶提示信息);

2. 使用樁函數(shù)藤违,樁函數(shù)與原函數(shù)名字相同浪腐,但是函數(shù)體為空,這樣就可以防止報(bào)錯(cuò)了顿乒。當(dāng)剩余的代碼加載完成時(shí)议街,樁函數(shù)就被原來的同名函數(shù)覆蓋掉。我們可以做的再狠一點(diǎn):記錄用戶的行為(點(diǎn)擊璧榄、下拉)特漩,當(dāng)剩余的代碼加載完成時(shí),再根據(jù)記錄調(diào)用相應(yīng)的函數(shù)骨杂。

****二涂身、無阻塞加載腳本****

大多數(shù)瀏覽器可以并行下載頁面所需要的組件,然而對于腳本文件卻并非如此搓蚪。腳本文件在下載時(shí)访得,在其下載完成、解析執(zhí)行完畢之前陕凹,并不會(huì)下載任何其他的內(nèi)容悍抑。這么做是有道理的,因?yàn)闉g覽器并不知道腳本是否會(huì)操作頁面的內(nèi)容杜耙;其次搜骡,后面加載的腳本可能會(huì)依賴前面的腳本 ,如果并行下載佑女,后面的腳本可能會(huì)先下載完并執(zhí)行记靡,產(chǎn)生錯(cuò)誤谈竿。所以,之前我們講到了腳本應(yīng)該盡可能放在底部接近</body>的位置摸吠,就是為了盡量減少整個(gè)頁面的影響空凸。

接下來我們討論幾種技術(shù)可以使頁面不會(huì)被腳本的下載阻塞:

1、Script Defer

<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: &quot;Courier New&quot; !important; font-size: 12px !important;"><script type="text/javascript" src="file1.js" defer></script></pre>

支持瀏覽器: IE4+ 寸痢、Firefox 3.5+以及其它新版本的瀏覽器

defer表示該腳本不打算修改DOM呀洲,可以稍后執(zhí)行。

2啼止、動(dòng)態(tài)腳本元素

<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: &quot;Courier New&quot; !important; font-size: 12px !important;">var script = document.createElement ("script");
script.type = "text/javascript";
script.src = "a.js"; 
document.body.appendChild(script);</pre>

用動(dòng)態(tài)創(chuàng)建script標(biāo)簽的方法不會(huì)阻塞其它的頁面處理過程道逗,在IE下還可以并行下載腳本。

3献烦、XHR(XMLHttpRequest)Eval

該方法通過XMLHttpRequest以非阻塞的方式從服務(wù)端加載腳本滓窍,加載完成之后通過eval解析執(zhí)行。

<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: &quot;Courier New&quot; !important; font-size: 12px !important;"> 1 var xhr = getXHRObj(); 
 xhr.onreadystatechange = function() {
      if(xhr.readyState == 4 && xhr.status == 200) {
          eval(xhr.responseText);
      }
  };
  
  xhr.open('GET','text.js',true); 10 xhr.send('');
 function getXHRObj() {      // ......
   return xhrObj; 
 }</pre>

該方式不會(huì)阻塞頁面中其它組件的下載巩那。

缺點(diǎn):(1)腳本的域必須和主頁面在相同的域中吏夯;(2)eval的安全性問題

**4、XHR Injection **

XMLHttpRequest Injection(XHR腳本注入)和XHR Eval類似即横,都是通過 XMLHttpRequest 來獲取JavaScript的噪生。 在獲得文件之后 ,將會(huì)創(chuàng)建一個(gè)script標(biāo)簽將得到的代碼注入頁面令境。

<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: &quot;Courier New&quot; !important; font-size: 12px !important;"> 
 1 var xhr = new XMLHttpRequest(); 
 2 xhr.open("GET", "test.js", true); 
 3 xhr.send('');
 4 xhr.onreadystatechange = function(){
 5     if (xhr.readyState == 4){
 6        if (xhr.status >= 200 && xhr.status < 300 || xhr.status == 304){ 
 7             var script = document.createElement("script"); 
 8             script.type = "text/javascript";
 9             script.text = xhr.responseText; 
10 document.body.appendChild(script); 
11 } 
12 } 
13 }; </pre>

XMLHttpRequest獲取的內(nèi)容必須和主頁處于相同的域杠园。

5、Script元素的src屬性

<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: &quot;Courier New&quot; !important; font-size: 12px !important;">1 var script = document.createElement('script'); 2 script.src = 'http://a.com/a.js'
3 document.body.appendChild(script);</pre>

這種方式不會(huì)阻塞其它組件舔庶,而且允許跨域獲取腳本抛蚁。

**6、IFrame嵌入Script **

頁面中的iframe和其它元素是并行下載的惕橙,因此可以利用這點(diǎn)將需要加載的腳本嵌入iframe中瞧甩。

<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: &quot;Courier New&quot; !important; font-size: 12px !important;"> <iframe src="1.html" frameborder="0" width=0 height="0"></iframe></pre>

注意:這里是1.html而不是1.js,iframe以為這是html文件弥鹦,而我們則把要加載的腳本嵌入其中肚逸。

這種方式要求iframe的請求url和主頁面同域。

三彬坏、整合異步腳本

上面我們介紹了如何異步加載腳本朦促,提高頁面的加載速度。但是異步加載腳本也是存在問題的栓始,譬如行內(nèi)腳本依賴外部腳本里面定義的標(biāo)識务冕,這樣當(dāng)內(nèi)聯(lián)的腳本執(zhí)行的時(shí)候外部腳本還沒有加載完成,那么就會(huì)發(fā)生錯(cuò)誤幻赚。

那么接下來我們就討論一下如何實(shí)現(xiàn)在異步加載腳本的時(shí)候又能保證腳本的能夠按照正確的順序執(zhí)行禀忆。

單個(gè)外部腳本與內(nèi)聯(lián)腳本

譬如:內(nèi)聯(lián)腳本使用了外部腳本定義的標(biāo)識符臊旭,外部腳本采用異步加載提高加載速度

<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: &quot;Courier New&quot; !important; font-size: 12px !important;">$(".button").click(function() {
    alert("hello");
}); </pre>
<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: &quot;Courier New&quot; !important; font-size: 12px !important;"><script src="jquery.js"></script></pre>

1、Script Onload

通過Script的onload方法監(jiān)聽腳本是否加載完成箩退,將依賴外部文件的內(nèi)聯(lián)代碼寫在init函數(shù)中绸硕,在onload事件函數(shù)中調(diào)用init函數(shù)开镣。

script.onload的支持情況:

IE6队寇、IE7斋竞、IE8不支持onload,可以用onreadystatechange來代替喊括。

IE9胧瓜、IE10先觸發(fā)onload事件矢棚,再觸發(fā)onreadystatechange事件

IE11(Edge)只觸發(fā)onload事件

其他瀏覽器支持均支持onload郑什,在opera中onload和onreadystatechange均有效。

<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: &quot;Courier New&quot; !important; font-size: 12px !important;"> 
 1 function init() { 
 2     // inline code......
 3 }
 4 var script = document.createElement("script");  
 5 script.type = "text/javascript";  
 6 script.src = "a.js";
 7 script.onloadDone = false;    
 8 
 9 script.onreadystatechange = function(){ 
10      if((script.readyState == 'loaded' || script.readyState == 'complete') && !script.onloadDone){ 
11         // alert("onreadystatechange"); 
12 init(); 
13 } 
14 } 
15 
16 script.onload = function(){ 
17     // alert("onload");
18 init(); 
19     script.onloadDone = true; 
20 } 
21 
22 document.getElementsByTagName('head')[0].appendChild(script);    </pre>

這里onloadDone用來防止在IE9蒲肋、IE10已結(jié)opera中初始化函數(shù)執(zhí)行兩次蘑拯。

Script Onload是整合內(nèi)聯(lián)腳本和外部異步加載腳本的首選。

推薦指數(shù):5顆星

2兜粘、硬編碼回調(diào)

將依賴外部文件的內(nèi)聯(lián)代碼寫在init函數(shù)中申窘,修改異步加載的文件,在文件中添加對init函數(shù)的調(diào)用孔轴。

缺點(diǎn):要修改外部文件剃法,而我們一般不會(huì)修改第三方的插件;缺乏靈活性路鹰,改變回調(diào)接口時(shí)贷洲,需要修改外部的腳本。

推薦指數(shù):2顆星

3晋柱、定時(shí)器

將依賴外部文件的內(nèi)聯(lián)代碼寫在init函數(shù)中优构,采用定時(shí)器的方法檢查依賴的名字空間是否存在。若已經(jīng)存在雁竞,則調(diào)用init函數(shù)钦椭;若不存在,則等待一段時(shí)間在檢查碑诉。

<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: &quot;Courier New&quot; !important; font-size: 12px !important;">function init() { // inline code......
} var script = document.createElement("script");  
script.type = "text/javascript";  
script.src = "jquery.js";
document.getElementsByTagName('head')[0].appendChild(script); function timer() { if("undefined" === typeof(jQuery)) {
        setTimeout(timer,500);
    } else {
        init();
    }
}

timer();</pre>

缺點(diǎn):

如果setTimeout設(shè)置的時(shí)間間隔過小彪腔,則可能會(huì)增加頁面的開銷;如果時(shí)間間隔過大进栽,就會(huì)發(fā)生外部腳本加載完畢而行內(nèi)腳本需要間隔一段才能時(shí)間執(zhí)行的狀況德挣,從而造成浪費(fèi)。

如果外部腳本(jquery.js)加載失敗泪幌,則這個(gè)輪詢將會(huì)一直持續(xù)下去盲厌。

增加維護(hù)成本署照,因?yàn)槲覀冃枰ㄟ^外部腳本的特定標(biāo)識符來判斷腳本是否加載完畢,如果外部腳本的標(biāo)識符變了吗浩,則行內(nèi)的代碼也需要改變建芙。

推薦指數(shù):2顆星

4、window.onload

我們可以使用window.onload事件來觸發(fā)行內(nèi)代碼的執(zhí)行懂扼,但是這要求外部的腳本必須在window.onload事件觸發(fā)之前下載完畢禁荸。

在 ****無阻塞加載腳本****提到的技術(shù)中,IFrame嵌入Script 阀湿、動(dòng)態(tài)腳本元素 赶熟、**Script Defer****** 可以滿足這點(diǎn)要求。

<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: &quot;Courier New&quot; !important; font-size: 12px !important;">
1 function init() { 
2     // inline code......
3 } 
4 if(window.addEventListener) { 
5     window.addEventListener("load",init,false); 
6 } 
7 else if(window.attachEvent) { 
8     window.attachEvent("onload",init); 
9 }</pre>

缺點(diǎn):這會(huì)阻塞window.onload事件陷嘴,所以并不是一個(gè)很好的辦法映砖;如果頁面中還有很多其他資源(譬如圖片、Flash等)灾挨,那么行內(nèi)腳本將會(huì)延遲執(zhí)行(就算它依賴的外部腳本一早就加載完了)邑退,因?yàn)閣indow.onload不會(huì)觸發(fā)。

推薦指數(shù):3顆星

5劳澄、降級使用script

來來來地技,先看看它什么樣子:

<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: &quot;Courier New&quot; !important; font-size: 12px !important;"><script src="jquery.js" type="text/javascript"> $(".button").click(function() {
        alert("hello");
    }); </script></pre>

然并卵,目前還沒有瀏覽器可以實(shí)現(xiàn)這種方式秒拔,一般情況下莫矗,外部腳本(jquery.js)加載成功后,兩個(gè)標(biāo)簽之間的代碼就不會(huì)執(zhí)行了砂缩。

但是我們可以改進(jìn)一下:修改外部腳本的代碼作谚,讓它在DOM樹種搜索自己,用innerHTML獲取自己內(nèi)部的代碼梯轻,然后用eval執(zhí)行食磕,就可以解決問題了。

然后我們在修改一下讓它異步加載喳挑,就變成了這樣:

<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: &quot;Courier New&quot; !important; font-size: 12px !important;">
1 function init() { 
2     // inline code......
3 } 
4 var script = document.createElement("script"); 
5 script.type = "text/javascript"; 
6 script.src = "jquery.js"; 
7 script.innerHTML = "init()'"
8 document.getElementsByTagName('head')[0].appendChild(script); </pre>

而在外部腳本中我們需要添加如下代碼:

<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: &quot;Courier New&quot; !important; font-size: 12px !important;">
1 var scripts = document.getElementsByTagName("script"); 
2 
3 for(var i = 0; i < scripts.length;i++) { 
4     if(-1 != scripts[i].src.indexOf('jquery.js')) { 
5 eval(script.innerHTML); 
6         break; 
7 } 
8 }</pre>

這樣就大功告成 彬伦。然而,缺點(diǎn)也很明顯伊诵,我們還是需要修改外部文件的代碼单绑。

推薦指數(shù):2顆星

內(nèi)聯(lián)腳本、多個(gè)外部腳本相互依賴

舉個(gè)栗子:

內(nèi)聯(lián)腳本依賴a.js曹宴,a.js依賴b.js搂橙;

這種情況比較麻煩(好吧,是因?yàn)槲姨耍┑烟梗唵谓榻B一下思路:

確保a.js在b.js之后執(zhí)行区转,內(nèi)聯(lián)腳本在a.js之后執(zhí)行苔巨。

我們可以使用XMLHttpRequest同時(shí)異步獲取兩個(gè)腳本,如果a.js先下載完成废离,則判斷b.js是否下載完成侄泽,如果下載完成則執(zhí)行,否則等待蜻韭,a.js執(zhí)行之后就可以調(diào)用內(nèi)聯(lián)腳本執(zhí)行了悼尾。b.js下載完成之后即可執(zhí)行。

代碼大概這樣(求指正):

<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: &quot;Courier New&quot; !important; font-size: 12px !important;"> 
1 function init() { 
2     // inline code......
3 }
4 
5 
6 var xhrA = new XMLHttpRequest(); 
7 var xhrB = new XMLHttpRequest(); 
8 var scriptA , scriptB; 
9 
10 var scriptA = document.createElement("script"); 
11 scriptA.type = "text/javascript"; 
12 
13 var scriptB = document.createElement("script"); 
14 scriptB.type = "text/javascript"; 
15 
16 scriptA = scriptB = false; 
17 
18 xhrA.open("GET", "a.js", true); 
19 xhrA.send(''); 
20 xhrA.onreadystatechange = function(){ 
21     if (xhr.readyState == 4){ 
22         if (xhr.status >= 200 && xhr.status < 300 || xhr.status == 304){ 
23             scriptA.text = xhr.responseText; 
24             scriptA = true; 
25             if(scriptB) { 
26 document.body.appendChild(scriptA); 
27 init(); 
28 } 
29 } 
30 } 
31 }; 
32 
33 xhrB.open("GET", "b.js", true); 
34 xhrB.send(''); 
35 xhrB.onreadystatechange = function(){ 
36     if (xhr.readyState == 4){ 
37         if (xhr.status >= 200 && xhr.status < 300 || xhr.status == 304){ 
38             scriptB.text = xhr.responseText; 
39             scriptB = true
40 document.body.appendChild(scriptB); 
41             if(scriptA) { 
42 document.body.appendChild(scriptA); 
43 init(); 
44 } 
45 } 
46 } 
47 }; </pre>

四肖方、編寫高效的JavaScript

之前講過了闺魏,大家可以猛戳 這里 看一下。

五俯画、CSS選擇器優(yōu)化

1析桥、在談?wù)撨x擇器優(yōu)化之前,我們先簡單介紹一下選擇器的類型:

ID選擇器 : #id;

類選擇器: .class

標(biāo)簽選擇器: a

兄弟選擇器:#id + a 

子選擇器: #id > a

后代選擇器: #id a

通賠選擇器: *

屬性選擇器: input[type='input']

偽類和偽元素:a:hover , div:after

組合選擇器:#id,.class

2活翩、瀏覽器的匹配規(guī)則

abc > a怎么匹配烹骨? 有人可能會(huì)以為:先找到id為abc的元素翻伺,再查找子元素為a的元素2男埂!too young吨岭,too simple拉宗!

其實(shí),瀏覽器時(shí)從右向左匹配選擇符的@北琛5┦隆!那么上面的寫法效率就低了:先查找頁面中的所有a標(biāo)簽急灭,在看它的父元素是不是id為abc

知道了瀏覽器的匹配規(guī)則我們就能盡可能的避免開銷很大的選擇器了:

避免通配規(guī)則

除了 * 之外姐浮,還包括子選擇器、后臺(tái)選擇器等葬馋。

而它們之間的組合更加逆天卖鲤,譬如:li *

瀏覽器會(huì)查找頁面的所有元素,然后一層一層地尋找他的祖先畴嘶,看是不是li蛋逾,這對可能極大地?fù)p耗性能。

不限定ID選擇器

ID就是唯一的窗悯,不要寫成類似div#nav這樣区匣,沒必要。

不限定class選擇器

我們可以進(jìn)一步細(xì)化類名蒋院,譬如li.nav 寫成 nav-item

盡量避免后代選擇器

通常后代選擇器是開銷最高的亏钩,如果可以莲绰,請使用子選擇器代替。

替換子選擇器

如果可以姑丑,用類選擇器代替子選擇器钉蒲,譬如

nav > li 改成 .nav-item

依靠繼承

了解那些屬性可以依靠繼承得來,從而避免重復(fù)設(shè)定規(guī)則彻坛。

3顷啼、關(guān)鍵選擇符

選擇器中最右邊的選擇符成為關(guān)鍵選擇符,它對瀏覽器執(zhí)行的工作量起主要影響昌屉。

舉個(gè)栗子:

div div li span.class-special

乍一看钙蒙,各種后代選擇器組合,性能肯定不能忍间驮。其實(shí)仔細(xì)一想躬厌,瀏覽器從右向左匹配,如果頁面中span.class-special的元素只有一個(gè)的話竞帽,那影響并不大啊扛施。

反過來看,如果是這樣

span.class-special li div div ,盡管span.class-special很少屹篓,但是瀏覽器從右邊匹配疙渣,查找頁面中所有div在層層向上查找,那性能自然就低了堆巧。

4妄荔、重繪與回流

優(yōu)化css選擇器不僅僅提高頁面加載時(shí)候的效率,在頁面回流谍肤、重繪的時(shí)候也可以得到不錯(cuò)的效果啦租,那么接下來我們說一下重繪與回流。

4.1荒揣、從瀏覽器的渲染過程談起

解析HTML構(gòu)建dom樹→構(gòu)建render樹→布局render樹→繪制render樹

1)構(gòu)建dom樹

根據(jù)獲得的html代碼生成一個(gè)DOM樹篷角,每個(gè)節(jié)點(diǎn)代表一個(gè)HTML標(biāo)簽,根節(jié)點(diǎn)是document對象系任。dom樹種包含了所有的HTML標(biāo)簽恳蹲,包括未顯示的標(biāo)簽(display:none)和js添加的標(biāo)簽。

2)構(gòu)建cssom樹

將得到所有樣式(瀏覽器和用戶定義的css)除去不能識別的(錯(cuò)誤的以及css hack)赋除,構(gòu)建成一個(gè)cssom樹

3)cssom和dom結(jié)合生成渲染樹阱缓,渲染樹中不包括隱藏的節(jié)點(diǎn)包括(display:none、head標(biāo)簽)举农,而且每個(gè)節(jié)點(diǎn)都有自己的style屬性荆针,渲染樹種每一個(gè)節(jié)點(diǎn)成為一個(gè)盒子(box)。注意:透明度為100%的元素以及visibility:hidden的元素也包含在渲染樹之中,因?yàn)樗麄儠?huì)影響布局航背。

4)瀏覽器根據(jù)渲染樹來繪制頁面

4.2喉悴、重繪(repaint)與回流(reflow)

1)重繪 當(dāng)渲染樹中的一部分或者全部因?yàn)轫撁嬷心承┰氐牟季帧@示與隱藏玖媚、尺寸等改變需要重新構(gòu)建箕肃,這就是回流。每個(gè)頁面至少會(huì)發(fā)生一次回流,在頁面第一次加載的時(shí)候發(fā)生今魔。在回流的時(shí)候勺像,瀏覽器會(huì)使渲染樹中受到影響的部分失效,并重新構(gòu)造這部分渲染樹错森,完成回流后吟宦,瀏覽器會(huì)重新繪制受影響的部分到屏幕中,該過程成為重繪涩维。

2. 當(dāng)渲染樹中的一些元素需要更新屬性殃姓,而這些屬性不會(huì)影響布局,只影響元素的外觀瓦阐、風(fēng)格蜗侈,比如color、background-color睡蟋,則稱為重繪踏幻。

注意:回流必將引起重繪,而重繪不一定會(huì)引起回流薄湿。

4.3叫倍、回流何時(shí)發(fā)生:

當(dāng)頁面布局和幾何屬性改變時(shí)就需要回流。下述情況會(huì)發(fā)生瀏覽器回流:

1豺瘤、添加或者刪除可見的DOM元素;

2听诸、元素位置改變坐求;

3、元素尺寸改變——邊距晌梨、填充桥嗤、邊框、寬度和高度

4仔蝌、內(nèi)容改變——比如文本改變或者圖片大小改變而引起的計(jì)算值寬度和高度改變泛领;

5、頁面渲染初始化敛惊;

6渊鞋、瀏覽器窗口尺寸改變——resize事件發(fā)生時(shí);

4.4、如何影響性能

頁面上任何一個(gè)結(jié)點(diǎn)觸發(fā)reflow锡宋,都會(huì)導(dǎo)致它的子結(jié)點(diǎn)及祖先結(jié)點(diǎn)重新渲染儡湾。

每次重繪和回流發(fā)生時(shí),瀏覽器會(huì)根據(jù)對應(yīng)的css重新繪制需要渲染的部分执俩,如果你的選擇器不優(yōu)化徐钠,就會(huì)導(dǎo)致效率降低,所以優(yōu)化選擇器的重要性可見一斑役首。

六尝丐、盡量少用iframe

在寫網(wǎng)頁的時(shí)候,我們可能會(huì)用到iframe衡奥,iframe的好處是它完全獨(dú)立于父文檔摊崭。iframe中包含的JavaScript文件訪問其父文檔是受限的。例如杰赛,來自不同域的iframe不能訪問其父文檔的Cookie呢簸。

開銷最高的DOM元素

通常創(chuàng)建iframe元素的開銷要比創(chuàng)建其它元素的開銷高幾十倍甚至幾百倍。

iframe阻塞onload事件

通常我們會(huì)希望window.onload事件能夠盡可能觸發(fā)乏屯,原因如下:

  • 我們可能在onload事件處理函數(shù)中編寫了用于初始化UI的代碼根时;
  • onload事件觸發(fā)時(shí),瀏覽器停止“忙指示器”辰晕,并向用戶反饋頁面已經(jīng)準(zhǔn)備就緒蛤迎。
  • 部分低版本瀏覽器(IE6、IE7含友、IE8替裆、Safari3、Safari4窘问、Chrome1辆童、Chrome2等)只有onload事件觸發(fā)之后才會(huì)觸發(fā)unload事件。有時(shí)惠赫,我們會(huì)把一些重要的操作和window的unload事件綁定在一起把鉴。例如,減少內(nèi)存泄露的代碼儿咱。如果onload花費(fèi)時(shí)間太長庭砍,用戶可能會(huì)離開頁面,那么在這些瀏覽器中unload可能就永遠(yuǎn)不會(huì)執(zhí)行了混埠。

通常情況下怠缸,iframe中的內(nèi)容對頁面來說不是很重要的(譬如第三方的廣告),我們不應(yīng)該因?yàn)檫@些內(nèi)容而延遲window.onload事件的觸發(fā)钳宪。

綜上揭北,即使iframe是空的扳炬,其開銷也會(huì)很高,而且他會(huì)阻塞onload事件罐呼。所以鞠柄,我們應(yīng)該盡可能避免iframe的使用。

七嫉柴、圖片優(yōu)化

在大多數(shù)網(wǎng)站中厌杜,圖片的大小往往能占到一半以上,所以優(yōu)化圖片能帶來更好的效果计螺;而且夯尽,對圖片的優(yōu)化,還可以實(shí)現(xiàn)再不刪減網(wǎng)站功能的條件下實(shí)現(xiàn)網(wǎng)站性能的提升登馒。

1匙握、圖像格式

GIF

透明:允許二進(jìn)制類型的透明度,要么完全透明陈轿,要么不透明圈纺。

動(dòng)畫:支持動(dòng)畫。動(dòng)畫由若干幀組成麦射。

無損:GIF是無損的

逐行掃描:生成GIF時(shí)蛾娶,會(huì)使用壓縮來減小文件大小。壓縮時(shí)潜秋,逐行掃描像素蛔琅,當(dāng)圖像在水平方向有很多重復(fù)顏色時(shí),可以獲得更好的壓縮效果峻呛。

支持隔行掃描

GIF有256色限制罗售,所以不適合顯示照片」呈觯可以用來顯示圖形寨躁,但是PNG8是用來顯示圖形的最佳方式。所以切距,一般在需要?jiǎng)赢嫊r(shí)才用到GIF朽缎。

JPEG

有損

不支持動(dòng)畫和透明

支持隔行掃描

PNG

透明:PNG支持完全的alpha透明

動(dòng)畫:目前無跨瀏覽器解決方案

無損

逐行掃描:和GIF類似,對水平方向有重復(fù)顏色的圖像壓縮比高谜悟。

支持隔行掃描

隔行掃描是什么:

網(wǎng)速很慢時(shí),部分圖像支持對那些連續(xù)采樣的圖像進(jìn)行隔行掃描北秽。隔行掃描可以讓用戶在完整下載圖像之前葡幸,可以先看到圖像的一個(gè)粗略的版本,從而消除頁面被延遲加載的感覺贺氓。

2蔚叨、PNG在IE6中的奇怪現(xiàn)象

所有在調(diào)色板PNG中的半透明像素在IE6下會(huì)顯示為完整的透明。

真彩色PNG中的alpha透明像素,會(huì)顯示為背景色

3蔑水、無損圖像優(yōu)化

PNG圖像優(yōu)化

PNG格式圖像信息保存在”塊“中邢锯,對于Web現(xiàn)實(shí)來說,大部分塊并非必要搀别,我們可以將其刪除丹擎。

推薦工具:Pngcrush

JPEG圖像優(yōu)化

剝離元數(shù)據(jù)(注釋、其他內(nèi)部信息等)

這些元數(shù)據(jù)可以安全刪除不會(huì)影響圖片質(zhì)量歇父。

推薦工具jpegtran

GIF轉(zhuǎn)換成PNG

前面提到GIF的功能吃了動(dòng)畫之外蒂培,完全可以用PNG8來代替,所以我們使用PNG代替GIF

推薦工具ImageMagick

優(yōu)化GIF動(dòng)畫

因?yàn)閯?dòng)畫里面有很多幀榜苫,并且部分內(nèi)容在很多幀上都是一樣的护戳,所以我們可以將圖像里面連續(xù)幀中的重復(fù)像素移除。

推薦工具:Gifsicle

4垂睬、CSS sprite優(yōu)化

如果網(wǎng)站頁面較少媳荒,可以將圖像放在一個(gè)超級CSS sprite中

看看Google就使用了一個(gè):

image

最佳實(shí)踐:

  • 按照顏色合并:顏色相近的突變組合在一起
  • 避免不必要的空白
  • 元素水平排列:比豎直排列稍微小點(diǎn)
  • 將顏色限制在25種之內(nèi)(盡量)
  • 先優(yōu)化單獨(dú)的圖像,再優(yōu)化Sprite
  • 通過控制大小和對齊減少反鋸齒的數(shù)量驹饺。
  • 避免使用對角線漸變钳枕,這種漸變無法被平鋪。
  • IE6中alpha透明圖像單獨(dú)使用sprite
  • 每2-3個(gè)像素改變漸變顏色逻淌,而不是每個(gè)
  • 避免對圖像縮放
  • 如果我們需要一張小的圖像么伯,就沒必要在下載一張大的圖像之后在HTML中將其縮小。
  • 譬如我們需要一個(gè)100*100的圖像卡儒,我們可以現(xiàn)在服務(wù)器端改變圖像的大小田柔,這樣可以節(jié)省下載的流量。

5骨望、避免對圖像縮放

如果我們在頁面中用不到大的圖像硬爆,就沒必要下載一個(gè)很大的然后用css限制他的大小。

譬如我們需要一個(gè)100*100的圖像擎鸠,我們可以現(xiàn)在服務(wù)器端改變圖像的大小缀磕,這樣可以節(jié)省下載的流量。

八劣光、劃分主域

在之前我們談到為了減少DNS的查找袜蚕,我們應(yīng)該減少域的數(shù)量。但有的時(shí)候增加域的數(shù)量反而會(huì)提高性能绢涡,關(guān)鍵是找到提升性能的關(guān)鍵路徑牲剃。如果一個(gè)域提供了太多的資源而成為關(guān)鍵路徑,那么將資源分配到多個(gè)域上(我們成為域劃分)雄可,可以使頁面加載更快凿傅。

當(dāng)單個(gè)域下載資源成為瓶頸時(shí)缠犀,可將資源分配到多個(gè)域上。通過并行的下載數(shù)來提高頁面速度聪舒。

譬如YouTube序列化域名:i1.ytimg.com辨液、i2.ytimg.com、i3.ytimg.com箱残、i4.ytimg.com

IP地址和主機(jī)名

瀏覽器執(zhí)行“每個(gè)服務(wù)端最大連接數(shù)”的限制是根據(jù)URL上的主機(jī)名滔迈,而不是解析出來的IP地址。因此疚宇,我們可以不必額外部署服務(wù)器亡鼠,而是為新域建立一條CNAME記錄。CNAME僅僅是域名的別名敷待,即使域名都指向同一個(gè)服務(wù)器间涵,瀏覽器依舊會(huì)為每個(gè)主機(jī)名開放最大連接數(shù)。

譬如榜揖,我們?yōu)?a target="_blank">www.abc.com建立一個(gè)別名abc.com勾哩,這兩個(gè)主機(jī)名有相同的IP地址,瀏覽器會(huì)將每個(gè)主機(jī)名當(dāng)做一個(gè)單獨(dú)的服務(wù)端举哟。

另外思劳,研究表明,域的數(shù)量從一個(gè)增加到兩個(gè)性能會(huì)得到提高妨猩,但超過兩個(gè)時(shí)就可能出現(xiàn)負(fù)面影響了潜叛。最終數(shù)量取決于資源的大小和數(shù)量,但分為兩個(gè)域是很好的經(jīng)驗(yàn)壶硅。

之前講了兩篇關(guān)于Web性能優(yōu)化的文章威兜,Web前端性能優(yōu)化——編寫高效的JavaScriptWeb前端性能優(yōu)化——如何提高頁面加載速度。那么關(guān)于Web性能優(yōu)化庐椒,就暫且說到這里了椒舵,如果有點(diǎn)用的話,不點(diǎn)一下推薦嗎约谈?

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末笔宿,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子棱诱,更是在濱河造成了極大的恐慌泼橘,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,544評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件迈勋,死亡現(xiàn)場離奇詭異侥加,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)粪躬,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評論 3 392
  • 文/潘曉璐 我一進(jìn)店門担败,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人镰官,你說我怎么就攤上這事提前。” “怎么了泳唠?”我有些...
    開封第一講書人閱讀 162,764評論 0 353
  • 文/不壞的土叔 我叫張陵狈网,是天一觀的道長。 經(jīng)常有香客問我笨腥,道長拓哺,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,193評論 1 292
  • 正文 為了忘掉前任脖母,我火速辦了婚禮士鸥,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘谆级。我一直安慰自己烤礁,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,216評論 6 388
  • 文/花漫 我一把揭開白布肥照。 她就那樣靜靜地躺著脚仔,像睡著了一般。 火紅的嫁衣襯著肌膚如雪舆绎。 梳的紋絲不亂的頭發(fā)上鲤脏,一...
    開封第一講書人閱讀 51,182評論 1 299
  • 那天,我揣著相機(jī)與錄音吕朵,去河邊找鬼猎醇。 笑死,一個(gè)胖子當(dāng)著我的面吹牛边锁,可吹牛的內(nèi)容都是我干的姑食。 我是一名探鬼主播,決...
    沈念sama閱讀 40,063評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼茅坛,長吁一口氣:“原來是場噩夢啊……” “哼音半!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起贡蓖,我...
    開封第一講書人閱讀 38,917評論 0 274
  • 序言:老撾萬榮一對情侶失蹤曹鸠,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后斥铺,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體彻桃,經(jīng)...
    沈念sama閱讀 45,329評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,543評論 2 332
  • 正文 我和宋清朗相戀三年晾蜘,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了邻眷。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片眠屎。...
    茶點(diǎn)故事閱讀 39,722評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖肆饶,靈堂內(nèi)的尸體忽然破棺而出改衩,到底是詐尸還是另有隱情,我是刑警寧澤驯镊,帶...
    沈念sama閱讀 35,425評論 5 343
  • 正文 年R本政府宣布葫督,位于F島的核電站,受9級特大地震影響板惑,放射性物質(zhì)發(fā)生泄漏橄镜。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,019評論 3 326
  • 文/蒙蒙 一冯乘、第九天 我趴在偏房一處隱蔽的房頂上張望洽胶。 院中可真熱鬧,春花似錦往湿、人聲如沸妖异。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,671評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽他膳。三九已至,卻和暖如春绒窑,著一層夾襖步出監(jiān)牢的瞬間棕孙,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,825評論 1 269
  • 我被黑心中介騙來泰國打工些膨, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蟀俊,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,729評論 2 368
  • 正文 我出身青樓订雾,卻偏偏與公主長得像肢预,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子洼哎,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,614評論 2 353

推薦閱讀更多精彩內(nèi)容