前端面試 性能優(yōu)化篇

2023-02-17 10:51 更新

一、CDN


1. CDN的概念

CDN(Content Delivery Network,內(nèi)容分發(fā)網(wǎng)絡(luò))是指一種通過(guò)互聯(lián)網(wǎng)互相連接的電腦網(wǎng)絡(luò)系統(tǒng),利用最靠近每位用戶的服務(wù)器,更快、更可靠地將音樂(lè)、圖片、視頻、應(yīng)用程序及其他文件發(fā)送給用戶,來(lái)提供高性能、可擴(kuò)展性及低成本的網(wǎng)絡(luò)內(nèi)容傳遞給用戶。

典型的CDN系統(tǒng)由下面三個(gè)部分組成:

  • 分發(fā)服務(wù)系統(tǒng):最基本的工作單元就是Cache設(shè)備,cache(邊緣cache)負(fù)責(zé)直接響應(yīng)最終用戶的訪問(wèn)請(qǐng)求,把緩存在本地的內(nèi)容快速地提供給用戶。同時(shí)cache還負(fù)責(zé)與源站點(diǎn)進(jìn)行內(nèi)容同步,把更新的內(nèi)容以及本地沒(méi)有的內(nèi)容從源站點(diǎn)獲取并保存在本地。Cache設(shè)備的數(shù)量、規(guī)模、總服務(wù)能力是衡量一個(gè)CDN系統(tǒng)服務(wù)能力的最基本的指標(biāo)。
  • 負(fù)載均衡系統(tǒng):主要功能是負(fù)責(zé)對(duì)所有發(fā)起服務(wù)請(qǐng)求的用戶進(jìn)行訪問(wèn)調(diào)度,確定提供給用戶的最終實(shí)際訪問(wèn)地址。兩級(jí)調(diào)度體系分為全局負(fù)載均衡(GSLB)和本地負(fù)載均衡(SLB)。全局負(fù)載均衡主要根據(jù)用戶就近性原則,通過(guò)對(duì)每個(gè)服務(wù)節(jié)點(diǎn)進(jìn)行“最優(yōu)”判斷,確定向用戶提供服務(wù)的cache的物理位置。本地負(fù)載均衡主要負(fù)責(zé)節(jié)點(diǎn)內(nèi)部的設(shè)備負(fù)載均衡
  • 運(yùn)營(yíng)管理系統(tǒng):運(yùn)營(yíng)管理系統(tǒng)分為運(yùn)營(yíng)管理和網(wǎng)絡(luò)管理子系統(tǒng),負(fù)責(zé)處理業(yè)務(wù)層面的與外界系統(tǒng)交互所必須的收集、整理、交付工作,包含客戶管理、產(chǎn)品管理、計(jì)費(fèi)管理、統(tǒng)計(jì)分析等功能。

2. CDN的作用

CDN一般會(huì)用來(lái)托管Web資源(包括文本、圖片和腳本等),可供下載的資源(媒體文件、軟件、文檔等),應(yīng)用程序(門戶網(wǎng)站等)。使用CDN來(lái)加速這些資源的訪問(wèn)。

(1)在性能方面,引入CDN的作用在于:

  • 用戶收到的內(nèi)容來(lái)自最近的數(shù)據(jù)中心,延遲更低,內(nèi)容加載更快
  • 部分資源請(qǐng)求分配給了CDN,減少了服務(wù)器的負(fù)載

(2)在安全方面,CDN有助于防御DDoS、MITM等網(wǎng)絡(luò)攻擊:

  • 針對(duì)DDoS:通過(guò)監(jiān)控分析異常流量,限制其請(qǐng)求頻率
  • 針對(duì)MITM:從源服務(wù)器到 CDN 節(jié)點(diǎn)到 ISP(Internet Service Provider),全鏈路 HTTPS 通信

除此之外,CDN作為一種基礎(chǔ)的云服務(wù),同樣具有資源托管、按需擴(kuò)展(能夠應(yīng)對(duì)流量高峰)等方面的優(yōu)勢(shì)。

3. CDN的原理

CDN和DNS有著密不可分的聯(lián)系,先來(lái)看一下DNS的解析域名過(guò)程,在瀏覽器輸入 www.test.com 的解析過(guò)程如下:

(1) 檢查瀏覽器緩存

(2)檢查操作系統(tǒng)緩存,常見的如hosts文件

(3)檢查路由器緩存

(4)如果前幾步都沒(méi)沒(méi)找到,會(huì)向ISP(網(wǎng)絡(luò)服務(wù)提供商)的LDNS服務(wù)器查詢

(5)如果LDNS服務(wù)器沒(méi)找到,會(huì)向根域名服務(wù)器(Root Server)請(qǐng)求解析,分為以下幾步:

  • 根服務(wù)器返回頂級(jí)域名(TLD)服務(wù)器如 ?.com?,?.cn?,?.org?等的地址,該例子中會(huì)返回 ?.com?的地址
  • 接著向頂級(jí)域名服務(wù)器發(fā)送請(qǐng)求,然后會(huì)返回次級(jí)域名(SLD)服務(wù)器的地址,本例子會(huì)返回 ?.test?的地址
  • 接著向次級(jí)域名服務(wù)器發(fā)送請(qǐng)求,然后會(huì)返回通過(guò)域名查詢到的目標(biāo)IP,本例子會(huì)返回 ?www.test.com?的地址
  • Local DNS Server會(huì)緩存結(jié)果,并返回給用戶,緩存在系統(tǒng)中

