1. 程式人生 > 其它 >高頻前端面試題彙總之前端效能優化篇

高頻前端面試題彙總之前端效能優化篇

一、CDN

1. CDN的概念

CDN(Content Delivery Network,內容分發網路)是指一種通過網際網路互相連線的電腦網路系統,利用最靠近每位使用者的伺服器,更快、更可靠地將音樂、圖片、視訊、應用程式及其他檔案傳送給使用者,來提供高效能、可擴充套件性及低成本的網路內容傳遞給使用者。

典型的CDN系統由下面三個部分組成:

  • 分發服務系統: 最基本的工作單元就是Cache裝置,cache(邊緣cache)負責直接響應終端使用者的訪問請求,把快取在本地的內容快速地提供給使用者。同時cache還負責與源站點進行內容同步,把更新的內容以及本地沒有的內容從源站點獲取並儲存在本地。Cache裝置的數量、規模、總服務能力是衡量一個CDN系統服務能力的最基本的指標。
  • 負載均衡系統: 主要功能是負責對所有發起服務請求的使用者進行訪問排程,確定提供給使用者的最終實際訪問地址。兩級排程體系分為全域性負載均衡(GSLB)和本地負載均衡(SLB)。全域性負載均衡主要根據使用者就近性原則,通過對每個服務節點進行“最優”判斷,確定向用戶提供服務的cache的物理位置。本地負載均衡主要負責節點內部的裝置負載均衡
  • 運營管理系統: 運營管理系統分為運營管理和網路管理子系統,負責處理業務層面的與外界系統互動所必須的收集、整理、交付工作,包含客戶管理、產品管理、計費管理、統計分析等功能。

2. CDN的作用

CDN一般會用來託管Web資源(包括文字、圖片和指令碼等),可供下載的資源(媒體檔案、軟體、文件等),應用程式(入口網站等)。使用CDN來加速這些資源的訪問。

(1)在效能方面,引入CDN的作用在於:

  • 使用者收到的內容來自最近的資料中心,延遲更低,內容載入更快
  • 部分資源請求分配給了CDN,減少了伺服器的負載

(2)在安全方面,CDN有助於防禦DDoS、MITM等網路攻擊:

  • 針對DDoS:通過監控分析異常流量,限制其請求頻率
  • 針對MITM:從源伺服器到 CDN 節點到 ISP(Internet Service Provider),全鏈路 HTTPS 通訊

除此之外,CDN作為一種基礎的雲服務,同樣具有資源託管、按需擴充套件(能夠應對流量高峰)等方面的優勢。

3. CDN的原理

CDN和DNS有著密不可分的聯絡,先來看一下DNS的解析域名過程,在瀏覽器輸入

www.test.com 的解析過程如下: (1) 檢查瀏覽器快取 (2)檢查作業系統快取,常見的如hosts檔案 (3)檢查路由器快取 (4)如果前幾步都沒沒找到,會向ISP(網路服務提供商)的LDNS伺服器查詢 (5)如果LDNS伺服器沒找到,會向根域名伺服器(Root Server)請求解析,分為以下幾步:

  • 根伺服器返回頂級域名(TLD)伺服器如.com.cn.org等的地址,該例子中會返回.com的地址
  • 接著向頂級域名伺服器傳送請求,然後會返回次級域名(SLD)伺服器的地址,本例子會返回.test的地址
  • 接著向次級域名伺服器傳送請求,然後會返回通過域名查詢到的目標IP,本例子會返回www.test.com的地址
  • Local DNS Server會快取結果,並返回給使用者,快取在系統中

CDN的工作原理: (1)使用者未使用CDN快取資源的過程:

  1. 瀏覽器通過DNS對域名進行解析(就是上面的DNS解析過程),依次得到此域名對應的IP地址
  2. 瀏覽器根據得到的IP地址,向域名的服務主機發送資料請求
  3. 伺服器向瀏覽器返回響應資料

