iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 20
2
Modern Web

前端三十 - 成為更好的前端工程師系列 第 20

20. [FE] 如何提升網站效能?

https://images.unsplash.com/photo-1547042591-aae98619aab5?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=751&q=80

Perl 之父 Larry Wall 曾說過,工程師有三大美德,分別是:「懶惰」、「急躁」與「傲慢」。其中的「急躁」指的便是對於效能低落的程式會有所追求;身為一個前端工程師,如果自己做出來的網站一打開就白畫面好幾秒,點擊連結後就開始無止盡的等待,畫面樣式變動還會一格一格卡卡的,相信無論是誰都會受不了吧?對於這些情況,身為前端工程師的你我,能夠怎麼優化、改善呢?

本系列文已經重新編校彙整編輯成冊,並正式出版囉!
《前端三十:從 HTML 到瀏覽器渲染的前端開發者必備心法》好評販售中!
喜歡我文章內容的讀者們,歡迎您 前往購買 支持!

尋找問題

要進行優化改善,首先就是要能夠辨識出問題可能在哪裡;不如先思考一下,使用者所看到的網頁內容是怎麼來的。

首先必然是下載內容。現代的網站為了更容易吸引使用者的眼球,除了豐富的文本內容外,還會使用的大量的圖片、影片傳達資訊,甚至透過聲音、動畫等方式與使用者做互動,這些多媒體內容,都代表著更多的多媒體資源以及程式碼。

取得網頁內容後,瀏覽器便開始解析程式碼,並渲染出畫面。這部分在 本系列文03. [CSS] Reflow 及 Repaint 是什麼? 中便有稍微提到,今天也會以那篇的內容為基礎,做更多關於優化的說明,如果您還沒看過,建議您可以先回頭參考該篇內容,會比較容易理解渲染的機制喔!

綜合以上,看來前端工程師較容易實現的網站優化項目,就會集中在「載入資源」及「畫面渲染」這兩部分了。

優化載入

如同前述,現代網站中需要下載的內容以驚人的速度瘋狂成長:

https://ithelp.ithome.com.tw/upload/images/20200823/20111380iJvnVFPA6G.png

上圖是來自 HTTP Archive 所紀錄的網站平均下載內容量,可以看到現在(2020/08)的網站平均需要下載 2M 之多的資源;這麼巨量的內容,中間也自然會有許多可以進行優化的地方。

減少資源量

最直覺的就是減少下載的內容。

程式碼的部份,可以將換行符號、無意義的空白、註解等方便人類看的內容移除,把程式內容最小化(minify);例如以下的 CSS:

p.round {
  border: 2px solid red;
  border-radius: 12px;
}

經過 minifier.org 最小化處理後,會變成這樣子:

p.round{border:2px solid red;border-radius:12px}

同樣的道理也可以套用在 HTML 及 JavaScript 上。例如我們 先前說明閉包 時用的範例程式碼:

function loadPicture() {
  let count = this.pageContent.length
  const load = (target, resolve) => {
    const counter = () => (--count ? count : resolve())
    let img = new Image()
    img.onload = counter
    img.onerror = counter
    img.src = target.url
  }
  return new Promise(resolve => {
    if (!count) return resolve()
    this.pageContent.forEach(target => load(target, resolve))
  })
}

同樣經過最小化處理後會變成:

function loadPicture(){let count=this.pageContent.length;const load=(target,resolve)=>{const counter=()=>(--count?count:resolve());let img=new Image();img.onload=counter;img.onerror=counter;img.src=target.url}
return new Promise(resolve=>{if(!count)return resolve();this.pageContent.forEach(target=>load(target,resolve))})}

由於所有的換行、縮排對瀏覽器解析內容來說都是沒有意義的部分,將它們移除不但不會影響到功能,還可以節省大量的傳輸量。

另外,JavaScript 的變數名稱,除非是留給給其他程式呼叫,否則對瀏覽器來說也是無意義的;我們還可以將程式碼 醜化(uglify),把變數名稱都換成更精簡的內容。

例如前例,再經過 UglifyJS online 的處理,會變成這樣:

function loadPicture(){let e=this.pageContent.length;return new Promise(n=>{if(!e)return n();this.pageContent.forEach(t=>((n,t)=>{const r=()=>--e?e:t();let o=new Image;o.onload=r,o.onerror=r,o.src=n.url})(t,n))})}

這段程式碼壓縮到最後,總共減少了 46.48% 的大小,是不是很驚人呢?

除了程式之外,多媒體的內容大小也是另一個重點;例如小動畫,其實與其用 Gif,還不如換成 MP4 等影片格式,整體的影片品質不但比較好,所佔容量也會下降。圖檔的部分,最基本的就是不要用超過顯示尺寸的圖,畢竟尺寸越大,所需要下載的內容也越多,但顯示時卻只放在一個小區塊中呈現,瀏覽器下載完還要計算如何顯示,一來一往造成大量的冗餘運算。另外,大部分的圖檔在網頁中顯示時,其實可以犧牲一部分的品質細節,換來更小的檔案容量;例如以下兩張圖:

