傳輸層面的從來都是優(yōu)化的核心點,而這個層面的優(yōu)化要對瀏覽器有一個基本的認識,比如:
① 網(wǎng)頁自上而下的解析渲染,邊解析邊渲染,頁面內(nèi)CSS文件會阻塞渲染,異步CSS文件會導(dǎo)致回流
② 瀏覽器在document下載結(jié)束會檢測靜態(tài)資源,新開線程下載(有并發(fā)上限),在帶寬限制的條件下,無序并發(fā)會導(dǎo)致主資源速度下降,從而影響首屏渲染
③ 瀏覽器緩存可用時會使用緩存資源,這個時候可以避免請求體的傳輸,對性能有極大提高
衡量性能的重要指標為首屏載入速度(指頁面可以看見,不一定可交互),影響首屏的最大因素為請求,所以請求是頁面真正的殺手,一般來說我們會做這些優(yōu)化:
減少請求數(shù)
① 合并樣式、腳本文件
② 合并背景圖片
③ CSS3圖標、Icon Font
降低請求量
① 開啟GZip
② 優(yōu)化靜態(tài)資源,jQuery->Zepto、去除冗余代碼
③ 圖片無損壓縮
④ 圖片延遲加載
⑤ 減少Cookie攜帶
很多時候,我們也會采用類似“時間換空間、空間換時間”的做法,比如:
① 緩存為王,對更新較緩慢的資源&接口做緩存(瀏覽器緩存、localsorage、application cache這個坑多)
② 按需加載,先加載主要資源,其余資源延遲加載,對非首屏資源滾動加載
③ fake頁技術(shù),將頁面最初需要顯示Html&Css內(nèi)聯(lián),在頁面所需資源加載結(jié)束前至少可看,理想情況是index.html下載結(jié)束即展示(2G 5S內(nèi))
④ CDN
從工程的角度來看,上述優(yōu)化點半數(shù)以上是重復(fù)的,一般在發(fā)布時候就直接使用項目構(gòu)建工具做掉了,還有一些只是簡單的服務(wù)器配置,開發(fā)時不需要關(guān)注。
可以看到,我們所做的優(yōu)化都是在減少請求數(shù),降低請求量,減小傳輸時的耗時,或者通過一個策略,優(yōu)先加載首屏渲染所需資源,而后再加載交互所需資源(比如點擊時候再加載UI組件)
解決冗余便拋開了歷史的包袱,是前端優(yōu)化的第一步也是比較難的一步,但模塊拆分也將全站分成了很多小的模塊,載入的資源分散會增加請求數(shù);如果全部合并,會導(dǎo)致首屏加載不需要的資源,也會導(dǎo)致下一個頁面不能使用緩存,如何做出合理的入口資源加載規(guī)則,如何合理的善用緩存,是前端優(yōu)化的第二步。
資源緩存
① 瀏覽器緩存
② localstorage緩存
時間戳更新
只要服務(wù)器配置,瀏覽器本身便具有緩存機制,如果要使用瀏覽器機制作緩存,勢必關(guān)心一個何時更新資源問題,我們一般是這樣做的:
<script type="text/javascript" src="libs.js?t=20151025"></script>
這樣做要求必須先發(fā)布js文件,才能發(fā)布html文件,否則讀不到最新靜態(tài)文件的。一個比較尷尬的場景是libs.js是框架團隊甚至第三方公司維護的,和業(yè)務(wù)團隊的index.html是兩個團隊,互相的發(fā)布是沒有關(guān)聯(lián)的,所以這兩者的發(fā)布順序是不能保證的,于是轉(zhuǎn)向開始使用了MD5的方式。
MD5時代
為了解決以上問題我們開始使用md5碼的方式為靜態(tài)資源命名:
<script type="text/javascript" src="libs_md5_1234.js"></script>
每次框架更新便不做文件覆蓋,直接生成一個唯一的文件名做增量發(fā)布,這個時候如果框架先發(fā)布,待業(yè)務(wù)發(fā)布時便已經(jīng)存在了最新的代碼;當業(yè)務(wù)先發(fā)布框架沒有新的時,便繼續(xù)沿用老的文件,一切都很美好,雖然業(yè)務(wù)開發(fā)偶爾會抱怨每次都要向框架拿MD5映射,直到框架一次BUG發(fā)生。
seed.js時代
突然一天框架發(fā)現(xiàn)一個全局性BUG,并且馬上做出了修復(fù),業(yè)務(wù)團隊也馬上發(fā)布上線,但這種事情出現(xiàn)第二次、第三次框架這邊便壓力大了,這個時候框架層面希望業(yè)務(wù)只需要引用一個不帶緩存的seed.js,seed.js要怎么加載是他自己的事情:
<script type="text/javascript" src="seed.js"></script>
//seed.js需要按需加載的資源
<script src="libs_md5.js"></script>
<script src="main_md5.js"></script>
當然,由于js加載是順序是不可控的,我們需要為seed.js實現(xiàn)一個最簡單的順序加載模塊,映射什么的由構(gòu)建工具完成,每次做覆蓋發(fā)布即可,這樣做的缺點是額外增加一個seed.js的文件,并且要承擔(dān)模塊加載代碼的下載量。
localstorage緩存
也會有團隊將靜態(tài)資源緩存至localstorage中,以期做離線應(yīng)用,但是我一般用它存json數(shù)據(jù),沒有做過靜態(tài)資源的存儲,想要嘗試的朋友一定要做好資源更新的策略,然后localstorage的讀寫也有一定損耗,不支持的情況還需要做降級處理,這里便不多介紹。
CSS性能讓JavaScript變慢?
當一個元素的外觀的可見性visibility發(fā)生改變的時候,重繪(repaint)也隨之發(fā)生,但是不影響布局。類似的例子包括:outline, visibility, or background color。根據(jù)Opera瀏覽器,重繪的代價是高昂的,因為瀏覽器必須驗證DOM樹上其他節(jié)點元素的可見性。而回流更是性能的關(guān)鍵因為其變化涉及到部分頁面(或是整個頁面)的布局。一個元素的回流導(dǎo)致了其所有子元素以及DOM中緊隨其后的祖先元素的隨后的回流。
<body>
<div class="error">
<h4>我的組件</h4>
<p><strong>錯誤:</strong>錯誤的描述…</p>
<h5>錯誤糾正</h5>
<ol>
<li>第一步</li>
<li>第二步</li>
</ol>
</div>
</body>
在上面的HTML片段中,對該段落(<p>標簽)回流將會引發(fā)強烈的回流,因為它是一個子節(jié)點。這也導(dǎo)致了祖先的回流(div.error和body – 視瀏覽器而定)。此外,h5和ol也會有簡單的回流,因為其在DOM中在回流元素之后。就Opera而言,大部分的回流將導(dǎo)致頁面的重新渲染。
既然它們對性能影響如此可怕,那什么會導(dǎo)致回流呢?
不幸的是,很多的東西,其中一些還與CSS的書寫特別相關(guān)。
調(diào)整窗口大小(Resizing the window)
改變字體(Changing the font)
增加或者移除樣式表(Adding or removing a stylesheet)
內(nèi)容變化,比如用戶在input框中輸入文字(Content changes, such as a user typing text in
an input box)
激活 CSS 偽類,比如 :hover (IE 中為兄弟結(jié)點偽類的激活)(Activation of CSS pseudo classes such as :hover (in IE the activation of the pseudo class of a sibling))
操作 class 屬性(Manipulating the class attribute)
腳本操作 DOM(A script manipulating the DOM)
計算 offsetWidth 和 offsetHeight 屬性(Calculating offsetWidth and offsetHeight)
設(shè)置 style 屬性的值 (Setting a property of the style attribute)
回流與重繪
當render tree中的一部分(或全部)因為元素的規(guī)模尺寸,布局,隱藏等改變而需要重新構(gòu)建。這就稱為回流(reflow)。每個頁面至少需要一次回流,就是在頁面第一次加載的時候。在回流的時候,瀏覽器會使渲染樹中受到影響的部分失效,并重新構(gòu)造這部分渲染樹,完成回流后,瀏覽器會重新繪制受影響的部分到屏幕中,該過程成為重繪。
當render tree中的一些元素需要更新屬性,而這些屬性只是影響元素的外觀,風(fēng)格,而不會影響布局的,比如background-color。則就叫稱為重繪。
注意:回流必將引起重繪,而重繪不一定會引起回流。
var s = document.body.style;
s.padding = "2px"; // 回流+重繪
s.border = "1px solid red"; // 再一次 回流+重繪
s.color = "blue"; // 再一次重繪
s.backgroundColor = "#ccc"; // 再一次 重繪
s.fontSize = "14px"; // 再一次 回流+重繪
// 添加node,再一次 回流+重繪
document.body.appendChild(document.createTextNode('abc!'));
說到這里大家都知道回流比重繪的代價要更高,回流的花銷跟render tree有多少節(jié)點需要重新構(gòu)建有關(guān)系,假設(shè)你直接操作body,比如在body最前面插入1個元素,會導(dǎo)致整個render tree回流,這樣代價當然會比較高,但如果是指body后面插入1個元素,則不會影響前面元素的回流。
如何減少回流、重繪
減少回流、重繪其實就是需要減少對render tree的操作(合并多次多DOM和樣式的修改),并減少對一些style信息的請求,盡量利用好瀏覽器的優(yōu)化策略。具體方法有:
- 直接改變className,如果動態(tài)改變樣式,則使用cssText(考慮沒有優(yōu)化的瀏覽器)
// 不好的寫法
var left = 1;
var top = 1;
el.style.left = left + "px";
el.style.top = top + "px";
// 比較好的寫法
el.className += " className1";
// 比較好的寫法
el.style.cssText += ";
left: " + left + "px;
top: " + top + "px;"
2.不要經(jīng)常訪問會引起瀏覽器flush隊列的屬性,如果你確實要訪問,利用緩存
// 別這樣寫,大哥
for(循環(huán)) {
el.style.left = el.offsetLeft + 5 + "px";
el.style.top = el.offsetTop + 5 + "px";
}
// 這樣寫好點
var left = el.offsetLeft,
top = el.offsetTop,
s = el.style;
for (循環(huán)) {
left += 10;
top += 10;
s.left = left + "px";
s.top = top + "px";
}
JS 模塊化
按需加載
自己實現(xiàn)也不難,主要思路就是有個config配置需要動態(tài)加載js庫的path,然后在運行時,由你的加載器,動態(tài)的創(chuàng) <script src='js_Path'/>標簽,并把把它append到<head/>中。你的加載器能自適應(yīng)市面上主流的幾種規(guī)范即可,比如global var,AMD,Commonjs等。
關(guān)于瀏覽器緩存
瀏覽器的資源請求,如果使用了緩存基本上是兩種情況
status code: 200 ok
status code: 304 Not Modified
If-Modified-Since是標準的HTTP請求頭標簽,它對應(yīng)的就是服務(wù)器發(fā)送過來的該文件的最后服務(wù)器修改時間。
在發(fā)送http請求的時候,就會將這個最后修改的時間發(fā)送到服務(wù)器,然后服務(wù)器會將這個時間與服務(wù)器上當前文件最后修改時間進行比對,如果時間一致,方式如下:
(1).那么返回HTTP狀態(tài)碼304(不返回文件內(nèi)容),客戶端接到之后,就直接把本地緩存文 件顯示到瀏覽器中。
(2).如果時間不一致(說明服務(wù)器有了更新的文件),就返回HTTP狀態(tài)碼200和新的文件內(nèi)容,客戶端接到之后,會丟棄舊文件,把新文件 緩存起來,并顯示到瀏覽器中。
問題1不一定,讀取過的文件在http header設(shè)置了expire(http 1.0) / max-age(http 1.1),在正常瀏覽時,如未超時,并且瀏覽器也有緩存時,會直接從瀏覽器緩存取出,但如果你在當前頁面按刷新按鈕(F5)時,有的瀏覽器會再次向服務(wù)器發(fā)出請求,有些瀏覽器不會。
問題2CSS是一次渲染全部后再接著做其它工作,重復(fù)的圖片URL,在沒緩存的情況下,只會發(fā)出一次請求,如果相同的圖片URL已在頁面或內(nèi)存中,不會再次請求。
問題3瀏覽器緩存是存在瀏覽器本地的文件,304是HTTP狀態(tài)碼,服務(wù)器用來標識這個文件沒修改,不返回內(nèi)容,瀏覽器在接收到個狀態(tài)碼后,會使用瀏覽器已緩存的文件