(2)使用者使用CDN快取資源的過程:

  1. 對於點選的資料的URL,經過本地DNS系統的解析,發現該URL對應的是一個CDN專用的DNS伺服器,DNS系統就會將域名解析權交給CNAME指向的CDN專用的DNS伺服器。
  2. CND專用DNS伺服器將CND的全域性負載均衡裝置IP地址返回給使用者
  3. 使用者向CDN的全域性負載均衡裝置發起資料請求
  4. CDN的全域性負載均衡裝置根據使用者的IP地址,以及使用者請求的內容URL,選擇一臺使用者所屬區域的區域負載均衡裝置,告訴使用者向這臺裝置發起請求
  5. 區域負載均衡裝置選擇一臺合適的快取伺服器來提供服務,將該快取伺服器的IP地址返回給全域性負載均衡裝置
  6. 全域性負載均衡裝置把伺服器的IP地址返回給使用者
  7. 使用者向該快取伺服器發起請求,快取伺服器響應使用者的請求,將使用者所需內容傳送至使用者終端。

如果快取伺服器沒有使用者想要的內容,那麼快取伺服器就會向它的上一級快取伺服器請求內容,以此類推,直到獲取到需要的資源。最後如果還是沒有,就會回到自己的伺服器去獲取資源。

CNAME(意為:別名):在域名解析中,實際上解析出來的指定域名對應的IP地址,或者該域名的一個CNAME,然後再根據這個CNAME來查詢對應的IP地址。

4. CDN的使用場景

  • **使用第三方的CDN服務:**如果想要開源一些專案,可以使用第三方的CDN服務
  • **使用CDN進行靜態資源的快取:**將自己網站的靜態資源放在CDN上,比如js、css、圖片等。可以將整個專案放在CDN上,完成一鍵部署。
  • **直播傳送:**直播本質上是使用流媒體進行傳送,CDN也是支援流媒體傳送的,所以直播完全可以使用CDN來提高訪問速度。CDN在處理流媒體的時候與處理普通靜態檔案有所不同,普通檔案如果在邊緣節點沒有找到的話,就會去上一層接著尋找,但是流媒體本身資料量就非常大,如果使用回源的方式,必然會帶來效能問題,所以流媒體一般採用的都是主動推送的方式來進行。

二、懶載入

1. 懶載入的概念

懶載入也叫做延遲載入、按需載入,指的是在長網頁中延遲載入圖片資料,是一種較好的網頁效能優化的方式。在比較長的網頁或應用中,如果圖片很多,所有的圖片都被加載出來,而使用者只能看到可視視窗的那一部分圖片資料,這樣就浪費了效能。

如果使用圖片的懶載入就可以解決以上問題。在滾動螢幕之前,視覺化區域之外的圖片不會進行載入,在滾動螢幕時才載入。這樣使得網頁的載入速度更快,減少了伺服器的負載。懶載入適用於圖片較多,頁面列表較長(長列表)的場景中。

2. 懶載入的特點

  • 減少無用資源的載入:使用懶載入明顯減少了伺服器的壓力和流量,同時也減小了瀏覽器的負擔。
  • 提升使用者體驗: 如果同時載入較多圖片,可能需要等待的時間較長,這樣影響了使用者體驗,而使用懶載入就能大大的提高使用者體驗。
  • 防止載入過多圖片而影響其他資原始檔的載入 :會影響網站應用的正常使用。

3. 懶載入的實現原理

圖片的載入是由src引起的,當對src賦值時,瀏覽器就會請求圖片資源。根據這個原理,我們使用HTML5 的data-xxx屬性來儲存圖片的路徑,在需要載入圖片的時候,將data-xxx中圖片的路徑賦值給src,這樣就實現了圖片的按需載入,即懶載入。

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

懶載入的實現重點在於確定使用者需要載入哪張圖片,在瀏覽器中,可視區域內的資源就是使用者需要的資源。所以當圖片出現在可視區域時,獲取圖片的真實地址並賦值給圖片即可。

使用原生JavaScript實現懶載入:

知識點:

(1)window.innerHeight 是瀏覽器可視區的高度

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

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

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

圖示: 程式碼實現:

<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. 懶載入與預載入的區別

