當面試官問我前端可以做的效能優化有哪些

語言: CN / TW / HK

theme: qklhk-chocolate

面試過程中面試官問到前端效能優化有哪些,當我咔咔一頓輸出之後面試官追問:前端可以做的效能優化有哪些呢?

前端優化大概可以有以下幾個方向: - 網路優化 - 頁面渲染優化 - JS優化 - 圖片優化 - webpack打包優化 - React優化 - Vue優化

網路優化

DNS預解析

link標籤的rel屬性設定dns-prefetch,提前獲取域名對應的IP地址

使用快取

減輕服務端壓力,快速得到資料(強快取和協商快取可以看這裡)

使用 CDN(內容分發網路)

使用者與伺服器的物理距離對響應時間也有影響。

內容分發網路(CDN)是一組分散在不同地理位置的 web 伺服器,用來給使用者更高效地傳送內容。典型地,選擇用來發送內容的伺服器是基於網路距離的衡量標準的。例如:選跳數(hop)最少的或者響應時間最快的伺服器。

壓縮響應

壓縮元件通過減少 HTTP 請求產生的響應包的大小,從而降低傳輸時間的方式來提高效能。從 HTTP1.1 開始,Web 客戶端可以通過 HTTP 請求中的 Accept-Encoding 頭來標識對壓縮的支援(這個請求頭會列出一系列的壓縮方法)

如果 Web 伺服器看到請求中的這個頭,就會使用客戶端列出的方法中的一種來壓縮響應。Web 伺服器通過響應中的 Content-Encoding 頭來告知 Web 客戶端使用哪種方法進行的壓縮

目前許多網站通常會壓縮 HTML 文件,指令碼和樣式表的壓縮也是值得的(包括 XML 和 JSON 在內的任何文字響應理論上都值得被壓縮)。但是,圖片和 PDF 檔案不應該被壓縮,因為它們本來已經被壓縮了。

使用多個域名

Chrome 等現代化瀏覽器,都會有同域名限制併發下載數的情況,不同的瀏覽器及版本都不一樣,使用不同的域名可以最大化下載執行緒,但注意保持在 2~4 個域名內,以避免 DNS 查詢損耗。

避免圖片src為空

雖然 src 屬性為空字串,但瀏覽器仍然會向伺服器發起一個 HTTP 請求:

IE 向頁面所在的目錄傳送請求; Safari、Chrome、Firefox 向頁面本身傳送請求; Opera 不執行任何操作。

頁面渲染優化

Webkit 渲染引擎流程:

  • 處理 HTML 並構建 DOM 樹
  • 處理 CSS 構建 CSS 規則樹(CSSOM)
  • DOM Tree 和 CSSOM Tree 合成一棵渲染樹 Render Tree。
  • 根據渲染樹來佈局,計算每個節點的位置
  • 呼叫 GPU 繪製,合成圖層,顯示在螢幕上

避免css阻塞

css影響renderTree的構建,會阻塞頁面的渲染,因此應該儘早(將 CSS 放在 head 標籤裡)和儘快(啟用 CDN 實現靜態資源載入速度的優化)的將css資源載入

降低css選擇器的複雜度

瀏覽器讀取選擇器,遵循的原則是從選擇器的右邊到左邊讀取。 - 減少巢狀:最多不要超過三層,並且後代選擇器的開銷較高,慎重使用 - 避免使用萬用字元,對用到的元素進行匹配即可 - 利用繼承,避免重複匹配和定義 - 正確使用類選擇器和id選擇器

避免使用CSS 表示式

css 表示式會被頻繁地計算。

避免js阻塞

js可以修改CSSOM和DOM,因此js會阻塞頁面的解析和渲染,並且會等待css資源的載入。也就是說js會搶走渲染引擎的控制權。所以我們需要給js資源新增defer或者async,延遲js指令碼的執行。

使用外鏈式的js和css

在現實環境中使用外部檔案通常會產生較快的頁面,因為 JavaScript 和 CSS 有機會被瀏覽器快取起來。對於內聯的情況,由於 HTML 文件通常不會被配置為可以進行快取的,所以每次請求 HTML 文件都要下載 JavaScript 和 CSS。所以,如果 JavaScript 和 CSS 在外部檔案中,瀏覽器可以快取它們,HTML 文件的大小會被減少而不必增加 HTTP 請求數量。