壓縮
沒壓

其中有一張是經過 imagecompressor.com 處理的圖片,且大小只有原本的 46%,讀者您看得出來哪一張圖被壓縮過了嗎?

只拿必要的檔案

把無意義的的內容都拿掉了,剩下就全都是這個網站需要的內容了嗎?其實不然,例如讀者您造訪一次 Instagram 或 Facebook,可能只會從眾多的功能之中使用一小部分,並不會全部功能都使用對吧?如果可以在使用者需要的時候,才將必要的資源提供給使用者,就可以進一步減少傳輸內容的數量。

在程式碼的部份,我們可以將整個網站共用的部分,以及彼此沒有相關聯的程式分成幾個大部分(chunk),在使用者造訪指定頁面時,只提供該頁面所需要的部分即可;例如一個網站有 10 個頁面,使用者看 A 頁面時給予 A 頁面所有需要的部分,看 B 頁面時也僅須傳遞 B 頁面的部份,其他未造訪、對使用者來說無意義的頁面內容,也就不用浪費資源傳遞,這樣的解決方案,在前端 SPA 中非常常見,也是能有效降低下載量的方法。

再仔細切分下去,使用者即使看了這個頁面,其實也不一定會整頁看完對吧?因此其實也不需要把整個頁面內的內容全都載下來;例如 FaceBook 的動態牆,也不會一口氣就載入個 50、100 則近況更新吧?而是在你向下滑動閱覽的同時,持續的載入更多新的內容;同樣的,我們只需要在資源需要顯示之前,將內容準備好即可,不用一口氣全部下載。

例如佔流量最大宗的圖檔,我們可以透過 Web API 的 Intersection Observer,建立一個觀察者,當圖檔區塊即將進入視窗時,替換掉圖檔的 src 屬性,達到圖片延遲加載(lazy loading)的目的。

最後,再更進一步的思考,頁面中總有些資料是不太會變動的,例如城市區域選單、郵遞區號選單的資料等等,如果使用者以前就曾經來過這個頁面,是不是可以讓使用者不要再次下載呢?

沒錯,就是快取(Cache)的機制,除了瀏覽器本身的快取機制外,如果真的必要,也可以透過 Web Storage,自行建立固定資料的儲存機制,就不用每次造訪頁面都重新下載,大幅度減少重複的資料傳遞。

優化渲染

優化完下載的部分,接著是渲染的優化。首先先複習一下 先前 提過的渲染流程,可以參考下圖:

rendering

其實在本系列文先前的內容中,我們就已經聊到不少關於優化渲染的方法,包含 這一篇 提到的一般的 <script> 載入資源時會阻礙畫面渲染,應該要加上 deferasync屬性;以及在 這一篇 提到的,盡量使用樣式屬性的變動來取代排版屬性的變動,避免造成 Reflow,還有 這一篇 也有小小聊到要注意圖層數量,過多時也會影響效能等等,這些內容讀者可以回頭參考相關文章,就不做重複說明了。

除了上述內容之外,還有一個先前比較少提到的,就是要讓出 JavaScript 的主執行緒(Main Thread);由於 JavaScript 是單執行緒的程式語言,現代網站也往往會利用 JavaScript 做一些複雜的視覺處理;當有複雜的運算卡住主執行緒時,需要由程式觸發的畫面效果便會無法正確呈現。解決方法的部分,可以利用 Web Worker 代為處理較複雜且長時間的計算。

關於用程式觸發動畫,請記得用 requestAnimationFrame 取代 setTimeoutsetInterval;後兩者因為 Event Loop 的緣故,執行的時間並不準確,動畫可能會造成跳格、延遲等效果;更多關於 Event Loop 可以參考 這裡

結語

優化網頁對前端來說是非常重要的一環,也是影響使用者體驗最大的關鍵,我們從優化載入 & 優化渲染兩個方向出發,逐步探討在瀏覽器解析網頁的各個階段中,程式碼是如何影響解析的過程及結果;希望今天的內容能幫助讀者您認識效能低落背後的潛在原因,並踏出優化網站效能的第一步。

本文是此系列的第 20 篇,這趟旅程也即將邁向尾聲;剩下的十天,本系列一樣會繼續在前端的相關領域中探索,每天成長一些些,一起讓自己成為更強大的前端工程師吧!

參考資料

筆者

Gary

半路出家網站工程師;半生熟的前端加上一點點的後端。
喜歡音樂,喜歡學習、分享,也喜歡當個遊戲宅。

相信一切安排都是最好的路。


上一篇
19. [FE] 如何實現網站 SEO?
下一篇
21. [FE] 用過 Webpack 之類的打包工具嗎?為什麼需要?
系列文
前端三十 - 成為更好的前端工程師31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言