CDN的工作原理:

(1)用戶未使用CDN緩存資源的過(guò)程:

  1. 瀏覽器通過(guò)DNS對(duì)域名進(jìn)行解析(就是上面的DNS解析過(guò)程),依次得到此域名對(duì)應(yīng)的IP地址
  2. 瀏覽器根據(jù)得到的IP地址,向域名的服務(wù)主機(jī)發(fā)送數(shù)據(jù)請(qǐng)求
  3. 服務(wù)器向?yàn)g覽器返回響應(yīng)數(shù)據(jù)

(2)用戶使用CDN緩存資源的過(guò)程:

  1. 對(duì)于點(diǎn)擊的數(shù)據(jù)的URL,經(jīng)過(guò)本地DNS系統(tǒng)的解析,發(fā)現(xiàn)該URL對(duì)應(yīng)的是一個(gè)CDN專用的DNS服務(wù)器,DNS系統(tǒng)就會(huì)將域名解析權(quán)交給CNAME指向的CDN專用的DNS服務(wù)器。
  2. CND專用DNS服務(wù)器將CND的全局負(fù)載均衡設(shè)備IP地址返回給用戶
  3. 用戶向CDN的全局負(fù)載均衡設(shè)備發(fā)起數(shù)據(jù)請(qǐng)求
  4. CDN的全局負(fù)載均衡設(shè)備根據(jù)用戶的IP地址,以及用戶請(qǐng)求的內(nèi)容URL,選擇一臺(tái)用戶所屬區(qū)域的區(qū)域負(fù)載均衡設(shè)備,告訴用戶向這臺(tái)設(shè)備發(fā)起請(qǐng)求
  5. 區(qū)域負(fù)載均衡設(shè)備選擇一臺(tái)合適的緩存服務(wù)器來(lái)提供服務(wù),將該緩存服務(wù)器的IP地址返回給全局負(fù)載均衡設(shè)備
  6. 全局負(fù)載均衡設(shè)備把服務(wù)器的IP地址返回給用戶
  7. 用戶向該緩存服務(wù)器發(fā)起請(qǐng)求,緩存服務(wù)器響應(yīng)用戶的請(qǐng)求,將用戶所需內(nèi)容發(fā)送至用戶終端。

如果緩存服務(wù)器沒(méi)有用戶想要的內(nèi)容,那么緩存服務(wù)器就會(huì)向它的上一級(jí)緩存服務(wù)器請(qǐng)求內(nèi)容,以此類推,直到獲取到需要的資源。最后如果還是沒(méi)有,就會(huì)回到自己的服務(wù)器去獲取資源。

image

CNAME(意為:別名):在域名解析中,實(shí)際上解析出來(lái)的指定域名對(duì)應(yīng)的IP地址,或者該域名的一個(gè)CNAME,然后再根據(jù)這個(gè)CNAME來(lái)查找對(duì)應(yīng)的IP地址。

4. CDN的使用場(chǎng)景

  • 使用第三方的CDN服務(wù):如果想要開源一些項(xiàng)目,可以使用第三方的CDN服務(wù)
  • 使用CDN進(jìn)行靜態(tài)資源的緩存:將自己網(wǎng)站的靜態(tài)資源放在CDN上,比如js、css、圖片等??梢詫⒄麄€(gè)項(xiàng)目放在CDN上,完成一鍵部署。
  • 直播傳送:直播本質(zhì)上是使用流媒體進(jìn)行傳送,CDN也是支持流媒體傳送的,所以直播完全可以使用CDN來(lái)提高訪問(wèn)速度。CDN在處理流媒體的時(shí)候與處理普通靜態(tài)文件有所不同,普通文件如果在邊緣節(jié)點(diǎn)沒(méi)有找到的話,就會(huì)去上一層接著尋找,但是流媒體本身數(shù)據(jù)量就非常大,如果使用回源的方式,必然會(huì)帶來(lái)性能問(wèn)題,所以流媒體一般采用的都是主動(dòng)推送的方式來(lái)進(jìn)行。

二、懶加載


1. 懶加載的概念

懶加載也叫做延遲加載、按需加載,指的是在長(zhǎng)網(wǎng)頁(yè)中延遲加載圖片數(shù)據(jù),是一種較好的網(wǎng)頁(yè)性能優(yōu)化的方式。在比較長(zhǎng)的網(wǎng)頁(yè)或應(yīng)用中,如果圖片很多,所有的圖片都被加載出來(lái),而用戶只能看到可視窗口的那一部分圖片數(shù)據(jù),這樣就浪費(fèi)了性能。

如果使用圖片的懶加載就可以解決以上問(wèn)題。在滾動(dòng)屏幕之前,可視化區(qū)域之外的圖片不會(huì)進(jìn)行加載,在滾動(dòng)屏幕時(shí)才加載。這樣使得網(wǎng)頁(yè)的加載速度更快,減少了服務(wù)器的負(fù)載。懶加載適用于圖片較多,頁(yè)面列表較長(zhǎng)(長(zhǎng)列表)的場(chǎng)景中。

