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

2023-02-17 10:51 更新

一、CDN


1. CDN的概念

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

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

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

2. CDN的作用

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

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

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

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

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

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

3. CDN的原理

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

(1) 檢查瀏覽器緩存

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

(3)檢查路由器緩存

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

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

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

CDN的工作原理:

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

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

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

  1. 對于點(diǎn)擊的數(shù)據(jù)的URL,經(jīng)過本地DNS系統(tǒng)的解析,發(fā)現(xiàn)該URL對應(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ù)請求
  4. CDN的全局負(fù)載均衡設(shè)備根據(jù)用戶的IP地址,以及用戶請求的內(nèi)容URL,選擇一臺(tái)用戶所屬區(qū)域的區(qū)域負(fù)載均衡設(shè)備,告訴用戶向這臺(tái)設(shè)備發(fā)起請求
  5. 區(qū)域負(fù)載均衡設(shè)備選擇一臺(tái)合適的緩存服務(wù)器來提供服務(wù),將該緩存服務(wù)器的IP地址返回給全局負(fù)載均衡設(shè)備
  6. 全局負(fù)載均衡設(shè)備把服務(wù)器的IP地址返回給用戶
  7. 用戶向該緩存服務(wù)器發(fā)起請求,緩存服務(wù)器響應(yīng)用戶的請求,將用戶所需內(nèi)容發(fā)送至用戶終端。

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

image

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

4. CDN的使用場景

  • 使用第三方的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來提高訪問速度。CDN在處理流媒體的時(shí)候與處理普通靜態(tài)文件有所不同,普通文件如果在邊緣節(jié)點(diǎn)沒有找到的話,就會(huì)去上一層接著尋找,但是流媒體本身數(shù)據(jù)量就非常大,如果使用回源的方式,必然會(huì)帶來性能問題,所以流媒體一般采用的都是主動(dòng)推送的方式來進(jìn)行。

二、懶加載


1. 懶加載的概念

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

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

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

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

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

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

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

懶加載的實(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)的過的距離

(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)頁性能的方式,兩者主要區(qū)別是一個(gè)是提前加載,一個(gè)是遲緩甚至不加載。懶加載對服務(wù)器前端有一定的緩解壓力作用,預(yù)加載則會(huì)增加服務(wù)器前端壓力。

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

三、回流與重繪


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

(1)回流

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

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

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

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

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

(2)重繪

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

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

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

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

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

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

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

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

MDN中對 documentFragment的解釋:

DocumentFragment,文檔片段接口,一個(gè)沒有父對象的最小文檔對象。它被作為一個(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)致性能等問題。

當(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ā)頁面的重繪,這樣就大大提高了頁面的性能。

四、節(jié)流與防抖


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

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

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

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

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

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

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í)間間隔超過了指定時(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. 如何對項(xiàng)目中的圖片進(jìn)行優(yōu)化?

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

2. 常見的圖片格式及使用場景

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

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

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

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

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

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

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

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

六、Webpack優(yōu)化


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

(1)優(yōu)化 Loader

對于 Loader 來說,影響打包效率首當(dāng)其沖必屬 Babel 了。因?yàn)?Babel 會(huì)將代碼轉(zhuǎn)為字符串生成 AST,然后對 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/
      }
    ]
  }
}

對于 Babel 來說,希望只作用在 JS 代碼上的,然后 node_modules 中使用的代碼都是編譯過的,所以完全沒有必要再去處理一遍。

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

loader: 'babel-loader?cacheDirectory=true'

(2)HappyPack

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

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

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

(3)DllPlugin

DllPlugin 可以將特定的類庫提前打包然后引入。這種方式可以極大的減少打包類庫的次數(shù),只有當(dāng)類庫更新版本才有需要重新打包,并且也實(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)一打包的類庫
    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è)配置文件生成依賴文件,接下來需要使用 DllReferencePlugin 將依賴文件引入項(xiàng)目中

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

(4)代碼壓縮

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

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

(5)其他

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

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

2. 如何減少 Webpack 打包體積

(1)按需加載

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

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

(2)Scope Hoisting

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

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

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

對于這種情況,打包出來的代碼會(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'

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

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

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

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

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

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

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


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

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號