前端性能優(yōu)化總結(jié)(一)-js、css優(yōu)化

前言

移動互聯(lián)網(wǎng)時代帜平,用戶對于網(wǎng)頁的打開速度要求越來越高。首屏作為直面用戶的第一屏裆甩,其重要性不言而喻。優(yōu)化用戶體驗(yàn)更是我們前端開發(fā)非常需要 focus 的東西之一嗤栓。

從用戶的角度而言,當(dāng)打開一個網(wǎng)頁抛腕,往往關(guān)心的是從輸入完網(wǎng)頁地址后到最后展現(xiàn)完整頁面這個過程需要的時間,這個時間越短,用戶體驗(yàn)越好摔敛。所以作為網(wǎng)頁的開發(fā)者,就從輸入url到頁面渲染呈現(xiàn)這個過程中去提升網(wǎng)頁的性能马昙。

所以輸入URL后發(fā)生了什么呢?在瀏覽器中輸入url會經(jīng)歷域名解析行楞、建立TCP連接、發(fā)送http請求子房、資源解析等步驟就轧。

http緩存優(yōu)化是網(wǎng)頁性能優(yōu)化的重要一環(huán)田度,這一部分我會在后續(xù)筆記中做一個詳細(xì)總結(jié)妒御,所以本文暫不多做詳細(xì)整理镇饺。本文主要從網(wǎng)頁渲染過程乎莉、網(wǎng)頁交互以及Vue應(yīng)用優(yōu)化三個角度對性能優(yōu)化做一個小結(jié)奸笤。

一、頁面加載及渲染過程優(yōu)化

瀏覽器渲染流程

首先談?wù)勀玫椒?wù)端資源后瀏覽器渲染的流程:


image.png
  1. 解析 HTML 文件监右,構(gòu)建 DOM 樹,同時瀏覽器主進(jìn)程負(fù)責(zé)下載 CSS 文件
  2. CSS 文件下載完成秸侣,解析 CSS 文件成樹形的數(shù)據(jù)結(jié)構(gòu),然后結(jié)合 DOM 樹合并成 RenderObject 樹
  3. 布局 RenderObject 樹 (Layout/reflow)味榛,負(fù)責(zé) RenderObject 樹中的元素的尺寸,位置等計算
  4. 繪制 RenderObject 樹 (paint)搏色,繪制頁面的像素信息
  5. 瀏覽器主進(jìn)程將默認(rèn)的圖層和復(fù)合圖層交給 GPU 進(jìn)程,GPU 進(jìn)程再將各個圖層合成(composite)频轿,最后顯示出頁面
CRP(關(guān)鍵渲染路徑Critical Rendering Path)優(yōu)化

關(guān)鍵渲染路徑是瀏覽器將 HTML、CSS航邢、JavaScript 轉(zhuǎn)換為在屏幕上呈現(xiàn)的像素內(nèi)容所經(jīng)歷的一系列步驟。也就是我們剛剛提到的的的瀏覽器渲染流程膳殷。

為盡快完成首次渲染,我們需要最大限度減小以下三種可變因素:

  • 關(guān)鍵資源的數(shù)量: 可能阻止網(wǎng)頁首次渲染的資源赚窃。
  • 關(guān)鍵路徑長度: 獲取所有關(guān)鍵資源所需的往返次數(shù)或總時間。
  • 關(guān)鍵字節(jié): 實(shí)現(xiàn)網(wǎng)頁首次渲染所需的總字節(jié)數(shù)勒极,等同于所有關(guān)鍵資源傳送文件大小的總和。
優(yōu)化 DOM
  • 刪除不必要的代碼和注釋包括空格辱匿,盡量做到最小化文件炫彩。
  • 可以利用 GZIP 壓縮文件散休。
  • 結(jié)合 HTTP 緩存文件媒楼。
優(yōu)化 CSSOM

首先戚丸,DOM 和 CSSOM 通常是并行構(gòu)建的,所以 CSS 加載不會阻塞 DOM 的解析限府。