2. 懶加載的特點(diǎn)

  • 減少無(wú)用資源的加載:使用懶加載明顯減少了服務(wù)器的壓力和流量,同時(shí)也減小了瀏覽器的負(fù)擔(dān)。
  • 提升用戶體驗(yàn): 如果同時(shí)加載較多圖片,可能需要等待的時(shí)間較長(zhǎng),這樣影響了用戶體驗(yàn),而使用懶加載就能大大的提高用戶體驗(yàn)。
  • 防止加載過(guò)多圖片而影響其他資源文件的加載 :會(huì)影響網(wǎng)站應(yīng)用的正常使用。

3. 懶加載的實(shí)現(xiàn)原理

圖片的加載是由 src引起的,當(dāng)對(duì) src賦值時(shí),瀏覽器就會(huì)請(qǐng)求圖片資源。根據(jù)這個(gè)原理,我們使用HTML5 的 data-xxx屬性來(lái)儲(chǔ)存圖片的路徑,在需要加載圖片的時(shí)候,將 data-xxx中圖片的路徑賦值給 src,這樣就實(shí)現(xiàn)了圖片的按需加載,即懶加載。

注意:data-xxx 中的 xxx可以自定義,這里我們使用 data-src來(lái)定義。

懶加載的實(shí)現(xiàn)重點(diǎn)在于確定用戶需要加載哪張圖片,在瀏覽器中,可視區(qū)域內(nèi)的資源就是用戶需要的資源。所以當(dāng)圖片出現(xiàn)在可視區(qū)域時(shí),獲取圖片的真實(shí)地址并賦值給圖片即可。

使用原生JavaScript實(shí)現(xiàn)懶加載:

知識(shí)點(diǎn):

(1)window.innerHeight 是瀏覽器可視區(qū)的高度 document.documentElement.clientHeight

(2)document.body.scrollTop || document.documentElement.scrollTop 是瀏覽器滾動(dòng)的過(guò)的距離

(3)imgs.offsetTop 是元素頂部距離文檔頂部的高度(包括滾動(dòng)條的距離)

(4)圖片加載條件:img.offsetTop < window.innerHeight + document.body.scrollTop;

圖示:

image

代碼實(shí)現(xiàn):

<div class="container">
     <img src="loading.gif"  data-src="pic.png">
     <img src="loading.gif"  data-src="pic.png">
     <img src="loading.gif"  data-src="pic.png">
     <img src="loading.gif"  data-src="pic.png">
     <img src="loading.gif"  data-src="pic.png">
     <img src="loading.gif"  data-src="pic.png">
</div>
<script>
var imgs = document.querySelectorAll('img');
function lozyLoad(){
        var scrollTop = document.body.scrollTop || document.documentElement.scrollTop;
        var winHeight= window.innerHeight;
        for(var i=0;i < imgs.length;i++){
            if(imgs[i].offsetTop < scrollTop + winHeight ){
                imgs[i].src = imgs[i].getAttribute('data-src');
            }
        }
    }
  window.onscroll = lozyLoad();
</script>

4. 懶加載與預(yù)加載的區(qū)別

這兩種方式都是提高網(wǎng)頁(yè)性能的方式,兩者主要區(qū)別是一個(gè)是提前加載,一個(gè)是遲緩甚至不加載。懶加載對(duì)服務(wù)器前端有一定的緩解壓力作用,預(yù)加載則會(huì)增加服務(wù)器前端壓力。

  • 懶加載也叫延遲加載,指的是在長(zhǎng)網(wǎng)頁(yè)中延遲加載圖片的時(shí)機(jī),當(dāng)用戶需要訪問(wèn)時(shí),再去加載,這樣可以提高網(wǎng)站的首屏加載速度,提升用戶的體驗(yàn),并且可以減少服務(wù)器的壓力。它適用于圖片很多,頁(yè)面很長(zhǎng)的電商網(wǎng)站的場(chǎng)景。懶加載的實(shí)現(xiàn)原理是,將頁(yè)面上的圖片的 src 屬性設(shè)置為空字符串,將圖片的真實(shí)路徑保存在一個(gè)自定義屬性中,當(dāng)頁(yè)面滾動(dòng)的時(shí)候,進(jìn)行判斷,如果圖片進(jìn)入頁(yè)面可視區(qū)域內(nèi),則從自定義屬性中取出真實(shí)路徑賦值給圖片的 src 屬性,以此來(lái)實(shí)現(xiàn)圖片的延遲加載。
  • 預(yù)加載指的是將所需的資源提前請(qǐng)求加載到本地,這樣后面在需要用到時(shí)就直接從緩存取資源。通過(guò)預(yù)加載能夠減少用戶的等待時(shí)間,提高用戶的體驗(yàn)。我了解的預(yù)加載的最常用的方式是使用 js 中的 image 對(duì)象,通過(guò)為 image 對(duì)象來(lái)設(shè)置 scr 屬性,來(lái)實(shí)現(xiàn)圖片的預(yù)加載。

