對(duì)于前端性能優(yōu)化的理解與實(shí)踐

先從一道題目說(shuō)起

從輸入 URL 到頁(yè)面加載完成,發(fā)生了什么?

  • 站在性能優(yōu)化的角度;我們可以分為5個(gè)過(guò)程;
  1. DNS 解析
  2. TCP 連接
  3. HTTP 請(qǐng)求拋出
  4. 服務(wù)端處理請(qǐng)求,HTTP 響應(yīng)返回
  5. 瀏覽器拿到響應(yīng)數(shù)據(jù),解析響應(yīng)內(nèi)容,把解析的結(jié)果展示給用戶
  • 我們從這五個(gè)過(guò)程個(gè)個(gè)擊破;
    dns解析花時(shí)間,tcp連接慢;這些需要我們的服務(wù)端解決;
    那么我們的前端工程師在HTTP請(qǐng)求或者瀏覽器端能做一些什么優(yōu)化呢?
    http方面前端可以減少請(qǐng)求次數(shù),壓縮體積;
    瀏覽器前端可以做的事情比較多了,例如資源加載優(yōu)化、服務(wù)端渲染、瀏覽器緩存機(jī)制的利用、DOM 樹的構(gòu)建、網(wǎng)頁(yè)排版和渲染過(guò)程、回流與重繪的考量、DOM 操作的合理規(guī)避


    1669f5358f63c0f8 (1).png

網(wǎng)絡(luò)層面(http請(qǐng)求優(yōu)化and減少網(wǎng)絡(luò)請(qǐng)求)

webpack打包體積優(yōu)化
  • webpack-bundle-analyzer 是一款包可視化工具,可以找出體積大的模塊;
  • 刪除冗余代碼 webpack3可以使用UglifyJsPlugin ;webpack4已經(jīng)自帶了,只需要配置下;
  • 按需加載 vue項(xiàng)目可以用require.ensure來(lái)實(shí)現(xiàn)
  • gzip 本來(lái)是服務(wù)端的工作,webpack也有g(shù)zip可以幫助服務(wù)端減輕壓力
圖片
  • JPEG/JPG:有損壓縮、體積小、加載快、不支持透明;龐大的圖片用jpg
  • PNG-8 與 PNG-24:無(wú)損壓縮、質(zhì)量高、體積大、支持透明 像logo類等比較突出的最好用png
  • SVG (字體圖標(biāo)):文本文件、體積小、不失真、兼容性好
  • Base64 :文本文件、依賴編碼、小圖標(biāo)解決方案,Base64 是作為雪碧圖的補(bǔ)充而存在的;Base64 編碼后,圖片大小會(huì)膨脹為原文件的 4/3;在傳輸非常小的圖片的時(shí)候,Base64 帶來(lái)的文件體積膨脹、以及瀏覽器解析 Base64 的時(shí)間開銷,與它節(jié)省掉的 HTTP 請(qǐng)求開銷相比,可以忽略不計(jì)
  • CSS Sprites(精靈圖/雪碧圖):小圖標(biāo)解決方案
  • WebP :與 PNG 相比,WebP 無(wú)損圖像的尺寸縮小了 26%。限制WebP發(fā)展的是瀏覽器兼容問(wèn)題;
瀏覽器緩存
  • Memory Cache 內(nèi)存緩存是快的,也是“短命”的。
  • Service Worker Cache 幫我們實(shí)現(xiàn)離線緩存、消息推送和網(wǎng)絡(luò)代理等功能,但必需以https 協(xié)議為前提
  • Push Cache HTTP2存在,Push Cache 是緩存的最后一道防線,會(huì)話階段的緩存;
  • HTTP Cache (主要、最具有代表性的)

HTTP緩存分為強(qiáng)緩存和協(xié)商緩存
強(qiáng)緩存:Expires 和 Cache-Control (http1.1新增)兩個(gè)字段來(lái)控制
expires 能做的事情,Cache-Control 都能做;expires 完成不了的事情,Cache-Control 也能做。因此,Cache-Control 可以視作是 expires 的完全替代方案。Cache-Control 相對(duì)于 expires 更加準(zhǔn)確,它的優(yōu)先級(jí)也更高。當(dāng) Cache-Control 與 expires 同時(shí)出現(xiàn)時(shí),我們以 Cache-Control 為準(zhǔn)。