然而,由于 Render Tree 是依賴于 DOM Tree 和 CSSOM Tree 的胁勺,
所以他必須等待到 CSSOM Tree 構(gòu)建完成,也就是 CSS 資源加載完成(或者 CSS 資源加載失敗)后署穗,才能開始渲染。因此案疲,CSS 加載會阻塞 Dom 的渲染。

由此可見褐啡,對于 CSSOM 縮小、壓縮以及緩存同樣重要备畦,我們可以從這方面考慮去優(yōu)化。

  • 減少關(guān)鍵 CSS 元素數(shù)量
  • 當(dāng)我們聲明樣式表時懂盐,請密切關(guān)注媒體查詢的類型,它們極大地影響了 CRP 的性能 莉恼。
優(yōu)化 JavaScript

當(dāng)瀏覽器遇到 script 標(biāo)記時,會阻止解析器繼續(xù)操作类垫,直到 CSSOM 構(gòu)建完畢琅坡,JavaScript 才會運(yùn)行并繼續(xù)完成 DOM 構(gòu)建過程。

  • async: 當(dāng)我們在 script 標(biāo)記添加 async 屬性以后榆俺,瀏覽器遇到這個 script 標(biāo)記時會繼續(xù)解析 DOM坞淮,同時腳本也不會被 CSSOM 阻止,即不會阻止 CRP陪捷。
  • defer: 與 async 的區(qū)別在于,腳本需要等到文檔解析后( DOMContentLoaded 事件前)執(zhí)>行市袖,而 async 允許腳本在文檔解析時位于后臺運(yùn)行(兩者下載的過程不會阻塞 DOM,但執(zhí)行會)苍碟。
  • 當(dāng)我們的腳本不會修改 DOM 或 CSSOM 時,推薦使用 async 微峰。
  • 預(yù)加載 —— preload & prefetch 。
  • DNS 預(yù)解析 —— dns-prefetch 蜓肆。
小結(jié)
  • 分析并用 關(guān)鍵資源數(shù) 關(guān)鍵字節(jié)數(shù) 關(guān)鍵路徑長度 來描述我們的 CRP 。
  • 最小化關(guān)鍵資源數(shù): 消除它們(內(nèi)聯(lián))症概、推遲它們的下載(defer)或者使它們異步解析(async)等等 。
  • 優(yōu)化關(guān)鍵字節(jié)數(shù)(縮小穴豫、壓縮)來減少下載時間 。
  • 優(yōu)化加載剩余關(guān)鍵資源的順序: 讓關(guān)鍵資源(CSS)盡早下載以減少 CRP 長度 精肃。

瀏覽器重繪(Repaint)和回流(Reflow)

回流必將引起重繪,重繪不一定會引起回流司抱。

重繪(Repaint)

當(dāng)頁面中元素樣式的改變并不影響它在文檔流中的位置時(例如:color、background-color习柠、visibility 等),瀏覽器會將新樣式賦予給元素并重新繪制它资溃,這個過程稱為重繪。

回流(Reflow)
當(dāng) Render Tree 中部分或全部元素的尺寸溶锭、結(jié)構(gòu)、或某些屬性發(fā)生改變時趴捅,瀏覽器重新渲染部分或全部文檔的過程稱為回流垫毙。

會導(dǎo)致回流的操作:
* 頁面首次渲染
* 瀏覽器窗口大小發(fā)生改變
* 元素尺寸或位置發(fā)生改變元素內(nèi)容變化(文字?jǐn)?shù)量或圖片大小等等)
* 元素字體大小變化
* 添加或者刪除可見的 DOM 元素
* 激活 CSS 偽類(例如:hover)
* 查詢某些屬性或調(diào)用某些方法
* 一些常用且會導(dǎo)致回流的屬性和方法
clientWidth拱绑、clientHeight、clientTop猎拨、clientLeftoffsetWidth、offsetHeight迟几、offsetTop、offsetLeftscrollWidth类腮、scrollHeight、scrollTop蚜枢、scrollLeftscrollIntoView()、scrollIntoViewIfNeeded()厂抽、getComputedStyle()、
getBoundingClientRect()筷凤、scrollTo()

性能影響

回流比重繪的代價要更高。

