從輸入 URL 到頁面加載完成,完整的鏈路
http層面優化
- DNS 解析:
DNS 實現域名到IP的映射。通過域名訪問站點,每次請求都要做DNS解析。目前每次DNS解析,通常在200ms以下。一般采用DNS Prefetch 一種DNS 預解析技術,當你瀏覽網頁時,瀏覽器會在加載網頁時對網頁中的域名進行解析緩存,這樣在你單擊當前網頁中的連接時就無需進行DNS的解析,減少用戶等待時間,提高用戶體驗。
<link rel="dns-prefetch" href="www.baidu.com" /> 只有部分瀏覽器支持
-
TCP 連接:
采用http2.0,可以復用tcp通道,采用二進制格式而非文本格式,使用報頭壓縮,HTTP/2降低了開銷,支持cache push -
瀏覽器並發
基於端口跟線程切換開銷,瀏覽器不可能無限的並發請求。chrome的並發為6,超過限制數目的請求就會被阻塞;
對於某些靜態資源,圖片等等,我們可以對其URL分散處理 ,不同的資源域名(部署在cdn上)。 -
http請求次數
cdn托管,主要針對http緩存;雪碧圖;腳本合並;圖片懶加載;本地緩存; -
webpack
充分利用webpack提供給我們的能力,利用DllPlugin與commonPlugins等插件對我們代碼進行
優化,文件的分割與合並,公共代碼的提取,長緩存等策略,webpack是個很好的東西,值得大家仔細研究 -
http壓縮
采用Gzip壓縮:HTTP 壓縮就是以縮小體積為目的,對 HTTP 內容進行重新編碼的過程,原理是找出一些重復出現的字符串、臨時替換它們,從而使整個文件變小,文件中代碼的重復率越高,那么壓縮的效率就越高,使用 Gzip 的收益也就越大
瀏覽器渲染
瀏覽器渲染機制
- DOM樹:
解析 HTML 以創建的是 DOM 樹(DOM tree ):渲染引擎開始解析 HTML 文檔,轉換樹中的標簽到 DOM 節點,它被稱為“內容樹”。 - CSSOM樹:
解析 CSS(包括外部 CSS 文件和樣式元素)創建的是 CSSOM 樹。CSSOM 的解析過程與 DOM 的解析過程是並行的。
-渲染樹:
CSSOM 與 DOM 結合,之后我們得到的就是渲染樹(Render tree )。 - 布局渲染樹:
從根節點遞歸調用,計算每一個元素的大小、位置等,給每個節點所應該出現在屏幕上的精確坐標,我們便得到了基於渲染樹的布局渲染樹(Layout of the render tree)。 - 繪制渲染樹:
遍歷渲染樹,每個節點將使用 UI 后端層來繪制。整個過程叫做繪制渲染樹(Painting the render tree)。
當我們瀏覽器獲得HTML文件后,會自上而下的加載,並在加載過程中進行解析和渲染。
加載說的就是獲取資源文件的過程,如果在加載過程中遇到外部CSS文件和圖片,瀏覽器會另外發送一個請求,去獲取CSS文件和相應的圖片,這個請求是異步的,並不會影響HTML文件的加載。
但是如果遇到Javascript文件,HTML文件會掛起渲染的進程,等待JavaScript文件加載完畢后,再繼續進行渲染。 為什么HTML需要等待JavaScript呢?因為JavaScript可能會修改DOM,導致后續HTML資源白白加載,所以HTML必須等待JavaScript文件加載完畢后,再繼續渲染,這也就是為什么JavaScript文件在寫在底部body標簽前的原因。
重繪與重排
當DOM的變化引發了元素幾何屬性的變化,比如改變元素的寬高
,元素的位置
,導致瀏覽器不得不重新計算元素的幾何屬性,並重新構建渲染樹,這個過程稱為“重排”。完成重排后,要將重新構建的渲染樹渲染到屏幕上,這個過程就是“重繪”。
簡單的說,重排負責元素的幾何屬性更新,重繪負責元素的樣式更新。而且,重排必然帶來重繪,但是重繪未必帶來重排。比如,改變某個元素的背景
,這個就不涉及元素的幾何屬性,所以只發生重繪。
上面已經提到了,重排發生的根本原理就是元素的幾何屬性發生了改變,那么我們就從能夠改變元素幾何屬性的角度入手
重排觸發幾種情景:
- 添加或刪除可見的DOM元素
- 元素位置改變
- 元素本身的尺寸發生改變
- 內容改變
- 頁面渲染器初始化
- 瀏覽器窗口大小發生改變
如何進行性能優化針對重排
重繪和重排的開銷是非常昂貴的,如果我們不停的在改變頁面的布局,就會造成瀏覽器耗費大量的開銷在進行頁面的計算,這樣的話,我們頁面在用戶使用起來,就會出現明顯的卡頓。現在的瀏覽器其實已經對重排進行了優化,比如如下代碼:
比較久遠的瀏覽器,這段代碼會觸發頁面2次重排,在分別設置寬高的時候,觸發2次.
當代的瀏覽器對此進行了優化,這種思路類似於現在流行的MVVM框架使用的虛擬DOM,對改變的DOM節點進行依賴收集,確認沒有改變的節點,就進行一次更新。但是瀏覽器針對重排的優化雖然思路和虛擬DOM接近,但是還是有本質的區別。大多數瀏覽器通過隊列化修改並批量執行來優化重排過程。也就是說上面那段代碼其實在現在的瀏覽器優化下,只構成一次重排。
但是還是有一些特殊的元素幾何屬性會造成這種優化失效。比如:
為什么造成優化失效呢?仔細看這些屬性,都是需要實時回饋給用戶的幾何屬性或者是布局屬性,當然不能再依靠瀏覽器的優化,因此瀏覽器不得不立即執行渲染隊列中的“待處理變化”,並隨之觸發重排返回正確的值。
最小化重繪和重排
既然重排&重繪是會影響頁面的性能,尤其是糟糕的JS代碼更會將重排帶來的性能問題放大。既然如此,我們首先想到的就是減少重排重繪。
批量修改DOM
批量修改DOM元素的核心思想是:
- 讓該元素脫離文檔流
- 對其進行多重改變
- 將元素帶回文檔中
優化技巧:
1.DOM 的多個讀操作(或多個寫操作),應該放在一起。不要兩個讀操作之間,加入一個寫操作。
2.不要一條條地改變樣式,而要通過改變class,或者csstext屬性,一次性地改變樣式。
3.盡量使用離線DOM,而不是真實的網面DOM,來改變元素樣式。比如,DocumentFragment,或 cloneNode()
4.也可以先 display: none ,然后隨便操作,最后再恢復顯示
5.position屬性為absolute或fixed的元素,重排的開銷會比較小,因為不用考慮它對其他元素的影響。
6.還可以考慮,“只在必要的時候,才將元素的display屬性為可見”
7.使用虛擬DOM(vue, react)
8.window.requestAnimationFrame() 方法,就個是“節流”的思想,將代碼放到下一次重新渲染時執行。 頁面滾動事件(scroll)的監聽函數,還有網頁動畫,就很適合用 window.requestAnimationFrame()
還有另一個,window.requestIdleCallback(),把函數放到瀏覽器的空閑時段內調用
9.如果你已經知道圖片的寬高了,你最好是寫在內聯樣式上。
這樣圖片在下載完之前,瀏覽器會根據你的寬高占一個位置,如果沒有寬高,圖片下載下來后,整個頁面會發生重排。
多余的的重排
vue方面的一些例子
1、v-if 和 v-show 區分使用場景
v-if 是 真正 的條件渲染,因為它會確保在切換過程中條件塊內的事件監聽器和子組件適當地被銷毀和重建;也是惰性的:如果在初始渲染時條件為假,則什么也不做——直到條件第一次變為真時,才會開始渲染條件塊。
v-show 就簡單得多, 不管初始條件是什么,元素總是會被渲染,並且只是簡單地基於 CSS 的 display 屬性進行切換。
所以,v-if 適用於在運行時很少改變條件,不需要頻繁切換條件的場景;v-show 則適用於需要非常頻繁切換條件的場景。
2、computed 和 watch 區分使用場景
computed:**是計算屬性,依賴其它屬性值,並且 computed 的值有緩存,只有它依賴的屬性值發生改變,下一次獲取 computed 的值時才會重新計算 computed 的值;
watch:**更多的是「觀察」的作用,類似於某些數據的監聽回調 ,每當監聽的數據變化時都會執行回調進行后續操作;
運用場景:
當我們需要進行數值計算,並且依賴於其它數據時,應該使用 computed,因為可以利用 computed 的緩存特性,避免每次獲取值時,都要重新計算;當我們需要在數據變化時執行異步或開銷較大的操作時,應該使用 watch,使用 watch 選項允許我們執行異步操作 ( 訪問一個 API ),限制我們執行該操作的頻率,並在我們得到最終結果前,設置中間狀態。這些都是計算屬性無法做到的。
3、利用凍結數據
Vue 會通過 Object.defineProperty 對數據進行劫持,來實現視圖響應數據的變化,然而有些時候我們的組件就是純粹的數據展示,不會有任何改變,我們就不需要 Vue 來劫持我們的數據,在大量數據展示的情況下,這能夠很明顯的減少組件初始化的時間,那如何禁止 Vue 劫持我們的數據呢?可以通過 Object.freeze 方法來凍結一個對象,一旦被凍結的對象就再也不能被修改了。
4、事件的銷毀
Vue 組件銷毀時,會自動清理它與其它實例的連接,解綁它的全部指令及事件監聽器,但是僅限於組件本身的事件。 如果在 js 內使用 addEventListene 等方式是不會自動銷毀的,我們需要在組件銷毀時手動移除這些事件的監聽,以免造成內存泄露,如:
created() { addEventListener('click', this.click, false) }, beforeDestroy() { removeEventListener('click', this.click, false) }
總之: 減少對dom的操作;減少重排與重繪;
CSS 方面
性能優化
慎重選擇高消耗的樣式
高消耗屬性在繪制前需要瀏覽器進行大量計算:
box-shadowsborder-radiustransparencytransformsCSS filters(性能殺手)
1.scope中元素選擇器盡量少用
用途:防止全局同名CSS污染
原理:在標簽加上v-data-something屬性,再在選擇器時加上對應[v-data-something],即CSS帶屬性選擇器,以此完成類似作用域的選擇方式
缺點:
(1)由於只是通過屬性限制,類還是原來的類,所以在其他地方對類設置樣式還是可以造成污染。
(2)添加了屬性選擇器,對於CSS選擇器的權重加重了。
(3)外層組件包裹子組件,會給子組件的根節點添加data屬性。在外層組件中無法修改子組件中除了根節點以外的節點的樣式。比如子組件中有box類,在父節點中設置樣式,會被編譯為
.box[data-v-x]
2. 減少查詢層級:如.header .logo要好過.header .top .logo;
3.減少查詢范圍
:如.header>li要好過.header li;
4.避免tag標簽與class或者ID並存:如a.top、button#submit;
5.避免在CSS中使用運算式:expression、calc、等等,可能會造成多次repaint和reflow。
6.不濫用 Float
Float在渲染時計算量比較大,盡量減少使用。 flex 基本上 可以比float布局 節省一倍的時間
7.如果使用基於 javaScript 的動畫,盡量使用 requestAnimationFrame.
避免使用 setTimeout, setInterval.避免通過類似 jQuery animate()-style 改變每幀的樣式,使用 CSS 聲明動畫會得到更好的瀏覽器優化。
使用 translate 取代 absolute 定位就會得到更好的 fps,動畫會更順滑。
8.善於css繼承
總之:CSS 優化主要是四個方面:
這個方面相關的 best practice 太多了,網上隨便找一找就是一堆資料,比如不要用 import 啊,壓縮啊等等,主要是從減少文件體積、減少阻塞加載、提高並發方面入手的,任何 hint 都逃不出這幾個大方向。
3.渲染性能
命名合理嗎?結構層次設計是否足夠健壯?對樣式進行抽象復用了嗎?優雅的 CSS 不僅僅會影響后期的維護成本,也會對加載性能等方面產生影響。這方面可以多找一些 OOCSS(不是說就要用 OOCSS,而是說多了解一下)等等不同 CSS Strategy 的信息,取長補短。
3、推薦書寫順序
(1)位置屬性(position, top, right, z-index, display, float等)
(2)大小(width, height, padding, margin)
(3)文字系列(font, line-height, letter-spacing, color- text-align等)
(4)背景(background, border等)
(5)其他(animation, transition等)