public 與 private 是針對(duì)資源是否能夠被代理服務(wù)緩存而存在的一組對(duì)立概念。

no-cache 繞開了瀏覽器:我們?yōu)橘Y源設(shè)置了 no-cache 后,每一次發(fā)起請(qǐng)求都不會(huì)再去詢問(wèn)瀏覽器的緩存情況,而是直接向服務(wù)端去確認(rèn)該資源是否過(guò)期;no-store 比較絕情,顧名思義就是不使用任何緩存策略。在 no-cache 的基礎(chǔ)上,它連服務(wù)端的緩存確認(rèn)也繞開了,只允許你直接向服務(wù)端發(fā)送請(qǐng)求、并下載完整的響應(yīng)。

協(xié)商緩存:協(xié)商緩存機(jī)制下,瀏覽器需要向服務(wù)器去詢問(wèn)緩存的相關(guān)信息,進(jìn)而判斷是重新發(fā)起請(qǐng)求、下載完整的響應(yīng),還是從本地獲取緩存的資源。資源會(huì)被重定向到瀏覽器緩存,這種情況下網(wǎng)絡(luò)請(qǐng)求對(duì)應(yīng)的狀態(tài)碼是 304

165f701820fafcf8 (1).png

當(dāng)我們的資源內(nèi)容不可復(fù)用時(shí),直接為 Cache-Control 設(shè)置 no-store,拒絕一切形式的緩存;否則考慮是否每次都需要向服務(wù)器進(jìn)行緩存有效確認(rèn),如果需要,那么設(shè) Cache-Control 的值為 no-cache;否則考慮該資源是否可以被代理服務(wù)器緩存,根據(jù)其結(jié)果決定是設(shè)置為 private 還是 public;然后考慮該資源的過(guò)期時(shí)間,設(shè)置對(duì)應(yīng)的 max-age 和 s-maxage 值;最后,配置協(xié)商緩存需要用到的 Etag、Last-Modified 等參數(shù)。

本地存儲(chǔ)

  • cookie 只能存儲(chǔ)4KB ,緊跟域名的
  • Web Storage Local Storage和Session Storage 這兩個(gè)對(duì)前端來(lái)說(shuō)很熟悉了;
  • IndexDB 運(yùn)行在瀏覽器上的非關(guān)系型數(shù)據(jù)庫(kù);IndexDB 是沒(méi)有存儲(chǔ)上限的(一般來(lái)說(shuō)不會(huì)小于 250M)

cdn

  • 內(nèi)容分發(fā)網(wǎng)絡(luò)
  • 緩存和回源。

緩存”就是說(shuō)我們把資源 copy 一份到 CDN 服務(wù)器上這個(gè)過(guò)程,“回源”就是說(shuō) CDN 發(fā)現(xiàn)自己沒(méi)有這個(gè)資源(一般是緩存的數(shù)據(jù)過(guò)期了),轉(zhuǎn)頭向根服務(wù)器(指業(yè)務(wù)服務(wù)器)或者它的上層服務(wù)器去要這個(gè)資源的過(guò)程。

  • CDN 往往被用來(lái)存放靜態(tài)資源

所謂“靜態(tài)資源”,就是像 JS、CSS、圖片等不需要業(yè)務(wù)服務(wù)器進(jìn)行計(jì)算即得的資源。而“動(dòng)態(tài)資源”,顧名思義是需要后端實(shí)時(shí)動(dòng)態(tài)生成的資源,較為常見的就是 JSP、ASP 或者依賴服務(wù)端渲染得到的 HTML 頁(yè)面。

  • 性能優(yōu)化方面的應(yīng)用

同一個(gè)域名下的請(qǐng)求會(huì)不分青紅皂白地?cái)y帶 Cookie,而靜態(tài)資源往往并不需要 Cookie 攜帶什么認(rèn)證信息。把靜態(tài)資源和主頁(yè)面置于不同的域名下,完美地避免了不必要的 Cookie 的出現(xiàn)!