這兩種方式都是提高網頁效能的方式,兩者主要區別是一個是提前載入,一個是遲緩甚至不載入。懶載入對伺服器前端有一定的緩解壓力作用,預載入則會增加伺服器前端壓力。

  • 懶載入也叫延遲載入,指的是在長網頁中延遲載入圖片的時機,當用戶需要訪問時,再去載入,這樣可以提高網站的首屏載入速度,提升使用者的體驗,並且可以減少伺服器的壓力。它適用於圖片很多,頁面很長的電商網站的場景。懶載入的實現原理是,將頁面上的圖片的 src 屬性設定為空字串,將圖片的真實路徑儲存在一個自定義屬性中,當頁面滾動的時候,進行判斷,如果圖片進入頁面可視區域內,則從自定義屬性中取出真實路徑賦值給圖片的 src 屬性,以此來實現圖片的延遲載入。
  • 預載入指的是將所需的資源提前請求載入到本地,這樣後面在需要用到時就直接從快取取資源。 通過預載入能夠減少使用者的等待時間,提高使用者的體驗。我瞭解的預載入的最常用的方式是使用 js 中的 image 物件,通過為 image 物件來設定 scr 屬性,來實現圖片的預載入。

三、迴流與重繪

1. 迴流與重繪的概念及觸發條件

(1)迴流

當渲染樹中部分或者全部元素的尺寸、結構或者屬性發生變化時,瀏覽器會重新渲染部分或者全部文件的過程就稱為迴流

下面這些操作會導致迴流:

  • 頁面的首次渲染
  • 瀏覽器的視窗大小發生變化
  • 元素的內容發生變化
  • 元素的尺寸或者位置發生變化
  • 元素的字型大小發生變化
  • 啟用CSS偽類
  • 查詢某些屬性或者呼叫某些方法
  • 新增或者刪除可見的DOM元素

在觸發迴流(重排)的時候,由於瀏覽器渲染頁面是基於流式佈局的,所以當觸發迴流時,會導致周圍的DOM元素重新排列,它的影響範圍有兩種:

  • 全域性範圍:從根節點開始,對整個渲染樹進行重新佈局
  • 區域性範圍:對渲染樹的某部分或者一個渲染物件進行重新佈局

(2)重繪

當頁面中某些元素的樣式發生變化,但是不會影響其在文件流中的位置時,瀏覽器就會對元素進行重新繪製,這個過程就是重繪

下面這些操作會導致迴流:

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

注意: 當觸發迴流時,一定會觸發重繪,但是重繪不一定會引發迴流。

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

減少迴流與重繪的措施:

  • 操作DOM時,儘量在低層級的DOM節點進行操作
  • 不要使用table佈局, 一個小的改動可能會使整個table進行重新佈局
  • 使用CSS的表示式
  • 不要頻繁操作元素的樣式,對於靜態頁面,可以修改類名,而不是樣式。
  • 使用absolute或者fixed,使元素脫離文件流,這樣他們發生變化就不會影響其他元素
  • 避免頻繁操作DOM,可以建立一個文件片段documentFragment,在它上面應用所有DOM操作,最後再把它新增到文件中
  • 將元素先設定display: none,操作結束後再把它顯示出來。因為在display屬性為none的元素上進行的DOM操作不會引發迴流和重繪。
  • 將DOM的多個讀操作(或者寫操作)放在一起,而不是讀寫操作穿插著寫。這得益於瀏覽器的渲染佇列機制

瀏覽器針對頁面的迴流與重繪,進行了自身的優化——渲染佇列

瀏覽器會將所有的迴流、重繪的操作放在一個佇列中,當佇列中的操作到了一定的數量或者到了一定的時間間隔,瀏覽器就會對佇列進行批處理。這樣就會讓多次的迴流、重繪變成一次迴流重繪。

上面,將多個讀操作(或者寫操作)放在一起,就會等所有的讀操作進入佇列之後執行,這樣,原本應該是觸發多次迴流,變成了只觸發一次迴流。

3. 如何優化動畫?

對於如何優化動畫,我們知道,一般情況下,動畫需要頻繁的操作DOM,就就會導致頁面的效能問題,我們可以將動畫的position屬性設定為absolute或者fixed,將動畫脫離文件流,這樣他的迴流就不會影響到頁面了。

4. documentFragment 是什麼?用它跟直接操作 DOM 的區別是什麼?

MDN中對documentFragment的解釋:

DocumentFragment,文件片段介面,一個沒有父物件的最小文件物件。它被作為一個輕量版的 Document使用,就像標準的document一樣,儲存由節點(nodes)組成的文件結構。與document相比,最大的區別是DocumentFragment不是真實 DOM 樹的一部分,它的變化不會觸發 DOM 樹的重新渲染,且不會導致效能等問題。