三、回流與重繪


1. 回流與重繪的概念及觸發(fā)條件

(1)回流

當(dāng)渲染樹中部分或者全部元素的尺寸、結(jié)構(gòu)或者屬性發(fā)生變化時(shí),瀏覽器會(huì)重新渲染部分或者全部文檔的過(guò)程就稱為回流。

下面這些操作會(huì)導(dǎo)致回流:

  • 頁(yè)面的首次渲染
  • 瀏覽器的窗口大小發(fā)生變化
  • 元素的內(nèi)容發(fā)生變化
  • 元素的尺寸或者位置發(fā)生變化
  • 元素的字體大小發(fā)生變化
  • 激活CSS偽類
  • 查詢某些屬性或者調(diào)用某些方法
  • 添加或者刪除可見的DOM元素

在觸發(fā)回流(重排)的時(shí)候,由于瀏覽器渲染頁(yè)面是基于流式布局的,所以當(dāng)觸發(fā)回流時(shí),會(huì)導(dǎo)致周圍的DOM元素重新排列,它的影響范圍有兩種:

  • 全局范圍:從根節(jié)點(diǎn)開始,對(duì)整個(gè)渲染樹進(jìn)行重新布局
  • 局部范圍:對(duì)渲染樹的某部分或者一個(gè)渲染對(duì)象進(jìn)行重新布局

(2)重繪

當(dāng)頁(yè)面中某些元素的樣式發(fā)生變化,但是不會(huì)影響其在文檔流中的位置時(shí),瀏覽器就會(huì)對(duì)元素進(jìn)行重新繪制,這個(gè)過(guò)程就是重繪。

下面這些操作會(huì)導(dǎo)致回流:

  • color、background 相關(guān)屬性:background-color、background-image 等
  • outline 相關(guān)屬性:outline-color、outline-width 、text-decoration
  • border-radius、visibility、box-shadow

注意: 當(dāng)觸發(fā)回流時(shí),一定會(huì)觸發(fā)重繪,但是重繪不一定會(huì)引發(fā)回流。

2. 如何避免回流與重繪?

減少回流與重繪的措施:

  • 操作DOM時(shí),盡量在低層級(jí)的DOM節(jié)點(diǎn)進(jìn)行操作
  • 不要使用 ?table?布局, 一個(gè)小的改動(dòng)可能會(huì)使整個(gè) ?table?進(jìn)行重新布局
  • 使用CSS的表達(dá)式
  • 不要頻繁操作元素的樣式,對(duì)于靜態(tài)頁(yè)面,可以修改類名,而不是樣式。
  • 使用absolute或者fixed,使元素脫離文檔流,這樣他們發(fā)生變化就不會(huì)影響其他元素
  • 避免頻繁操作DOM,可以創(chuàng)建一個(gè)文檔片段 ?documentFragment?,在它上面應(yīng)用所有DOM操作,最后再把它添加到文檔中
  • 將元素先設(shè)置 ?display: none?,操作結(jié)束后再把它顯示出來(lái)。因?yàn)樵赿isplay屬性為none的元素上進(jìn)行的DOM操作不會(huì)引發(fā)回流和重繪。
  • 將DOM的多個(gè)讀操作(或者寫操作)放在一起,而不是讀寫操作穿插著寫。這得益于瀏覽器的渲染隊(duì)列機(jī)制。

瀏覽器針對(duì)頁(yè)面的回流與重繪,進(jìn)行了自身的優(yōu)化——渲染隊(duì)列

瀏覽器會(huì)將所有的回流、重繪的操作放在一個(gè)隊(duì)列中,當(dāng)隊(duì)列中的操作到了一定的數(shù)量或者到了一定的時(shí)間間隔,瀏覽器就會(huì)對(duì)隊(duì)列進(jìn)行批處理。這樣就會(huì)讓多次的回流、重繪變成一次回流重繪。

上面,將多個(gè)讀操作(或者寫操作)放在一起,就會(huì)等所有的讀操作進(jìn)入隊(duì)列之后執(zhí)行,這樣,原本應(yīng)該是觸發(fā)多次回流,變成了只觸發(fā)一次回流。

3. 如何優(yōu)化動(dòng)畫?

對(duì)于如何優(yōu)化動(dòng)畫,我們知道,一般情況下,動(dòng)畫需要頻繁的操作DOM,就就會(huì)導(dǎo)致頁(yè)面的性能問(wèn)題,我們可以將動(dòng)畫的 position屬性設(shè)置為 absolute或者 fixed,將動(dòng)畫脫離文檔流,這樣他的回流就不會(huì)影響到頁(yè)面了。

4. documentFragment 是什么?用它跟直接操作 DOM 的區(qū)別是什么?

MDN中對(duì) documentFragment的解釋:

DocumentFragment,文檔片段接口,一個(gè)沒(méi)有父對(duì)象的最小文檔對(duì)象。它被作為一個(gè)輕量版的 Document使用,就像標(biāo)準(zhǔn)的document一樣,存儲(chǔ)由節(jié)點(diǎn)(nodes)組成的文檔結(jié)構(gòu)。與document相比,最大的區(qū)別是DocumentFragment不是真實(shí) DOM 樹的一部分,它的變化不會(huì)觸發(fā) DOM 樹的重新渲染,且不會(huì)導(dǎo)致性能等問(wèn)題。