服務(wù)端渲染(SSR)

  • 客戶端渲染:需要把js文件跑完,生成對(duì)應(yīng)的dom樹;
  • 服務(wù)端渲染:直接拿到服務(wù)端放回的html就可以呈現(xiàn)在用戶面前
  • 質(zhì)上是本該瀏覽器做的事情,分擔(dān)給服務(wù)器去做。這樣當(dāng)資源抵達(dá)瀏覽器時(shí),它呈現(xiàn)的速度就快了。

CSSOM,JS的優(yōu)化

瀏覽器背后的運(yùn)行機(jī)制
QQ截圖20181123134723.png
165f7018d20fafcf8 (1).png
CSS 選擇符是從右到左進(jìn)行匹配的

避免使用通配符,只對(duì)需要用到的元素進(jìn)行選擇。
關(guān)注可以通過(guò)繼承實(shí)現(xiàn)的屬性,避免重復(fù)匹配重復(fù)定義。
少用標(biāo)簽選擇器。如果可以,用類選擇器替代
不要畫蛇添足,id 和 class 選擇器不應(yīng)該被多余的標(biāo)簽選擇器拖后腿。
減少嵌套。后代選擇器的開銷是最高的,因此我們應(yīng)該盡量將選擇器的深度降到最低

JS的加載方式

正常模式 <script src="index.js"></script>
async 模式 <script async src="index.js"></script>
defer 模式 <script defer src="index.js"></script>
defer 模式下,JS 的加載是異步的,執(zhí)行是被推遲的。等整個(gè)文檔解析完成、DOMContentLoaded 事件即將被觸發(fā)時(shí),被標(biāo)記了 defer 的 JS 文件才會(huì)開始依次執(zhí)行。
從應(yīng)用的角度來(lái)說(shuō),一般當(dāng)我們的腳本與 DOM 元素和其它腳本之間的依賴關(guān)系不強(qiáng)時(shí),我們會(huì)選用 async;當(dāng)腳本依賴于 DOM 元素和其它腳本的執(zhí)行結(jié)果時(shí),我們會(huì)選用 defer。

DOM的優(yōu)化

  • 回流和重繪

回流:當(dāng)我們對(duì) DOM 的修改引發(fā)了 DOM 幾何尺寸的變化(比如修改元素的寬、高或隱藏元素等)時(shí),瀏覽器需要重新計(jì)算元素的幾何屬性(其他元素的幾何屬性和位置也會(huì)因此受到影響),然后再將計(jì)算的結(jié)果繪制出來(lái)。這個(gè)過(guò)程就是回流(也叫重排)。
重繪:當(dāng)我們對(duì) DOM 的修改導(dǎo)致了樣式的變化、卻并未影響其幾何屬性(比如修改了顏色或背景色)時(shí),瀏覽器不需重新計(jì)算元素的幾何屬性、直接為該元素繪制新的樣式(跳過(guò)了上圖所示的回流環(huán)節(jié))。這個(gè)過(guò)程叫做重繪。
由此我們可以看出,重繪不一定導(dǎo)致回流,回流一定會(huì)導(dǎo)致重繪。我們對(duì)dom的優(yōu)化主要在于減少DOM操作;

  • 回流的“導(dǎo)火索”

改變 DOM 元素的幾何屬性
改變 DOM 樹的結(jié)構(gòu)
獲取一些特定屬性的值:offsetTop、offsetLeft、 offsetWidth、offsetHeight、scrollTop、scrollLeft、scrollWidth、scrollHeight、clientTop、clientLeft、clientWidth、clientHeight...

  • 規(guī)避回流與重繪

js先用變量保存好要計(jì)算的值,最終再設(shè)置dom
避免逐條改變樣式,使用類名去合并樣式
將 DOM “離線”,先設(shè)置display:none;中間操作,后面再設(shè)置display:block;

  • 瀏覽器Flush 隊(duì)列
  • DOM Fragment 需要了解一下

本質(zhì)上是作為脫離了真實(shí) DOM 樹的容器出現(xiàn),用于緩存批量化的 DOM 操作

Event Loop 與異步更新策略

  • macro(洪任務(wù)): setTimeout、setInterval、 setImmediate、script(整體代碼)、 I/O 操作、UI 渲染等。
  • micro-task(微任務(wù)): process.nextTick、Promise、MutationObserver