有時即使僅僅回流一個單一的元素藐守,它的父元素以及任何跟隨它的元素也會產(chǎn)生回流。現(xiàn)代瀏覽器會對頻繁的回流或重繪操作進(jìn)行優(yōu)化:瀏覽器會維護(hù)一個隊(duì)列卢厂,把所有引起回流和重繪的操作放入隊(duì)列中,如果隊(duì)列中的任務(wù)數(shù)量或者時間間隔達(dá)到一個閾值的慎恒,瀏覽器就會將隊(duì)列清空,進(jìn)行一次批處理融柬,這樣可以把多次回流和重繪變成一次。
當(dāng)你訪問以下屬性或方法時粒氧,瀏覽器會立刻清空隊(duì)列:

clientWidth、clientHeight、clientTop、clientLeft
offsetWidth门怪、offsetHeight骡澈、offsetTop掷空、offsetLeft
scrollWidth、scrollHeight坦弟、scrollTop、scrollLeft
width酿傍、height
getComputedStyle()
getBoundingClientRect()

因?yàn)殛?duì)列中可能會有影響到這些屬性或方法返回值的操作,即使你希望獲取的信息與隊(duì)列中操作引發(fā)的改變無關(guān)赤炒,瀏覽器也會強(qiáng)行清空隊(duì)列,確保你拿到的值是最精確的莺褒。

如何避免

css

避免使用 table 布局。
盡可能在 DOM 樹的最末端改變 class遵岩。
避免設(shè)置多層內(nèi)聯(lián)樣式。
將動畫效果應(yīng)用到 position 屬性為 absolute 或 fixed 的元素上尘执。
避免使用 CSS 表達(dá)式(例如:calc())。

Javascript

避免頻繁操作樣式正卧,最好一次性重寫 style 屬性,或者將樣式列表定義為 class 并一次性更改 class 屬性炉旷。

// 優(yōu)化前
const el = document.getElementById('test');
el.style.borderLeft = '1px';
el.style.borderRight = '2px';
el.style.padding = '5px';
// 優(yōu)化后,一次性修改樣式,這樣可以將三次重排減少到一次重排
const el = document.getElementById('test');
el.style.cssText += '; border-left: 1px ;border-right: 2px; padding: 5px;'

避免頻繁操作 DOM窘行,創(chuàng)建一個 documentFragment,在它上面應(yīng)用所有 DOM 操作罐盔,最后再把它添加到文檔中。
也可以先為元素設(shè)置 display: none,操作結(jié)束后再把它顯示出來六孵。因?yàn)樵?display 屬性為 none 的元素上進(jìn)行的 DOM 操作不會引發(fā)回流和重繪。
避免頻繁讀取會引發(fā)回流/重繪的屬性劫窒,如果確實(shí)需要多次使用,就用一個變量緩存起來主巍。
對具有復(fù)雜動畫的元素使用絕對定位,使它脫離文檔流孕索,否則會引起父元素及后續(xù)元素頻繁回流。

圖片懶加載

圖片懶加載在一些圖片密集型的網(wǎng)站中運(yùn)用比較多搞旭,通過圖片懶加載可以讓一些不可視的圖片不去加載,避免一次性加載過多的圖片導(dǎo)致請求阻塞(瀏覽器一般對同一域名下的并發(fā)請求的連接數(shù)有限制)选脊,這樣就可以提高網(wǎng)站的加載速度,提高用戶體驗(yàn)恳啥。

原理

將頁面中的img標(biāo)簽src指向一張小圖片或者src為空,然后定義data-src(這個屬性可以自定義命名钝的,我才用data-src)屬性指向真實(shí)的圖片。src指向一張默認(rèn)的圖片硝桩,否則當(dāng)src為空時也會向服務(wù)器發(fā)送一次請求⊥爰梗可以指向loading的地址。注意衙伶,圖片要指定寬高。

<img src="default.jpg" data-src="666.jpg" />

當(dāng)載入頁面時矢劲,先把可視區(qū)域內(nèi)的img標(biāo)簽的data-src屬性值負(fù)給src,然后監(jiān)聽滾動事件芬沉,把用戶即將看到的圖片加載躺同。這樣便實(shí)現(xiàn)了懶加載丸逸。