當(dāng)我們把一個(gè) DocumentFragment 節(jié)點(diǎn)插入文檔樹時(shí),插入的不是 DocumentFragment 自身,而是它的所有子孫節(jié)點(diǎn)。在頻繁的DOM操作時(shí),我們就可以將DOM元素插入DocumentFragment,之后一次性的將所有的子孫節(jié)點(diǎn)插入文檔中。和直接操作DOM相比,將DocumentFragment 節(jié)點(diǎn)插入DOM樹時(shí),不會(huì)觸發(fā)頁(yè)面的重繪,這樣就大大提高了頁(yè)面的性能。

四、節(jié)流與防抖


1. 對(duì)節(jié)流與防抖的理解

  • 函數(shù)防抖是指在事件被觸發(fā) n 秒后再執(zhí)行回調(diào),如果在這 n 秒內(nèi)事件又被觸發(fā),則重新計(jì)時(shí)。這可以使用在一些點(diǎn)擊請(qǐng)求的事件上,避免因?yàn)橛脩舻亩啻吸c(diǎn)擊向后端發(fā)送多次請(qǐng)求。
  • 函數(shù)節(jié)流是指規(guī)定一個(gè)單位時(shí)間,在這個(gè)單位時(shí)間內(nèi),只能有一次觸發(fā)事件的回調(diào)函數(shù)執(zhí)行,如果在同一個(gè)單位時(shí)間內(nèi)某事件被觸發(fā)多次,只有一次能生效。節(jié)流可以使用在 scroll 函數(shù)的事件監(jiān)聽上,通過(guò)事件節(jié)流來(lái)降低事件調(diào)用的頻率。

防抖函數(shù)的應(yīng)用場(chǎng)景:

  • 按鈕提交場(chǎng)景:防?多次提交按鈕,只執(zhí)?最后提交的?次
  • 服務(wù)端驗(yàn)證場(chǎng)景:表單驗(yàn)證需要服務(wù)端配合,只執(zhí)??段連續(xù)的輸?事件的最后?次,還有搜索聯(lián)想詞功能類似?存環(huán)境請(qǐng)?lodash.debounce

節(jié)流函數(shù)的適?場(chǎng)景:

  • 拖拽場(chǎng)景:固定時(shí)間內(nèi)只執(zhí)??次,防?超?頻次觸發(fā)位置變動(dòng)
  • 縮放場(chǎng)景:監(jiān)控瀏覽器resize
  • 動(dòng)畫場(chǎng)景:避免短時(shí)間內(nèi)多次觸發(fā)動(dòng)畫引起性能問(wèn)題

2. 實(shí)現(xiàn)節(jié)流函數(shù)和防抖函數(shù)

函數(shù)防抖的實(shí)現(xiàn):

function debounce(fn, wait) {
  var timer = null;

  return function() {
    var context = this,
      args = [...arguments];

    // 如果此時(shí)存在定時(shí)器的話,則取消之前的定時(shí)器重新記時(shí)
    if (timer) {
      clearTimeout(timer);
      timer = null;
    }

    // 設(shè)置定時(shí)器,使事件間隔指定事件后執(zhí)行
    timer = setTimeout(() => {
      fn.apply(context, args);
    }, wait);
  };
}

函數(shù)節(jié)流的實(shí)現(xiàn):

// 時(shí)間戳版
function throttle(fn, delay) {
  var preTime = Date.now();

  return function() {
    var context = this,
      args = [...arguments],
      nowTime = Date.now();

    // 如果兩次時(shí)間間隔超過(guò)了指定時(shí)間,則執(zhí)行函數(shù)。
    if (nowTime - preTime >= delay) {
      preTime = Date.now();
      return fn.apply(context, args);
    }
  };
}

// 定時(shí)器版
function throttle (fun, wait){
  let timeout = null
  return function(){
    let context = this
    let args = [...arguments]
    if(!timeout){
      timeout = setTimeout(() => {
        fun.apply(context, args)
        timeout = null 
      }, wait)
    }
  }
}

五、圖片優(yōu)化


1. 如何對(duì)項(xiàng)目中的圖片進(jìn)行優(yōu)化?

  1. 不用圖片。很多時(shí)候會(huì)使用到很多修飾類圖片,其實(shí)這類修飾圖片完全可以用 CSS 去代替。
  2. 對(duì)于移動(dòng)端來(lái)說(shuō),屏幕寬度就那么點(diǎn),完全沒(méi)有必要去加載原圖浪費(fèi)帶寬。一般圖片都用 CDN 加載,可以計(jì)算出適配屏幕的寬度,然后去請(qǐng)求相應(yīng)裁剪好的圖片。
  3. 小圖使用 base64 格式
  4. 將多個(gè)圖標(biāo)文件整合到一張圖片中(雪碧圖)
  5. 選擇正確的圖片格式:
    • 對(duì)于能夠顯示 WebP 格式的瀏覽器盡量使用 WebP 格式。因?yàn)?nbsp;WebP 格式具有更好的圖像數(shù)據(jù)壓縮算法,能帶來(lái)更小的圖片體積,而且擁有肉眼識(shí)別無(wú)差異的圖像質(zhì)量,缺點(diǎn)就是兼容性并不好
    • 小圖使用 PNG,其實(shí)對(duì)于大部分圖標(biāo)這類圖片,完全可以使用 SVG 代替
    • 照片使用 JPEG