當我們把一個 DocumentFragment 節點插入文件樹時,插入的不是 DocumentFragment 自身,而是它的所有子孫節點。在頻繁的DOM操作時,我們就可以將DOM元素插入DocumentFragment,之後一次性的將所有的子孫節點插入文件中。和直接操作DOM相比,將DocumentFragment 節點插入DOM樹時,不會觸發頁面的重繪,這樣就大大提高了頁面的效能。

四、節流與防抖

1. 對節流與防抖的理解

  • 函式防抖是指在事件被觸發 n 秒後再執行回撥,如果在這 n 秒內事件又被觸發,則重新計時。這可以使用在一些點選請求的事件上,避免因為使用者的多次點擊向後端傳送多次請求。
  • 函式節流是指規定一個單位時間,在這個單位時間內,只能有一次觸發事件的回撥函式執行,如果在同一個單位時間內某事件被觸發多次,只有一次能生效。節流可以使用在 scroll 函式的事件監聽上,通過事件節流來降低事件呼叫的頻率。

防抖函式的應用場景:

  • 按鈕提交場景:防⽌多次提交按鈕,只執⾏最後提交的⼀次
  • 服務端驗證場景:表單驗證需要服務端配合,只執⾏⼀段連續的輸⼊事件的最後⼀次,還有搜尋聯想詞功能類似⽣存環境請⽤lodash.debounce

節流函式的適⽤場景:

  • 拖拽場景:固定時間內只執⾏⼀次,防⽌超⾼頻次觸發位置變動
  • 縮放場景:監控瀏覽器resize
  • 動畫場景:避免短時間內多次觸發動畫引起效能問題

2. 實現節流函式和防抖函式

函式防抖的實現:

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

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

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

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

函式節流的實現:

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

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

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

// 定時器版
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)
    }
  }
}

五、圖片優化

1. 如何對專案中的圖片進行優化?

  1. 不用圖片。很多時候會使用到很多修飾類圖片,其實這類修飾圖片完全可以用 CSS 去代替。
  2. 對於移動端來說,螢幕寬度就那麼點,完全沒有必要去載入原圖浪費頻寬。一般圖片都用 CDN 載入,可以計算出適配螢幕的寬度,然後去請求相應裁剪好的圖片。
  3. 小圖使用 base64 格式
  4. 將多個圖示檔案整合到一張圖片中(雪碧圖)
  5. 選擇正確的圖片格式:
    • 對於能夠顯示 WebP 格式的瀏覽器儘量使用 WebP 格式。因為 WebP 格式具有更好的影象資料壓縮演算法,能帶來更小的圖片體積,而且擁有肉眼識別無差異的影象質量,缺點就是相容性並不好
    • 小圖使用 PNG,其實對於大部分圖示這類圖片,完全可以使用 SVG 代替
    • 照片使用 JPEG

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

(1)BMP,是無損的、既支援索引色也支援直接色的點陣圖。這種圖片格式幾乎沒有對資料進行壓縮,所以BMP格式的圖片通常是較大的檔案。

(2)GIF是無損的、採用索引色的點陣圖。採用LZW壓縮演算法進行編碼。檔案小,是GIF格式的優點,同時,GIF格式還具有支援動畫以及透明的優點。但是GIF格式僅支援8bit的索引色,所以GIF格式適用於對色彩要求不高同時需要檔案體積較小的場景。

(3)JPEG是有損的、採用直接色的點陣圖。JPEG的圖片的優點是採用了直接色,得益於更豐富的色彩,JPEG非常適合用來儲存照片,與GIF相比,JPEG不適合用來儲存企業Logo、線框類的圖。因為有失真壓縮會導致圖片模糊,而直接色的選用,又會導致圖片檔案較GIF更大。

(4)PNG-8是無損的、使用索引色的點陣圖。PNG是一種比較新的圖片格式,PNG-8是非常好的GIF格式替代者,在可能的情況下,應該儘可能的使用PNG-8而不是GIF,因為在相同的圖片效果下,PNG-8具有更小的檔案體積。除此之外,PNG-8還支援透明度的調節,而GIF並不支援。除非需要動畫的支援,否則沒有理由使用GIF而不是PNG-8。

(5)PNG-24是無損的、使用直接色的點陣圖。PNG-24的優點在於它壓縮了圖片的資料,使得同樣效果的圖片,PNG-24格式的檔案大小要比BMP小得多。當然,PNG24的圖片還是要比JPEG、GIF、PNG-8大得多。