使用字型圖示 iconfont 代替圖片圖示

  • 圖片會增加網路請求次數,從而拖慢頁面載入時間
  • iconfont可以很好的縮放並且不會新增額外的請求

首屏載入優化

  • 使用骨架屏或者動畫優化使用者體驗
  • 資源按需載入,首頁不需要的資源延遲載入

減少重繪和迴流

  • 增加多個節點使用documentFragment:不是真實dom的部分,不會引起重繪和迴流

  • 用 translate 代替 top ,因為 top 會觸發迴流,但是translate不會。所以translate會比top節省了一個layout的時間

  • 使用 visibility 替換 display: none ,因為前者只會引起重繪,後者會引發迴流(改變了佈局);opacity 代替 visiabilityvisiability會觸發重繪(paint),但opacity不會。

  • 把 DOM 離線後修改,比如:先把 DOM 給 display:none (有一次 Reflow),然後你修改 100 次,然後再把它顯示出來

  • 不要把 DOM 結點的屬性值放在一個迴圈裡當成迴圈裡的變數

    for (let i = 0; i < 1000; i++) { // 獲取 offsetTop 會導致迴流,因為需要去獲取正確的值 console.log(document.querySelector('.test').style.offsetTop) }

  • 儘量少用table佈局,table佈局的話,每次有單元格佈局改變,都會進行整個tabel迴流重繪;

  • 最好別頻繁去操作DOM節點,最好把需要操作的樣式,提前寫成class,之後需要修改。只需要修改一次,需要修改的時候,直接修改className,做成一次性更新多條css DOM屬性,一次迴流重繪總比多次迴流重繪要付出的成本低得多;

  • 動畫實現的速度的選擇,動畫速度越快,迴流次數越多,也可以選擇使用 requestAnimationFrame

  • 每次訪問DOM的偏移量屬性的時候,例如獲取一個元素的scrollTop、scrollLeft、scrollWidth、offsetTop、offsetLeft、offsetWidth、offsetHeight之類的屬性,瀏覽器為了保證值的正確也會迴流取得最新的值,所以如果你要多次操作,最取完做個快取。更加不要for迴圈中訪問DOM偏移量屬性,而且使用的時候,最好定義一個變數,把要需要的值賦值進去,進行值快取,把迴流重繪的次數減少;

  • 將頻繁執行的動畫變為圖層,圖層能夠阻止該節點回流影響別的元素。比如對於 video 標籤,瀏覽器會自動將該節點變為圖層。

JS中的效能優化

使用事件委託

防抖和節流

儘量不要使用JS動畫

css3動畫canvas動畫都比JS動畫效能好

多執行緒

複雜的計算開啟webWorker進行計算,避免頁面假死

計算結果快取

減少運算次數,比如vue中的computed

圖片的優化

雪碧圖

藉助減少http請求次數來進行優化

圖片懶載入

在圖片即將進入可視區域的時候進行載入(判斷圖片進入可視區域請參考這裡

使用CSS3代替圖片

有很多圖片使用 CSS 效果(漸變、陰影等)就能畫出來,這種情況選擇 CSS3 效果更好

圖片壓縮

壓縮方法有兩種,一是通過線上網站進行壓縮,二是通過 webpack 外掛 image-webpack-loader。它是基於 imagemin 這個 Node 庫來實現圖片壓縮的。

使用漸進式jpeg

使用漸進式jpeg,會提高使用者體驗 參考文章

使用 webp 格式的圖片

webp 是一種新的圖片檔案格式,它提供了有失真壓縮和無失真壓縮兩種方式。在相同圖片質量下,webp 的體積比 png 和 jpg 更小。

webpack打包優化

縮小loader 匹配範圍

  • 優化loader配置
  • test、include、exclude三個配置項來縮⼩loader的處理範圍
  • 推薦include

js include: path.resolve(__dirname, "./src"),

resolve.modules

resolve.modules用於配置webpack去哪些目錄下尋找第三方模組,預設是 node_modules。

尋找第三方,預設是在當前專案目錄下的node_modules裡面去找,如果沒有找到,就會去上一級目錄../node_modules找,再沒有會去../../node_modules中找,以此類推,和Node.js的模組尋找機制很類似。

如果我們的第三⽅模組都安裝在了項⽬根⽬錄下,就可以直接指明這個路徑。

```js module.exports={ resolve:{ modules: [path.resolve(__dirname, "./node_modules")] } }

```

resolve.extensions

resolve.extensions在導⼊語句沒帶⽂件字尾時,webpack會⾃動帶上字尾後,去嘗試查詢⽂件是否存在。

  • 字尾嘗試列表儘量的⼩
  • 導⼊語句儘量的帶上字尾。

如果想優化到極致的話,不建議用extensionx, 因為它會消耗一些效能。雖然它可以帶來一些便利。

抽離css

藉助mini-css-extract-plugin:本外掛會將 CSS 提取到單獨的檔案中,為每個包含 CSS 的 JS 檔案建立一個 CSS 檔案,並且支援 CSS 和 SourceMaps 的按需載入。。

js const MiniCssExtractPlugin = require("mini-css-extract-plugin"); { test: /\.less$/, use: [ // "style-loader", // 不再需要style-loader,⽤MiniCssExtractPlugin.loader代替 MiniCssExtractPlugin.loader, "css-loader", // 編譯css "postcss-loader", "less-loader" // 編譯less ] }, plugins: [ new MiniCssExtractPlugin({ filename: "css/[name]_[contenthash:6].css", chunkFilename: "[id].css" }) ]

程式碼壓縮

JS程式碼壓縮

mode:production,使用的是terser-webpack-plugin

module.exports = {    // ...    optimization: {        minimize: true,        minimizer: [            new TerserPlugin({}),       ]   } }

CSS程式碼壓縮

css-minimizer-webpack-plugin

module.exports = {    // ...    optimization: {        minimize: true,        minimizer: [            new CssMinimizerPlugin({})       ]   } }

Html檔案程式碼壓縮

module.exports = {    ...    plugin:[        new HtmlwebpackPlugin({            ...            minify:{                minifyCSS:false, // 是否壓縮css                collapseWhitespace:false, // 是否摺疊空格                removeComments:true // 是否移除註釋           }       })   ] }

設定了minify,實際會使用另一個外掛html-minifier-terser

檔案大小壓縮

對檔案的大小進行壓縮,減少http傳輸過程中寬頻的損耗

npm install compression-webpack-plugin -D

new ComepressionPlugin({    test:/.(css|js)$/,  // 哪些檔案需要壓縮    threshold:500, // 設定檔案多大開始壓縮    minRatio:0.7, // 至少壓縮的比例    algorithm:"gzip", // 採用的壓縮演算法 })

圖片壓縮

一般來說在打包之後,一些圖片檔案的大小是遠遠要比 js 或者 css 檔案要來的大,所以圖片壓縮較為重要

配置方法如下:

module: {  rules: [   {      test: /.(png|jpg|gif)$/,      use: [       {          loader: 'file-loader',          options: {            name: '[name]_[hash].[ext]',            outputPath: 'images/',         }       },       {          loader: 'image-webpack-loader',          options: {            // 壓縮 jpeg 的配置            mozjpeg: {              progressive: true,              quality: 65           },            // 使用 imagemin**-optipng 壓縮 png,enable: false 為關閉            optipng: {              enabled: false,           },            // 使用 imagemin-pngquant 壓縮 png            pngquant: {              quality: '65-90',              speed: 4           },            // 壓縮 gif 的配置            gifsicle: {              interlaced: false,           },            // 開啟 webp,會把 jpg 和 png 圖片壓縮為 webp 格式            webp: {              quality: 75           }         }       }     ]   }, ] }

Tree shaking 去除死程式碼

Tree Shaking 是一個術語,在計算機中表示消除死程式碼,依賴於ES Module的靜態語法分析(不執行任何的程式碼,可以明確知道模組的依賴關係)

webpack實現Trss shaking有兩種不同的方案:

  • usedExports:通過標記某些函式是否被使用,之後通過Terser來進行優化的
  • sideEffects:跳過整個模組/檔案,直接檢視該檔案是否有副作用

兩種不同的配置方案, 有不同的效果

usedExports

配置方法也很簡單,只需要將usedExports設為true

module.exports = {    ...    optimization:{        usedExports   } }

使用之後,沒被用上的程式碼在webpack打包中會加入unused harmony export mul註釋,用來告知 Terser 在優化時,可以刪除掉這段程式碼

sideEffects

sideEffects用於告知webpack compiler哪些模組時有副作用,配置方法是在package.json中設定sideEffects屬性

如果sideEffects設定為false,就是告知webpack可以安全的刪除未用到的exports

如果有些檔案需要保留,可以設定為陣列的形式

"sideEffecis":[    "./src/util/format.js",    "*.css" // 所有的css檔案 ]

上述都是關於javascripttree shakingcss同樣也能夠實現tree shaking

css tree shaking

css進行tree shaking優化可以安裝PurgeCss外掛

npm install purgecss-plugin-webpack -D

const PurgeCssPlugin = require('purgecss-webpack-plugin') module.exports = {    ...    plugins:[        new PurgeCssPlugin({            path:glob.sync(`${path.resolve('./src')}/**/*`), {nodir:true}// src裡面的所有檔案            satelist:function(){                return {                    standard:["html"]               }           }       })   ] }

  • paths:表示要檢測哪些目錄下的內容需要被分析,配合使用glob
  • 預設情況下,Purgecss會將我們的html標籤的樣式移除掉,如果我們希望保留,可以新增一個safelist的屬性

babel-plugin-transform-runtime減少ES6轉化ES5的冗餘

Babel 外掛會在將 ES6 程式碼轉換成 ES5 程式碼時會注入一些輔助函式。在預設情況下, Babel 會在每個輸出檔案中內嵌這些依賴的輔助函式程式碼,如果多個原始碼檔案都依賴這些輔助函式,那麼這些輔助函式的程式碼將會出現很多次,造成程式碼冗餘。為了不讓這些輔助函式的程式碼重複出現,可以在依賴它們時通過 require('babel-runtime/helpers/createClass')的方式匯入,這樣就能做到只讓它們出現一次。babel-plugin-transform-runtime 外掛就是用來實現這個作用的,將相關輔助函式進行替換成匯入語句,從而減小 babel 編譯出來的程式碼的檔案大小。

程式碼分離

將程式碼分離到不同的bundle中,之後我們可以按需載入,或者並行載入這些檔案

預設情況下,所有的JavaScript程式碼(業務程式碼、第三方依賴、暫時沒有用到的模組)在首頁全部都載入,就會影響首頁的載入速度

程式碼分離可以分出更小的bundle,以及控制資源載入優先順序,提供程式碼的載入效能

這裡通過splitChunksPlugin來實現,該外掛webpack已經預設安裝和整合,只需要配置即可

預設配置中,chunks僅僅針對於非同步(async)請求,我們可以設定為initial或者all

module.exports = {    ...    optimization:{        splitChunks:{            chunks:"all"       }   } }

splitChunks主要屬性有如下:

  • Chunks,對同步程式碼還是非同步程式碼進行處理
  • minSize: 拆分包的大小, 至少為minSize,如何包的大小不超過minSize,這個包不會拆分
  • maxSize: 將大於maxSize的包,拆分為不小於minSize的包
  • minChunks:被引入的次數,預設是1

多執行緒打包提升打包速度

vue

  1. v-for新增key
  2. 路由懶載入
  3. 第三方外掛按需引入
  4. 合理使用computed和watch
  5. v-for的同時避免使用v-if
  6. destory時銷燬事件:比如addEventListener新增的事件、setTimeout、setInterval、bus.$on繫結的監聽事件等

react

  1. map迴圈展示新增key
  2. 路由懶載入
  3. 第三方外掛按需引入
  4. 使用scu,memo或者pureComponent避免不必要的渲染
  5. 合理使用useMemo、memo、useCallback

    他們三個的應用場景都是快取結果,當依賴值沒有改變時避免不必要的計算或者渲染。

    • useCallback 是針對函式進行“記憶”的,當它依賴項沒有發生改變時,那麼該函式的引用並不會隨著元件的重新整理而被重新賦值。當我們覺得一個函式不需要隨著元件的更新而更新引用地址的時候,我們就可以使用 useCallback 去修飾它。
    • React.memo 是對元件進行 “記憶”,當它接收的 props 沒有發生改變的時候,那麼它將返回上次渲染的結果,不會重新執行函式返回新的渲染結果。
    • React.useMemo是針對 值計算 的一種“記憶“,當依賴項沒有發生改變時,那麼無需再去計算,直接使用之前的值,對於元件而言,這帶來的一個好處就是,可以減少一些計算,避免一些多餘的渲染。當我們遇到一些資料需要在元件內部進行計算的時候,可以考慮一下 React.useMemo

參考連結

https://learnku.com/docs/f2e-performance-rules

https://segmentfault.com/a/1190000041753539

https://juejin.cn/post/6951297954770583565