2. 常見的圖片格式及使用場(chǎng)景

(1)BMP是無(wú)損的、既支持索引色也支持直接色的點(diǎn)陣圖。這種圖片格式幾乎沒(méi)有對(duì)數(shù)據(jù)進(jìn)行壓縮,所以BMP格式的圖片通常是較大的文件。

(2)GIF是無(wú)損的、采用索引色的點(diǎn)陣圖。采用LZW壓縮算法進(jìn)行編碼。文件小,是GIF格式的優(yōu)點(diǎn),同時(shí),GIF格式還具有支持動(dòng)畫以及透明的優(yōu)點(diǎn)。但是GIF格式僅支持8bit的索引色,所以GIF格式適用于對(duì)色彩要求不高同時(shí)需要文件體積較小的場(chǎng)景。

(3)JPEG是有損的、采用直接色的點(diǎn)陣圖。JPEG的圖片的優(yōu)點(diǎn)是采用了直接色,得益于更豐富的色彩,JPEG非常適合用來(lái)存儲(chǔ)照片,與GIF相比,JPEG不適合用來(lái)存儲(chǔ)企業(yè)Logo、線框類的圖。因?yàn)橛袚p壓縮會(huì)導(dǎo)致圖片模糊,而直接色的選用,又會(huì)導(dǎo)致圖片文件較GIF更大。

(4)PNG-8是無(wú)損的、使用索引色的點(diǎn)陣圖。PNG是一種比較新的圖片格式,PNG-8是非常好的GIF格式替代者,在可能的情況下,應(yīng)該盡可能的使用PNG-8而不是GIF,因?yàn)樵谙嗤膱D片效果下,PNG-8具有更小的文件體積。除此之外,PNG-8還支持透明度的調(diào)節(jié),而GIF并不支持。除非需要?jiǎng)赢嫷闹С郑駝t沒(méi)有理由使用GIF而不是PNG-8。

(5)PNG-24是無(wú)損的、使用直接色的點(diǎn)陣圖。PNG-24的優(yōu)點(diǎn)在于它壓縮了圖片的數(shù)據(jù),使得同樣效果的圖片,PNG-24格式的文件大小要比BMP小得多。當(dāng)然,PNG24的圖片還是要比JPEG、GIF、PNG-8大得多。

(6)SVG是無(wú)損的矢量圖。SVG是矢量圖意味著SVG圖片由直線和曲線以及繪制它們的方法組成。當(dāng)放大SVG圖片時(shí),看到的還是線和曲線,而不會(huì)出現(xiàn)像素點(diǎn)。這意味著SVG圖片在放大時(shí),不會(huì)失真,所以它非常適合用來(lái)繪制Logo、Icon等。

(7)WebP是谷歌開發(fā)的一種新圖片格式,WebP是同時(shí)支持有損和無(wú)損壓縮的、使用直接色的點(diǎn)陣圖。從名字就可以看出來(lái)它是為Web而生的,什么叫為Web而生呢?就是說(shuō)相同質(zhì)量的圖片,WebP具有更小的文件體積?,F(xiàn)在網(wǎng)站上充滿了大量的圖片,如果能夠降低每一個(gè)圖片的文件大小,那么將大大減少瀏覽器和服務(wù)器之間的數(shù)據(jù)傳輸量,進(jìn)而降低訪問(wèn)延遲,提升訪問(wèn)體驗(yàn)。目前只有Chrome瀏覽器和Opera瀏覽器支持WebP格式,兼容性不太好。

  • 在無(wú)損壓縮的情況下,相同質(zhì)量的WebP圖片,文件大小要比PNG小26%;
  • 在有損壓縮的情況下,具有相同圖片精度的WebP圖片,文件大小要比JPEG小25%~34%;
  • WebP圖片格式支持圖片透明度,一個(gè)無(wú)損壓縮的WebP圖片,如果要支持透明度只需要22%的格外文件大小。

六、Webpack優(yōu)化


1. 如何提?webpack的打包速度?

(1)優(yōu)化 Loader

對(duì)于 Loader 來(lái)說(shuō),影響打包效率首當(dāng)其沖必屬 Babel 了。因?yàn)?Babel 會(huì)將代碼轉(zhuǎn)為字符串生成 AST,然后對(duì) AST 繼續(xù)進(jìn)行轉(zhuǎn)變最后再生成新的代碼,項(xiàng)目越大,轉(zhuǎn)換代碼越多,效率就越低。當(dāng)然了,這是可以優(yōu)化的。

首先我們優(yōu)化 Loader 的文件搜索范圍

module.exports = {
  module: {
    rules: [
      {
        // js 文件才使用 babel
        test: /\.js$/,
        loader: 'babel-loader',
        // 只在 src 文件夾下查找
        include: [resolve('src')],
        // 不會(huì)去查找的路徑
        exclude: /node_modules/
      }
    ]
  }
}

