淺談前端效能優化
阿新 • • 發佈:2020-04-03
## 一、資源的合併和壓縮
`web`前端應用的開發與部署過程:
![image-20200330221214962](https://user-gold-cdn.xitu.io/2020/4/2/1713ac1c67248a4d?w=1064&h=601&f=png&s=332796)
輸入`url`到頁面顯示出來的過程:
![image-20200330221250064](https://gitee.com/ahuntsun/BlogImgs/raw/master/前端效能優化/2.png)
請求過程中一些潛在的效能優化點:
* `dns`是否可以通過快取減少`dns`查詢時間?
* 網路請求的過程如何走最近的網路環境?
* 相同的靜態資源是否可以快取?
* 能否減少`http`請求的大小和次數?
* 能否進行服務端渲染?
**總結:**深入理解`http`請求的過程是前端效能優化的核心。
**優化核心**
* 減少`http`請求數量;
* 減少請求資源的大小;
**`google`首頁案例學習**
* `html`壓縮;
* `css`壓縮;
* `js`的壓縮和混亂;
* 檔案合併;
* 開啟`gzip`;
### 1.`html`壓縮
`HTML`程式碼壓縮就是壓縮一些在文字檔案中有意義,但是在`HTML`中**不顯示**的字元,包括**空格**,**製表符**,**換行符**等,還有一些其他意義的字元,如**`HTML`註釋**也可以被壓縮;
**一個簡單的計算:**
`google`的流量,佔到整個網際網路的`40%`,預計`2016`年全球網路流量將達到`1.3ZB(1ZB = 10^9TB)`,那麼`google`在`2016`年的流量就是`1.3ZB * 40%`,如果`google`每`1MB`請求減少一個位元組,**每年可以節省流量近`500TB`**流量。
**如何進行`html`壓縮**
- 使用線上網站進行壓縮;
- `nodejs`提供的`html-minifier`工具;
- 後端模板引擎渲染壓縮;
### 2.`css`程式碼壓縮
分為兩部分:
* 無效程式碼的壓縮;
* `css`語義合併;
**如何進行`css`壓縮**
* 使用線上網站進行壓縮;
* 使用`html-minifier`對`html`中的`css`進行壓縮;
* 使用`clean-css`對`css`進行壓縮;
### 3.`js`壓縮與混亂(醜化)
包括:
* 無效字元的刪除(空格,回車等);
* 剔除註釋;
* 程式碼語義的縮減和優化;
* 程式碼保護(如果程式碼不經處理,客戶端可直接窺探程式碼漏洞);
**`JS`壓縮與混亂(醜化)**
* 使用線上網站進行壓縮:https://tool.oschina.net/jscompress/
![image-20200330230443570](https://user-gold-cdn.xitu.io/2020/4/2/1713ac1c6d13fd18?w=1318&h=514&f=png&s=51886)
* 使用`html-minifier`對`html`中的`js`進行壓縮;
* 使用`uglify.js2`對`js`進行壓縮;
### 4.檔案合併
檔案合併的好處:
![image-20200330224139300](https://user-gold-cdn.xitu.io/2020/4/2/1713ac1c6ddc2008?w=752&h=584&f=png&s=244674)
左邊的表示使用`http`長連結`keep-alive`但不合並請求的情況,需要分三次去獲取`a.js`、`b.js`、`c.js`;右邊是使用長連結並且合併請求的情況,只需要傳送一次獲取合併檔案`a-b-c.js`的請求,就能將三個檔案都請求回來。
不合並請求有下列缺點:
* 檔案與檔案之間有插入的上行請求,會增加`N-1`個網路延遲;
* 受丟包問題的影響更嚴重:因為每次請求都可能出現丟包的情況,減少請求能有效減少丟包情況;
* `keep-alive`本身也存在問題:經過代理伺服器時可能會被斷開;
**檔案合併存在的問題**
* 首屏渲染問題:當請求`js`檔案的時候,如果頁面渲染只依賴`a.js`檔案,由於檔案合併,需要等待合併後的`a-b-c.js`檔案請求回來才能繼續渲染,這樣就會導致頁面渲染速度變慢。這種情況大多出現在現代化的前端框架,如`Vue`等的使用過程中;
* 快取失效問題:合併後的檔案`a-b-c.js`中只要其中一個檔案(比如`a.js`)發生變化,那麼整個合併檔案都將失效,而不採用檔案合併就不會出現這種情況;
**使用建議**
* 公共庫合併:將不經常發生變化的公共元件庫檔案進行合併;
* 將不同頁面的`js`檔案單獨合併:比如在單頁面應用`SPA`中,當路由跳轉到具體的頁面時才請求該頁面需要的`js`檔案;
**如何進行檔案合併**
* 使用線上網站進行檔案合併;
* 使用`nodejs`實現檔案合併;
* 使用`webpack`等前端構件化工具也可以很好地實現;
## 二、圖片相關的優化
**有失真壓縮過程:**
![image-20200330232432166](https://user-gold-cdn.xitu.io/2020/4/2/1713ac1c6f5a52c3?w=1102&h=571&f=png&s=215654)
一張`JPG`圖片的解析分別要進行:
* 顏色空間的轉換:從`RGB`的顏色空間轉到其他的顏色空間 ;
* 進行重取樣:區分高頻和低頻的顏色變換;
* 進行`DCT`過程:對高頻的顏色取樣結果進行壓縮,這樣壓縮的收益會比較大;
* 再對資料進行量化;
* 最後進行編碼(`encoding`);
最終得到`JPEG-Compressed Image Data`,即真正顯示出來的`JPG`圖片。雖然這是一種有失真壓縮,但是很多情況下,這些損失的資料並不影響顯示;
**`png8/png24/png32`之間的區別**
* `png8`:`256`色 `+` 支援透明;
* `png24`:`2^24`色 `+` 不支援透明;
* `png32`:`2^32`色 `+` 支援透明;
**不同格式圖片常用的業務場景**
* `jpg`有失真壓縮,壓縮率高,支援透明;應用:大部分不需要透明圖片的業務場景;
* `png`支援透明,瀏覽器相容好;應用:大部分需要透明圖片的業務場景;
* `webp`(`2010`年由谷歌推出)壓縮程度更好,在`ios webview`中有相容性問題;應用:安卓全部;
* `svg`向量圖,程式碼內嵌,相對較小,用於圖片樣式相對簡單的場景;應用:比如`logo`和`iconfont`;
### 1.圖片壓縮
針對真實圖片情況,捨棄一些相對無關緊要的色彩資訊,對圖片進行壓縮;比如線上壓縮網站:https://tinypng.com/
![image-20200331102612277](https://user-gold-cdn.xitu.io/2020/4/2/1713ac1c777fd86d?w=1312&h=671&f=png&s=610754)
### 2.`css`雪碧圖
將網站上用到的一些圖片整合到一張單獨的圖片中,從而減少網站`HTTP`請求數量。原理為:設定整張雪碧圖可示區域,將想要顯示的圖示定位到該處(左上角);**缺點:**整合圖片比較大時,一次載入比較慢。
如天貓的雪碧圖:
![image-20200331100812635](https://user-gold-cdn.xitu.io/2020/4/2/1713ac1cabcab8f3?w=955&h=521&f=png&s=71525)
很多情況下,並不是所有的小圖示都放在一張雪碧圖中,而是會適當進行拆分。現在使用雪碧圖的場景比較少了。
自動生成雪碧圖樣式的網站:http://www.spritecow.com/
![image-20200331111632363](https://user-gold-cdn.xitu.io/2020/4/2/1713ac1cae1c4e75?w=1140&h=785&f=png&s=363553)
選中雪碧圖中對應的圖示,就會生成對應的樣式。
### 3.網頁內聯圖片(`Image inline`)
將圖片的內容內嵌到`html`當中,減少網站的`HTTP`請求數量,常用於處理小圖示和背景圖片。網頁內聯圖片寫法為:
```
```
瀏覽器上的表現形式為:
![image-20200331105927649](https://user-gold-cdn.xitu.io/2020/4/2/1713ac1cbdabaf38?w=1054&h=325&f=png&s=76209)
這裡提供一個將:`image` 轉 `DataUrI`的網址:http://tool.c7sky.com/datauri/
![image-20200331105719573](https://user-gold-cdn.xitu.io/2020/4/2/1713ac1cb0bdca9b?w=1030&h=586&f=png&s=77587)
**缺點:**
- 瀏覽器不會快取內聯圖片資源;
- 相容性較差,只支援`ie8`以上瀏覽器;
- 超過`1000kb`的圖片,`base64`編碼會使圖片大小增大,導致網頁整體下載速度減慢;
所以要根據場景使用,不過內聯圖片**減少`HTTP`請求**的優點還是很顯著的。比如,在開發中小於`4KB`或`8KB`的圖片都會通過構建工具自動`inline`到`HTML`中,這種情況下`Image inline`帶來的圖片大小增長其實是比增加`HTTP`請求次數更優的。
### 4.向量圖`SVG`與`iconfont`
**使用`iconfont`解決`icon`問題**
應儘量使用該方式,比如可以採用阿里巴巴向量相簿:
![image-20200331102136261](https://user-gold-cdn.xitu.io/2020/4/2/1713ac1cb4385403?w=1018&h=421&f=png&s=24626)
可以選擇格式進行下載:
![image-20200331102259285](https://user-gold-cdn.xitu.io/2020/4/2/1713ac1cbb0d2a3d?w=1018&h=732&f=png&s=42482)
可以看到它們的大小有著明顯的差異:
![image-20200331102353635](https://user-gold-cdn.xitu.io/2020/4/2/1713ac1cf1bd08bf?w=768&h=57&f=png&s=8963)
**使用`SVG`進行向量圖的控制**
`SVG `意為可縮放向量圖形(`Scalable Vector Graphics`)。`SVG` 使用 `XML` 格式定義影象。下為示例:
![image-20200331112542540](https://user-gold-cdn.xitu.io/2020/4/2/1713ac1cf24b39af?w=1230&h=406&f=png&s=35645)
線上轉換網站:http://www.bejson.com/convert/image_to_svg/
![image-20200331112802316](https://user-gold-cdn.xitu.io/2020/4/2/1713ac1cf23f75ed?w=1418&h=799&f=png&s=114803)
### 5.`webp`
`webp`的優勢體現在它具有更優的影象壓縮演算法,能帶來更小的圖片體積,而且擁有肉眼識別無差異的影象質量;同時具備了無損和有損的壓縮模式、`Alpha`透明以及動畫的特性。在`JPEG`和`PNG`上的轉化效果都非常優秀、穩定和統一。安卓上不存在相容性問題,推薦安卓下使用。
以下為淘寶網首頁請求的圖片:
![image-20200331101234068](https://user-gold-cdn.xitu.io/2020/4/2/1713ac1d020b8613?w=965&h=305&f=png&s=121907)
可以看到,圖片中大量地添加了`webp`格式的選擇。`.jpg_.webp`表示當瀏覽器支援`webp`時採用`webp`格式,否則採用`jpg`格式。
下面為`B`站首頁的圖片,可以看到基本都採用了`webp`格式:
![image-20200401110811561](https://user-gold-cdn.xitu.io/2020/4/2/1713ac1d0ed2bb59?w=991&h=604&f=png&s=180232)
同一張圖片`jpg`格式和`webp`格式壓縮率有著明顯的差異:
![image-20200331101633934](https://user-gold-cdn.xitu.io/2020/4/2/1713ac1d117540e8?w=747&h=56&f=png&s=8490)
可以通過線上網站將圖片轉換為`webp`:https://zhitu.isux.us/
![image-20200331103230221](https://user-gold-cdn.xitu.io/2020/4/2/1713ac1d274f721d?w=1267&h=782&f=png&s=766897)
像圖片這樣的靜態檔案可以存放在`CDN`伺服器上,讓`CDN`伺服器批量地將圖片轉換成`Webp`格式;
## 三、瀏覽器渲染引擎與阻塞
### 1.渲染的主要模組
版本一:
![image-20200331131925229](https://user-gold-cdn.xitu.io/2020/4/2/1713ac1d2e30d383?w=817&h=391&f=png&s=93555)
版本二:
![image-20200331113305758](https://user-gold-cdn.xitu.io/2020/4/2/1713ac1d3b852d62?w=1167&h=264&f=png&s=134420)
一個渲染引擎主要包括:`HTML`解析器,`CSS`解析器,`javascript`引擎,佈局`layout`模組,繪圖模組:
* `HTML`解析器:解釋`HTML`文件的解析器,主要作用是將`HTML`文字解釋成`DOM`樹;
* `CSS`解析器:它的作用是為`DOM`中的各個元素物件計算出樣式資訊,為佈局提供基礎設施;
* `Javascript`引擎:使用`Javascript`程式碼可以修改網頁的內容,也能修改`css`的資訊,`javascript`引擎能夠解釋`javascript`程式碼,並通過`DOM`介面和`CSS`樹介面來修改網頁內容和樣式資訊,從而改變渲染的結果;
* 佈局(`layout`):在`DOM`建立之後,`Webkit`需要將其中的元素物件同樣式資訊結合起來,計算他們的大小位置等佈局資訊,形成一個能表達這所有資訊的內部表示模型;
* 繪圖模組(`paint`):使用圖形庫將佈局計算後的各個網頁的節點繪製成影象結果;
### 2.渲染過程
瀏覽器渲染頁面的整個過程:瀏覽器會從上到下解析文件。
2. 瀏覽器解析時遇見 `HTML` 標記,就會呼叫`HTML`解析器解析為對應的 `token` (一個`token`就是一個標籤文字的序列化)並構建 `DOM` 樹(就是一塊記憶體,儲存著`tokens`,建立它們之間的關係)。在生成`DOM`的最開始階段(應該是`Bytes` → `characters`後),並行發起`css`、圖片、`js`的請求,無論他們是否在`HEAD`標籤中。
> 注意:發起`js`檔案的下載請求(`request`)並不需要`DOM`處理到那個`script`節點;
3. 遇見 `style/link` 標記 呼叫解析器 處理 `CSS` 標記並構建 `CSS`樣式樹;
4. 遇見 `script` 標記 呼叫 `javascript`解析器處理`script`標記,繫結事件、修改`DOM`樹/`CSS`樹等;
5. 將 `DOM`樹 與 `CSS`樹 合併成一棵渲染樹(`Render Tree`)。
6. 佈局(`Layout`):根據渲染樹中各節點的樣式和依賴關係,計算出每個節點在螢幕中的位置;
7. 繪圖(`Painting`):按照計算出來的結果:要顯示的節點、節點的`CSS`與位置資訊,通過顯示卡,把內容畫到螢幕上;
經過第一次`Painting`之後`DOM`、`CSSOM`、`Render Tree`都可能會被多次更新,比如`JS`修改了`DOM`或者`CSS`屬性時,`Layout`和`Painting`就會被重複執行。除了`DOM`、`CSSOM`更新的原因外,圖片下載完成後也需要呼叫`Layout` 和 `Painting`來更新網頁。
> **補充:**
>
> * `HTML`中可能會引入很多的`css、js`這樣的外部資源,這些外部資源在瀏覽器端是併發載入的。但是瀏覽器會對同一域名進行併發數量(度)的限制,即單個域名的併發度是有限的;
> * 所以,經常將大部分的資源託管到`CDN`伺服器上,並且設定`3~4`個`CDN`域名。防止只有一個`CDN`域名的情況下,達到了瀏覽器外部資源併發請求數目的上限,導致很多資源無法做到併發請求。所以,應設定多個`CDN`域名;
### 3.`css`阻塞
只有通過`link`引入的外部`css`才會產生阻塞:
* **`style`標籤中的樣式:**
* 由`html`解析器進行解析;
* 不阻塞瀏覽器渲染(可能會產生“閃屏現象”);
* 不阻塞`DOM`解析;
* **`link`引入的外部`css`樣式(推薦使用的方式):**
* 由`CSS`解析器進行解析;
* 阻塞瀏覽器渲染:由於`css`已經載入完畢,所以整個渲染過程是帶樣式的,所以這種阻塞可以避免“閃屏現象”;
* 阻塞其後面的`js`語句的執行:這個不難理解,`js`檔案中經常會出現`DOM`操作,操作過程中有可能涉及到`css`樣式的修改。實際上,這些修改往往是依賴於之前引入的`css`設定的樣式的,所以`css`會阻塞`js`的執行;
* 不阻塞DOM的解析;
* **優化核心理念:儘可能快的提高外部`css`載入速度:**
* 使用`CDN`節點進行外部資源加速;
* 對`css`進行壓縮(利用打包工具,比如`webpack`,`gulp`等);
* 減少`http`請求數,將多個`css`檔案合併;
* 優化樣式表的程式碼;
### 4.`js`阻塞
* **阻塞DOM解析:**
原因:瀏覽器不知道後續指令碼的內容,如果先去解析了下面的`DOM`,而隨後的`js`刪除了後面所有的`DOM`,那麼瀏覽器就做了無用功,瀏覽器無法預估腳本里面具體做了什麼操作,例如像`document.write`這種操作,索性全部停住,等指令碼執行完了,瀏覽器再繼續向下解析`DOM`;可以通過給`script`標籤新增`defer`和`async`屬性,非同步引入`js`檔案,以此來解決這一問題。
* **阻塞頁面渲染:**
原因:`js`中也可以給`DOM`設定樣式,瀏覽器同樣等該指令碼執行完畢,再繼續幹活,避免做無用功;
* **阻塞後續`js`的執行:**
原因:`js`是按順序執行的,這樣可以維護依賴關係,例如:必須先引入`jQuery`再引入`bootstrap`;
* **不阻塞資源的載入:**
這並不與上面矛盾,因為不可能由於載入一個`js`檔案就把其他資源的載入都阻塞了。針對這種常見的情況,瀏覽器會通過預載入的方式載入後續的資源;
### 5.總結
* `css`的解析和`js`的執行是互斥的(互相排斥),`css`解析的時候`js`停止執行,`js`執行的時候`css`停止解析;
* 無論`css`阻塞,還是`js`阻塞,都不會阻塞瀏覽器**載入**外部資源(圖片、視訊、樣式、指令碼等);
因為覽器始終處於一種:“先把請求發出去”的工作模式,只要是涉及到網路請求的內容,無論是:圖片、樣式、指令碼,都會先發送請求去獲取資源,至於資源到本地之後什麼時候用,由瀏覽器自己協調。顯然這種做法效率很高;
* `WebKit` 和`Firefox` 都進行了【**預解析**】這項優化。在執行`js`指令碼時,瀏覽器的其他執行緒會解析文件的其餘部分,找出並載入需要通過網路載入的其他資源。通過這種方式,資源可以在並行連線上載入,從而提高總體速度。請注意,預解析器不會修改 `DOM` 樹
## 四、懶載入和預載入
### 1.懶載入
圖片進入可視區域之後再請求圖片資源的方式稱為圖片懶載入。適用於圖片很多,頁面很長的業務場景,比如電商;
**懶載入的作用:**
* **減少無效資源的載入:**
比如一個網站有十頁圖片,使用者只查看了第一頁的圖片,這就沒必要將十頁圖片全都加載出來;
* **併發載入的資源過多會阻塞`js`的載入,影響網站正常的使用:**
由於瀏覽器對某一個`host name`是有併發度上限的,如果圖片資源所在的`CDN`和靜態資源所在的`CDN`是同一個的話,過多圖片的併發載入就會阻塞後續`js`檔案的併發載入。
**懶載入實現的原理:**
監聽`onscroll`事件,判斷可視區域位置:
圖片的載入是依賴於`src`路徑的,首先可以為所有懶載入的靜態資源新增自定義屬性欄位,用於儲存真實的`url`。比如是圖片的話,可以定義`data-src`屬性儲存真實的圖片地址,`src`指向`loading`的圖片或佔位符。然後當資源進入視口的時候,才將`src`屬性值替換成`data-src`中存放的真實`url`。
```html
```
**懶載入例項**
可以使用元素的`getBoundingRect().top`來判斷當前位置是否在視口內,也可以使用元素距離文件頂部的距離`offsetTop`和`scrollTop`是否小於視口高度來判斷:
![image-20200331153831469](https://user-gold-cdn.xitu.io/2020/4/2/1713ac1d3dd88bc2?w=770&h=657&f=png&s=24377)
**舉例**
比如手機淘寶首頁:
![image-20200331145210486](https://user-gold-cdn.xitu.io/2020/4/2/1713ac1d3f33f519?w=1627&h=803&f=png&s=543095)
當快要滾動到需要展示的圖片時才進行圖片的請求,可以看到圖片上有一個`lazyload`的屬性:
![image-20200331145756313](https://user-gold-cdn.xitu.io/2020/4/2/1713ac1d751162c7?w=481&h=83&f=png&s=11410)
### 2.預載入
預載入與懶載入正好是相反的過程:**懶載入**實際上是延遲載入,將我們所需的靜態資源載入時間延後;而**預載入**是將圖片等靜態資源在使用之前的提前請求,這樣資源在使用到時能從快取中直接載入,從而提升使用者體驗;
**預載入的作用:**
* **提前請求資源,提升載入速度**:使用時只需要讀取瀏覽器快取中提前請求到的資源即可;
* **維護頁面的依賴關係**:比如`WebGL`頁面,會依賴一些`3D`模型,這些都是頁面渲染所必須的資源。如果資源都沒有載入完畢就進行頁面的渲染,就會造成非常不好的體驗。
所以時常使用預載入的方式維護頁面渲染的依賴關係,比如將`WebGL`頁面依賴的`3D`模型載入完之後才進行頁面渲染。這樣渲染的過程就不會有任何阻礙,具有較好的使用者體驗;
**預載入的例項**
例如九宮格抽獎業務,每個獎品都有一個選中態和非選中態,實際上這是由兩張圖片組合而成的。由於每個獎品的選中過程都是一瞬間,這就對圖片的選中態和非選中態切換效率要求很高,如果選中態的圖片沒有預載入的話顯然是來不及的。
![image-20200331150725347](https://user-gold-cdn.xitu.io/2020/4/2/1713ac1d4f47acaa?w=631&h=577&f=png&s=219895)
所以,實際上對於九宮格中所有圖片選中態的樣式和對應的圖片都需要進行預載入,從而讓我們在抽獎的過程中,能夠瞬間從快取中讀取到選中態的圖片,從而不影響抽獎效果的展示。
除此之外還有網站登入或活動時需要用到的動畫,這是在動畫需要的每幀圖片都完全預載入完之後才會進行顯示的。
## 五、重繪與迴流
### 1.`CSS`圖層
瀏覽器在渲染一個頁面時,會將頁面分為很多個圖層,圖層有大有小,每個圖層上有一個或多個節點。在渲染 `DOM`的時候,瀏覽器所做的工作實際上是:
1、獲取`DOM`後分割為多個圖層;
2、對每個圖層的節點計算樣式結果(`Recalculate style`--樣式重計算);
3、為每個節點生成圖形和位置(`Layout`--迴流和重佈局);
4、將每個節點繪製填充到圖層點陣圖中(`Paint Setup`和`Paint`--重繪);
5、圖層作為紋理上傳至`GUI`;
6、複合多個圖層到頁面上生成最終螢幕影象(`Composive Layers`--圖層重組);
### 2.建立圖層的條件
* 擁有`3D`或透視變換的`css`屬性(`prespective transform` );
* 使用加速視訊解碼的`