當(dāng)我們需要在異步任務(wù)中實(shí)現(xiàn) DOM 修改時(shí),把它包裝成 micro 任務(wù)是相對(duì)明智的選擇。

  • Vue狀態(tài)更新手法:nextTick
export function nextTick (cb?: Function, ctx?: Object) {
  let _resolve
  callbacks.push(() => {
    if (cb) {
      try {
        cb.call(ctx)
      } catch (e) {
        handleError(e, ctx, 'nextTick')
      }
    } else if (_resolve) {
      _resolve(ctx)
    }
  })
  // 檢查上一個(gè)異步任務(wù)隊(duì)列(即名為callbacks的任務(wù)數(shù)組)是否派發(fā)和執(zhí)行完畢了。pending此處相當(dāng)于一個(gè)鎖
  if (!pending) {
    // 若上一個(gè)異步任務(wù)隊(duì)列已經(jīng)執(zhí)行完畢,則將pending設(shè)定為true(把鎖鎖上)
    pending = true
    // 是否要求一定要派發(fā)為macro任務(wù)
    if (useMacroTask) {
      macroTimerFunc()
    } else {
      // 如果不說(shuō)明一定要macro 你們就全都是micro
      microTimerFunc()
    }
  }
  // $flow-disable-line
  if (!cb && typeof Promise !== 'undefined') {
    return new Promise(resolve => {
      _resolve = resolve
    })
  }
}

實(shí)際上也是運(yùn)用了promise

lazy-load(懶加載)

在懶加載的實(shí)現(xiàn)中,有兩個(gè)關(guān)鍵的數(shù)值:一個(gè)是當(dāng)前可視區(qū)域的高度,另一個(gè)是元素距離可視區(qū)域頂部的高度。

事件的節(jié)流(throttle)與防抖(debounce)

像scroll,resize,keyup等事件頻繁觸發(fā)會(huì)引發(fā)頁(yè)面的抖動(dòng)甚至卡頓
節(jié)流”與“防抖”是以閉包的形式來(lái)實(shí)現(xiàn)的;
它們通過(guò)對(duì)事件對(duì)應(yīng)的回調(diào)函數(shù)進(jìn)行包裹、以自由變量的形式緩存時(shí)間信息,最后用 setTimeout 來(lái)控制事件的觸發(fā)頻率。

  • 節(jié)流和防抖結(jié)合體
// fn是我們需要包裝的事件回調(diào), delay是時(shí)間間隔的閾值
function throttle(fn, delay) {
  // last為上一次觸發(fā)回調(diào)的時(shí)間, timer是定時(shí)器
  let last = 0, timer = null
  // 將throttle處理結(jié)果當(dāng)作函數(shù)返回
  
  return function () { 
    // 保留調(diào)用時(shí)的this上下文
    let context = this
    // 保留調(diào)用時(shí)傳入的參數(shù)
    let args = arguments
    // 記錄本次觸發(fā)回調(diào)的時(shí)間
    let now = +new Date()
    
    // 判斷上次觸發(fā)的時(shí)間和本次觸發(fā)的時(shí)間差是否小于時(shí)間間隔的閾值
    if (now - last < delay) {
    // 如果時(shí)間間隔小于我們?cè)O(shè)定的時(shí)間間隔閾值,則為本次觸發(fā)操作設(shè)立一個(gè)新的定時(shí)器
       clearTimeout(timer)
       timer = setTimeout(function () {
          last = now
          fn.apply(context, args)
        }, delay)
    } else {
        // 如果時(shí)間間隔超出了我們?cè)O(shè)定的時(shí)間間隔閾值,那就不等了,無(wú)論如何要反饋給用戶一次響應(yīng)
        last = now
        fn.apply(context, args)
    }
  }
}

// 用新的throttle包裝scroll的回調(diào)
document.addEventListener('scroll', throttle(() => console.log('觸發(fā)了滾動(dòng)事件'), 1000))...

谷歌瀏覽器自帶的Performance以及瀏覽器插件LightHouse可以監(jiān)測(cè)網(wǎng)站性能;

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容