(6)SVG是無損的向量圖。SVG是向量圖意味著SVG圖片由直線和曲線以及繪製它們的方法組成。當放大SVG圖片時,看到的還是線和曲線,而不會出現畫素點。這意味著SVG圖片在放大時,不會失真,所以它非常適合用來繪製Logo、Icon等。

(7)WebP是谷歌開發的一種新圖片格式,WebP是同時支援有損和無失真壓縮的、使用直接色的點陣圖。從名字就可以看出來它是為Web而生的,什麼叫為Web而生呢?就是說相同質量的圖片,WebP具有更小的檔案體積。現在網站上充滿了大量的圖片,如果能夠降低每一個圖片的檔案大小,那麼將大大減少瀏覽器和伺服器之間的資料傳輸量,進而降低訪問延遲,提升訪問體驗。目前只有Chrome瀏覽器和Opera瀏覽器支援WebP格式,相容性不太好。

  • 在無失真壓縮的情況下,相同質量的WebP圖片,檔案大小要比PNG小26%;
  • 在有失真壓縮的情況下,具有相同圖片精度的WebP圖片,檔案大小要比JPEG小25%~34%;
  • WebP圖片格式支援圖片透明度,一個無失真壓縮的WebP圖片,如果要支援透明度只需要22%的格外檔案大小。

六、Webpack優化

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

(1)優化 Loader

對於 Loader 來說,影響打包效率首當其衝必屬 Babel 了。因為 Babel 會將程式碼轉為字串生成 AST,然後對 AST 繼續進行轉變最後再生成新的程式碼,專案越大,轉換程式碼越多,效率就越低。當然了,這是可以優化的。

首先我們優化 Loader 的檔案搜尋範圍

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

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

當然這樣做還不夠,還可以將 Babel 編譯過的檔案快取起來,下次只需要編譯更改過的程式碼檔案即可,這樣可以大幅度加快打包時間

loader: 'babel-loader?cacheDirectory=true'

(2)HappyPack

受限於 Node 是單執行緒執行的,所以 Webpack 在打包的過程中也是單執行緒的,特別是在執行 Loader 的時候,長時間編譯的任務很多,這樣就會導致等待的情況。

HappyPack 可以將 Loader 的同步執行轉換為並行的,這樣就能充分利用系統資源來加快打包效率了

module: {
  loaders: [
    {
      test: /\.js$/,
      include: [resolve('src')],
      exclude: /node_modules/,
      // id 後面的內容對應下面
      loader: 'happypack/loader?id=happybabel'
    }
  ]
},
plugins: [
  new HappyPack({
    id: 'happybabel',
    loaders: ['babel-loader?cacheDirectory'],
    // 開啟 4 個執行緒
    threads: 4
  })
]

(3)DllPlugin

DllPlugin 可以將特定的類庫提前打包然後引入。這種方式可以極大的減少打包類庫的次數,只有當類庫更新版本才有需要重新打包,並且也實現了將公共程式碼抽離成單獨檔案的優化方案。DllPlugin的使用方法如下:

// 單獨配置在一個檔案中
// webpack.dll.conf.js
const path = require('path')
const webpack = require('webpack')
module.exports = {
  entry: {
    // 想統一打包的類庫
    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')
    })
  ]
}

然後需要執行這個配置檔案生成依賴檔案,接下來需要使用 DllReferencePlugin 將依賴檔案引入專案中

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

(4)程式碼壓縮

在 Webpack3 中,一般使用 UglifyJS 來壓縮程式碼,但是這個是單執行緒執行的,為了加快效率,可以使用 webpack-parallel-uglify-plugin 來並行執行 UglifyJS,從而提高效率。

在 Webpack4 中,不需要以上這些操作了,只需要將 mode 設定為 production 就可以預設開啟以上功能。程式碼壓縮也是我們必做的效能優化方案,當然我們不止可以壓縮 JS 程式碼,還可以壓縮 HTML、CSS 程式碼,並且在壓縮 JS 程式碼的過程中,我們還可以通過配置實現比如刪除 console.log 這類程式碼的功能。

(5)其他