實(shí)例
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <style>
    img {
      display: block;
      margin-bottom: 50px;
      width: 400px;
      height: 400px;
    }
  </style>
</head>
<body>
  <img src="Go.png" data-src="./lifecycle.jpeg" alt="">
  <img src="Go.png" data-src="./lifecycle.jpeg" alt="">
  <img src="Go.png" data-src="./lifecycle.jpeg" alt="">
  <img src="Go.png" data-src="./lifecycle.jpeg" alt="">
  <img src="Go.png" data-src="./lifecycle.jpeg" alt="">
  <img src="Go.png" data-src="./lifecycle.jpeg" alt="">
  <img src="Go.png" data-src="./lifecycle.jpeg" alt="">
  <img src="Go.png" data-src="./lifecycle.jpeg" alt="">
  <img src="Go.png" data-src="./lifecycle.jpeg" alt="">
  <img src="Go.png" data-src="./lifecycle.jpeg" alt="">
  <img src="Go.png" data-src="./lifecycle.jpeg" alt="">
  <script>
    let num = document.getElementsByTagName('img').length;
    let img = document.getElementsByTagName("img");
    let n = 0; //存儲圖片加載到的位置,避免每次都從第一張圖片開始遍歷

    lazyload(); //頁面載入完畢加載可是區(qū)域內(nèi)的圖片

    window.onscroll = lazyload;

    function lazyload() { //監(jiān)聽頁面滾動事件
      let seeHeight = document.documentElement.clientHeight; //可見區(qū)域高度
      let scrollTop = document.documentElement.scrollTop || document.body.scrollTop; //滾動條距離頂部高度
      for (let i = n; i < num; i++) {
        if (img[i].offsetTop < seeHeight + scrollTop) {
          if (img[i].getAttribute("src") == "Go.png") {
            img[i].src = img[i].getAttribute("data-src");
          }
          n = i + 1;
        }
      }
    }
  </script>

</body>

</html>

事件委托

事件委托其實(shí)就是利用JS事件冒泡機(jī)制把原本需要綁定在子元素的響應(yīng)事件(click椭员、keydown……)委托給父元素,讓父元素?fù)?dān)當(dāng)事件監(jiān)聽的職務(wù)隘击。事件代理的原理是DOM元素的事件冒泡研铆。
優(yōu)點(diǎn):

  1. 大量減少內(nèi)存占用埋同,減少事件注冊棵红。
  2. 新增元素實(shí)現(xiàn)動態(tài)綁定事件

例如有一個列表需要綁定點(diǎn)擊事件,每一個列表項(xiàng)的點(diǎn)擊都需要返回不同的結(jié)果逆甜。
傳統(tǒng)寫法:

<ul id="color-list">
    <li>red</li>
    <li>yellow</li>
    <li>blue</li>
    <li>green</li>
    <li>black</li>
    <li>white</li>
  </ul>
  <script>
    (function () {
      var color_list = document.querySelectorAll('li')
      console.log("color_list", color_list)
      for (let item of color_list) {
        item.onclick = showColor;
      }
      function showColor(e) {
        alert(e.target.innerHTML)
        console.log("showColor -> e.target", e.target.innerHTML)
      }
    })();
  </script>

傳統(tǒng)方法會利用for循環(huán)遍歷列表為每一個列表元素綁定點(diǎn)擊事件,當(dāng)列表中元素數(shù)量非常龐大時交煞,需要綁定大量的點(diǎn)擊事件,這種方式就會產(chǎn)生性能問題素征。這種情況下利用事件委托就能很好的解決這個問題。

改用事件委托:

<ul id="color-list">
    <li>red</li>
    <li>yellow</li>
    <li>blue</li>
    <li>green</li>
    <li>black</li>
    <li>white</li>
  </ul>
  <script>
    (function () {
      var color_list = document.getElementByid('color-list');
      color_list.addEventListener('click', showColor, true);
      function showColor(e) {
        var x = e.target;
        if (x.nodeName.toLowerCase() === 'li') {
          alert(x.innerHTML);
        }
      }
    })();
  </script>

二御毅、渲染完成后的頁面交互優(yōu)化:

防抖(debounce)/節(jié)流(throttle)
防抖(debounce)

輸入搜索時,可以用防抖debounce等優(yōu)化方式端蛆,減少http請求;
這里以滾動條事件舉例:防抖函數(shù) onscroll 結(jié)束時觸發(fā)一次今豆,延遲執(zhí)行

function debounce(func, wait) {
  let timeout;
  return function() {
    let context = this; // 指向全局
    let args = arguments;
    if (timeout) {
      clearTimeout(timeout);
    }
    timeout = setTimeout(() => {
      func.apply(context晚凿, args); // context.func(args)
    }, wait);
  };
}
// 使用
window.onscroll = debounce(function() {
  console.log('debounce');
}歼秽, 1000);
節(jié)流(throttle)

節(jié)流函數(shù):只允許一個函數(shù)在N秒內(nèi)執(zhí)行一次。滾動條調(diào)用接口時,可以用節(jié)流throttle等優(yōu)化方式箩祥,減少http請求;
下面還是一個簡單的滾動條事件節(jié)流函數(shù):節(jié)流函數(shù) onscroll 時袍祖,每隔一段時間觸發(fā)一次,像水滴一樣

function throttle(fn蕉陋, delay) {
  let prevTime = Date.now();
  return function() {
    let curTime = Date.now();
    if (curTime - prevTime > delay) {
      fn.apply(this, arguments);
      prevTime = curTime;
    }
  };
}
// 使用
var throtteScroll = throttle(function() {
  console.log('throtte');
}凳鬓, 1000);
window.onscroll = throtteScroll;

參考鏈接:https://zhuanlan.zhihu.com/p/113864878?from_voters_page=true

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市缩举,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌仅孩,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,544評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件辽慕,死亡現(xiàn)場離奇詭異,居然都是意外死亡鼻百,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評論 3 392
  • 文/潘曉璐 我一進(jìn)店門温艇,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人勺爱,你說我怎么就攤上這事∷雎常” “怎么了?”我有些...
    開封第一講書人閱讀 162,764評論 0 353
  • 文/不壞的土叔 我叫張陵围段,是天一觀的道長。 經(jīng)常有香客問我奈泪,道長灸芳,這世上最難降的妖魔是什么拜姿? 我笑而不...
    開封第一講書人閱讀 58,193評論 1 292
  • 正文 為了忘掉前任烙样,我火速辦了婚禮,結(jié)果婚禮上蕊肥,老公的妹妹穿的比我還像新娘。我一直安慰自己壁却,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,216評論 6 388
  • 文/花漫 我一把揭開白布展东。 她就那樣靜靜地躺著,像睡著了一般琅锻。 火紅的嫁衣襯著肌膚如雪向胡。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,182評論 1 299
  • 那天僵芹,我揣著相機(jī)與錄音,去河邊找鬼拇派。 笑死,一個胖子當(dāng)著我的面吹牛件豌,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播茧彤,決...
    沈念sama閱讀 40,063評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼曾掂!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起珠洗,我...
    開封第一講書人閱讀 38,917評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎许蓖,沒想到半個月后调衰,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,329評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡窖式,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,543評論 2 332
  • 正文 我和宋清朗相戀三年动壤,在試婚紗的時候發(fā)現(xiàn)自己被綠了萝喘。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片琼懊。...
    茶點(diǎn)故事閱讀 39,722評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖哼丈,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情醉旦,我是刑警寧澤饶米,帶...
    沈念sama閱讀 35,425評論 5 343
  • 正文 年R本政府宣布车胡,位于F島的核電站,受9級特大地震影響匈棘,放射性物質(zhì)發(fā)生泄漏丧慈。R本人自食惡果不足惜主卫,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,019評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望簇搅。 院中可真熱鬧,春花似錦馍资、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,671評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽建钥。三九已至藤韵,卻和暖如春熊经,著一層夾襖步出監(jiān)牢的瞬間欲险,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,825評論 1 269
  • 我被黑心中介騙來泰國打工天试, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人然低。 一個月前我還...
    沈念sama閱讀 47,729評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像雳攘,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子吨灭,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,614評論 2 353

推薦閱讀更多精彩內(nèi)容