對(duì)于 Babel 來(lái)說(shuō),希望只作用在 JS 代碼上的,然后 node_modules 中使用的代碼都是編譯過(guò)的,所以完全沒(méi)有必要再去處理一遍。

當(dāng)然這樣做還不夠,還可以將 Babel 編譯過(guò)的文件緩存起來(lái),下次只需要編譯更改過(guò)的代碼文件即可,這樣可以大幅度加快打包時(shí)間

loader: 'babel-loader?cacheDirectory=true'

(2)HappyPack

受限于 Node 是單線程運(yùn)行的,所以 Webpack 在打包的過(guò)程中也是單線程的,特別是在執(zhí)行 Loader 的時(shí)候,長(zhǎng)時(shí)間編譯的任務(wù)很多,這樣就會(huì)導(dǎo)致等待的情況。

HappyPack 可以將 Loader 的同步執(zhí)行轉(zhuǎn)換為并行的,這樣就能充分利用系統(tǒng)資源來(lái)加快打包效率了

module: {
  loaders: [
    {
      test: /\.js$/,
      include: [resolve('src')],
      exclude: /node_modules/,
      // id 后面的內(nèi)容對(duì)應(yīng)下面
      loader: 'happypack/loader?id=happybabel'
    }
  ]
},
plugins: [
  new HappyPack({
    id: 'happybabel',
    loaders: ['babel-loader?cacheDirectory'],
    // 開啟 4 個(gè)線程
    threads: 4
  })
]

(3)DllPlugin

DllPlugin 可以將特定的類庫(kù)提前打包然后引入。這種方式可以極大的減少打包類庫(kù)的次數(shù),只有當(dāng)類庫(kù)更新版本才有需要重新打包,并且也實(shí)現(xiàn)了將公共代碼抽離成單獨(dú)文件的優(yōu)化方案。DllPlugin的使用方法如下:

// 單獨(dú)配置在一個(gè)文件中
// webpack.dll.conf.js
const path = require('path')
const webpack = require('webpack')
module.exports = {
  entry: {
    // 想統(tǒng)一打包的類庫(kù)
    vendor: ['react']
  },
  output: {
    path: path.join(__dirname, 'dist'),
    filename: '[name].dll.js',
    library: '[name]-[hash]'
  },
  plugins: [
    new webpack.DllPlugin({
      // name 必須和 output.library 一致
      name: '[name]-[hash]',
      // 該屬性需要與 DllReferencePlugin 中一致
      context: __dirname,
      path: path.join(__dirname, 'dist', '[name]-manifest.json')
    })
  ]
}

然后需要執(zhí)行這個(gè)配置文件生成依賴文件,接下來(lái)需要使用 DllReferencePlugin 將依賴文件引入項(xiàng)目中

// webpack.conf.js
module.exports = {
  // ...省略其他配置
  plugins: [
    new webpack.DllReferencePlugin({
      context: __dirname,
      // manifest 就是之前打包出來(lái)的 json 文件
      manifest: require('./dist/vendor-manifest.json'),
    })
  ]
}

(4)代碼壓縮

在 Webpack3 中,一般使用 UglifyJS 來(lái)壓縮代碼,但是這個(gè)是單線程運(yùn)行的,為了加快效率,可以使用 webpack-parallel-uglify-plugin 來(lái)并行運(yùn)行 UglifyJS,從而提高效率。

在 Webpack4 中,不需要以上這些操作了,只需要將 mode 設(shè)置為 production 就可以默認(rèn)開啟以上功能。代碼壓縮也是我們必做的性能優(yōu)化方案,當(dāng)然我們不止可以壓縮 JS 代碼,還可以壓縮 HTML、CSS 代碼,并且在壓縮 JS 代碼的過(guò)程中,我們還可以通過(guò)配置實(shí)現(xiàn)比如刪除 console.log 這類代碼的功能。

(5)其他

可以通過(guò)一些小的優(yōu)化點(diǎn)來(lái)加快打包速度

  • ?resolve.extensions?:用來(lái)表明文件后綴列表,默認(rèn)查找順序是 ?['.js', '.json']?,如果你的導(dǎo)入文件沒(méi)有添加后綴就會(huì)按照這個(gè)順序查找文件。我們應(yīng)該盡可能減少后綴列表長(zhǎng)度,然后將出現(xiàn)頻率高的后綴排在前面
  • ?resolve.alias?:可以通過(guò)別名的方式來(lái)映射一個(gè)路徑,能讓 Webpack 更快找到路徑
  • ?module.noParse?:如果你確定一個(gè)文件下沒(méi)有其他依賴,就可以使用該屬性讓 Webpack 不掃描該文件,這種方式對(duì)于大型的類庫(kù)很有幫助

2. 如何減少 Webpack 打包體積

(1)按需加載