可以通過一些小的優化點來加快打包速度

  • resolve.extensions:用來表明檔案字尾列表,預設查詢順序是 ['.js', '.json'],如果你的匯入檔案沒有新增字尾就會按照這個順序查詢檔案。我們應該儘可能減少字尾列表長度,然後將出現頻率高的字尾排在前面
  • resolve.alias:可以通過別名的方式來對映一個路徑,能讓 Webpack 更快找到路徑
  • module.noParse:如果你確定一個檔案下沒有其他依賴,就可以使用該屬性讓 Webpack 不掃描該檔案,這種方式對於大型的類庫很有幫助

2. 如何減少 Webpack 打包體積

(1)按需載入

在開發 SPA 專案的時候,專案中都會存在很多路由頁面。如果將這些頁面全部打包進一個 JS 檔案的話,雖然將多個請求合併了,但是同樣也載入了很多並不需要的程式碼,耗費了更長的時間。那麼為了首頁能更快地呈現給使用者,希望首頁能載入的檔案體積越小越好,這時候就可以使用按需載入,將每個路由頁面單獨打包為一個檔案。當然不僅僅路由可以按需載入,對於 loadash 這種大型類庫同樣可以使用這個功能。

按需載入的程式碼實現這裡就不詳細展開了,因為鑑於用的框架不同,實現起來都是不一樣的。當然了,雖然他們的用法可能不同,但是底層的機制都是一樣的。都是當使用的時候再去下載對應檔案,返回一個 Promise,當 Promise 成功以後去執行回撥。

(2)Scope Hoisting

Scope Hoisting 會分析出模組之間的依賴關係,儘可能的把打包出來的模組合併到一個函式中去。

比如希望打包兩個檔案:

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

對於這種情況,打包出來的程式碼會類似這樣:

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

但是如果使用 Scope Hoisting ,程式碼就會盡可能的合併到一個函式中去,也就變成了這樣的類似程式碼:

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

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

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

(3)Tree Shaking

Tree Shaking 可以實現刪除專案中未被引用的程式碼,比如:

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

對於以上情況,test 檔案中的變數 b 如果沒有在專案中使用到的話,就不會被打包到檔案中。

如果使用 Webpack 4 的話,開啟生產環境就會自動啟動這個優化功能。

3. 如何⽤webpack來優化前端效能?

⽤webpack優化前端效能是指優化webpack的輸出結果,讓打包的最終結果在瀏覽器運⾏快速⾼效。

  • 壓縮程式碼:刪除多餘的程式碼、註釋、簡化程式碼的寫法等等⽅式。可以利⽤webpack的 UglifyJsPlugin 和 ParallelUglifyPlugin 來壓縮JS⽂件, 利⽤ cssnano (css-loader?minimize)來壓縮css
  • 利⽤CDN加速: 在構建過程中,將引⽤的靜態資源路徑修改為CDN上對應的路徑。可以利⽤webpack對於 output 引數和各loader的 publicPath 引數來修改資源路徑
  • Tree Shaking: 將程式碼中永遠不會⾛到的⽚段刪除掉。可以通過在啟動webpack時追加引數 --optimize-minimize 來實現
  • Code Splitting: 將程式碼按路由維度或者元件分塊(chunk),這樣做到按需載入,同時可以充分利⽤瀏覽器快取
  • 提取公共第三⽅庫: SplitChunksPlugin外掛來進⾏公共模組抽取,利⽤瀏覽器快取可以⻓期快取這些⽆需頻繁變動的公共程式碼

4. 如何提⾼webpack的構建速度?

  1. 多⼊⼝情況下,使⽤ CommonsChunkPlugin 來提取公共程式碼
  2. 通過 externals 配置來提取常⽤庫
  3. 利⽤ DllPlugin 和 DllReferencePlugin 預編譯資源模組 通過 DllPlugin 來對那些我們引⽤但是絕對不會修改的npm包來進⾏預編譯,再通過 DllReferencePlugin 將預編譯的模組載入進來。
  4. 使⽤ Happypack 實現多執行緒加速編譯
  5. 使⽤ webpack-uglify-parallel 來提升 uglifyPlugin 的壓縮速度。 原理上 webpack-uglify-parallel 採⽤了多核並⾏壓縮來提升壓縮速度
  6. 使⽤ Tree-shaking 和 Scope Hoisting 來剔除多餘程式碼


轉自:https://juejin.cn/post/6941278592215515143