在開發(fā) SPA 項(xiàng)目的時(shí)候,項(xiàng)目中都會(huì)存在很多路由頁(yè)面。如果將這些頁(yè)面全部打包進(jìn)一個(gè) JS 文件的話,雖然將多個(gè)請(qǐng)求合并了,但是同樣也加載了很多并不需要的代碼,耗費(fèi)了更長(zhǎng)的時(shí)間。那么為了首頁(yè)能更快地呈現(xiàn)給用戶,希望首頁(yè)能加載的文件體積越小越好,這時(shí)候就可以使用按需加載,將每個(gè)路由頁(yè)面單獨(dú)打包為一個(gè)文件。當(dāng)然不僅僅路由可以按需加載,對(duì)于 loadash 這種大型類庫(kù)同樣可以使用這個(gè)功能。

按需加載的代碼實(shí)現(xiàn)這里就不詳細(xì)展開了,因?yàn)殍b于用的框架不同,實(shí)現(xiàn)起來(lái)都是不一樣的。當(dāng)然了,雖然他們的用法可能不同,但是底層的機(jī)制都是一樣的。都是當(dāng)使用的時(shí)候再去下載對(duì)應(yīng)文件,返回一個(gè) Promise,當(dāng) Promise 成功以后去執(zhí)行回調(diào)。

(2)Scope Hoisting

Scope Hoisting 會(huì)分析出模塊之間的依賴關(guān)系,盡可能的把打包出來(lái)的模塊合并到一個(gè)函數(shù)中去。

比如希望打包兩個(gè)文件:

// test.js
export const a = 1
// index.js
import { a } from './test.js'

對(duì)于這種情況,打包出來(lái)的代碼會(huì)類似這樣:

[
  /* 0 */
  function (module, exports, require) {
    //...
  },
  /* 1 */
  function (module, exports, require) {
    //...
  }
]

但是如果使用 Scope Hoisting ,代碼就會(huì)盡可能的合并到一個(gè)函數(shù)中去,也就變成了這樣的類似代碼:

[
  /* 0 */
  function (module, exports, require) {
    //...
  }
]

這樣的打包方式生成的代碼明顯比之前的少多了。如果在 Webpack4 中你希望開啟這個(gè)功能,只需要啟用 optimization.concatenateModules 就可以了:

module.exports = {
  optimization: {
    concatenateModules: true
  }
}

(3)Tree Shaking

Tree Shaking 可以實(shí)現(xiàn)刪除項(xiàng)目中未被引用的代碼,比如:

// test.js
export const a = 1
export const b = 2
// index.js
import { a } from './test.js'

對(duì)于以上情況,test 文件中的變量 b 如果沒(méi)有在項(xiàng)目中使用到的話,就不會(huì)被打包到文件中。

如果使用 Webpack 4 的話,開啟生產(chǎn)環(huán)境就會(huì)自動(dòng)啟動(dòng)這個(gè)優(yōu)化功能。

3. 如何?webpack來(lái)優(yōu)化前端性能?

?webpack優(yōu)化前端性能是指優(yōu)化webpack的輸出結(jié)果,讓打包的最終結(jié)果在瀏覽器運(yùn)?快速?效。

  • 壓縮代碼:刪除多余的代碼、注釋、簡(jiǎn)化代碼的寫法等等?式??梢岳?webpack的 UglifyJsPlugin 和 ParallelUglifyPlugin 來(lái)壓縮JS?件, 利? cssnano (css-loader?minimize)來(lái)壓縮css
  • 利?CDN加速: 在構(gòu)建過(guò)程中,將引?的靜態(tài)資源路徑修改為CDN上對(duì)應(yīng)的路徑??梢岳?webpack對(duì)于 output 參數(shù)和各loader的 publicPath 參數(shù)來(lái)修改資源路徑
  • Tree Shaking: 將代碼中永遠(yuǎn)不會(huì)?到的?段刪除掉。可以通過(guò)在啟動(dòng)webpack時(shí)追加參數(shù) --optimize-minimize 來(lái)實(shí)現(xiàn)
  • Code Splitting: 將代碼按路由維度或者組件分塊(chunk),這樣做到按需加載,同時(shí)可以充分利?瀏覽器緩存
  • 提取公共第三?庫(kù): SplitChunksPlugin插件來(lái)進(jìn)?公共模塊抽取,利?瀏覽器緩存可以?期緩存這些?需頻繁變動(dòng)的公共代碼

4. 如何提?webpack的構(gòu)建速度?

  1. 多??情況下,使? CommonsChunkPlugin 來(lái)提取公共代碼
  2. 通過(guò) externals 配置來(lái)提取常?庫(kù)
  3. 利? DllPlugin 和 DllReferencePlugin 預(yù)編譯資源模塊 通過(guò) DllPlugin 來(lái)對(duì)那些我們引?但是絕對(duì)不會(huì)修改的npm包來(lái)進(jìn)?預(yù)編譯,再通過(guò) DllReferencePlugin 將預(yù)編譯的模塊加載進(jìn)來(lái)。
  4. 使? Happypack 實(shí)現(xiàn)多線程加速編譯
  5. 使? webpack-uglify-parallel 來(lái)提升 uglifyPlugin 的壓縮速度。 原理上 webpack-uglify-parallel 采?了多核并?壓縮來(lái)提升壓縮速度
  6. 使? Tree-shaking 和 Scope Hoisting 來(lái)剔除多余代碼


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)