[譯] 2019 前端效能優化年度總結 — 第五部分

wznonstop發表於2019-03-02

讓 2019 來得更迅速吧!你正在閱讀的是 2019 年前端效能優化年度總結,始於 2016。

目錄

交付優化

39. 是否所有的 JavaScript 庫都採用了非同步載入?

當使用者請求頁面時,瀏覽器獲取 HTML 並構造 DOM,然後獲取 CSS 並構造 CSSOM,然後通過匹配 DOM 和 CSSOM 生成渲染樹。一旦出現需要解析的 JavaScript,瀏覽器將停止渲染,直到 JavaScript 被解析完成,從而造成渲染延遲。作為開發人員,我們必須明確告訴瀏覽器不要等待 JS 解析,直接渲染頁面。對指令碼執行此操作的方法是使用 HTML 中的 deferasync 屬性。

在實踐中,事實證明我們應該更傾向於使用 defer。使用 async 的話,Internet Explorer 9 及其之前的版本有相容性問題,可能會破壞它們的指令碼。根據Steve Souders 的講述,一旦 async 指令碼載入完成,它們就會立即執行。如果這種情況發生得非常快,例如當指令碼處於快取中時,它實際上可以阻止 HTML 解析器。使用 defer 的話,瀏覽器在解析 HTML 之前不會執行指令碼。因此,除非在開始渲染之前需要執行 JavaScript,否則最好使用 defer

此外,如上所述,限制第三方庫和指令碼的可能造成的影響,尤其是社交分享按鈕和嵌入式 <iframe>(如地圖)。Size Limit 庫可以幫助防止 JavaScript 庫過大 :如果不小心新增了一個大的依賴項,該工具將通知你並丟擲錯誤。可以使用靜態的社交分享按鈕(例如 SSBG)和互動式地圖的靜態連結

也可以試著修改非阻塞指令碼載入器以實現 CSP 合規性

40. 使用 IntersectionObserver 載入大型元件

一般來說,延遲載入所有大型元件是一個好主意,例如大體積的 JavaScript、視訊、iframe、小部件和潛在的影像。最高效的方法是使用Intersection Observer API,它對具有祖先元素或頂級文件視口的目標元素提供了一種非同步觀察交叉點變化的方法。基本用法是,建立一個新的 IntersectionObserver 物件,該物件接收回撥函式和配置物件。然後再新增一個觀察目標就可以了。

回撥函式在目標變為可見或不可見時執行,因此當它擷取視窗時,可以在元素變為可見之前開始執行某些操作。實際上,我們使用了 rootMargin(根周圍的邊距)和 threshold(單個數字或數字陣列,表示目標可見性的百分比)對何時呼叫回撥函式進行精確控制。

Alejandro Garcia Anglada 發表了一篇關於如何將其應用到實踐中的簡易教程,Rahul Nanwani 寫了一篇關於延遲載入前景和背景圖片的詳細文章,Google Fundamentals 提供了關於 Intersection Observer 延遲載入影像和視訊的詳細教程。還記得使用動靜結合的物體進行藝術指導的長篇故事嗎?你也可以使用 Intersection Observer 實現高效能的滾動型講述

另外,請注意 lazyload 屬性,它將允許我們以原生的方式指定哪些影像和 iframe 應該是延遲載入。功能說明:LazyLoad 將提供一種機制,允許我們強制在每個域的基礎上選擇加入或退出 LazyLoad 功能(類似於內容安全政策的功能。驚喜:一旦啟用,優先提示 priority hints 將允許我們在標題中指定指令碼和預載入資源的權重(目前已在 Chrome Canary 中實現)。

41. 漸進式載入圖片

你甚至可以通過向頁面新增漸進式影像載入技術將延遲載入提升到新的水平。與 Facebook,Pinterest 和 Medium 類似,可以先載入質量較差甚至模糊的影像,然後在頁面繼續載入時,使用 Guy Podjarny 提出的 LQIP(低質量影像佔位符)技術將其替換為原圖。

對於這項技術是否提升了使用者體驗,大家各執一詞,但它一定縮短了第一次有效的繪圖時間。我們甚至可以使用 SQIP 將其建立為 SVG 佔位符或帶有 CSS 線性漸變的漸變影像佔位符。這些佔位符可以嵌入 HTML 中,因為它們可以使用文字壓縮方法自然地壓縮。Dean Hume 在他的文章中描述了 如何使用 Intersection Observer 實現此技術。

瀏覽器支援怎麼樣呢?主流瀏覽器、Chrome、Firefox、Edge 和三星的瀏覽器均有支援。WebKit 狀態目前已在預覽中支援。如何優雅降級?如果瀏覽器不支援 intersection observer,我們仍然可以使用 polyfill延遲載入或立即載入影像。甚至有一個可以用來實現它。

想成為一名發燒友?你可以追蹤你的影像並使用原始形狀和邊框來建立一個輕量級的 SVG 佔位符,首先載入它,然後把佔位符向量影像轉換為(已載入的)點陣圖影像。

José M. Pérez 的 SVG 延遲載入技術

José M. Pérez的 SVG 延遲載入技術。(大圖預覽

42. 你是否傳送了關鍵的 css?

為了確保瀏覽器儘快開始渲染頁面,通常做法是收集開始渲染頁面的第一個可見部分所需的所有 CSS(稱為“關鍵 CSS”或“首頁 CSS”)並將其以內聯的形式新增到頁面的 “” 中,從而減少往返請求。由於在慢啟動階段交換的包的大小有限,因此關鍵 CSS 的預算大小約為 14 KB。

如果超出此範圍,瀏覽器將需要額外的開銷來獲取更多樣式。CriticalCSSCritical 使你能夠做到這一點。你可能需要為正在使用的每個模板執行此操作。如果可能的話,請考慮使用 Filament Group 使用的條件內聯方法,或動態地將內聯程式碼轉換為靜態資源

使用 HTTP/2,關鍵的 CSS 可以儲存在單獨的 CSS 檔案中,並通過伺服器推送傳送,而不會增加 HTML 的大小。問題是,伺服器推送很麻煩 ,瀏覽器存在許多陷阱和競爭條件。往往並不能始終支援,且伴有一些快取問題(參見 Hooman Beheshti 簡報幻燈片的 114 頁)。事實上,這種影響可能是負面的,它會使網路緩衝區膨脹,從而導致文件中真實幀的傳遞被阻止。此外,由於 TCP 啟動緩慢,伺服器推送似乎在熱連線上更有效

即使使用 HTTP/1,將關鍵 CSS 放在根域名下的單獨檔案中也是有好處的,由於快取的原因,有時甚至比內聯更優。Chrome 在請求頁面時會嘗試開啟根域名下的第二個 HTTP 連線,從而無需 TCP 連線來獲取此 CSS(感謝 Philip!

需要記住的一些問題是:與可以從任何域觸發預載入的“預載入”不同,你只能從自己的域或認證過的域中推送資源。一旦伺服器從客戶端獲得了第一個請求,就可以啟動該連線。伺服器推送資源落在 Push 快取中,並在連線終止時被刪除。但是,由於 HTTP/2 連線可以在多個選項卡中重複使用,因此也可以使用通過其他選項卡的請求宣告推送的資源(感謝 Inian!)。

目前,伺服器沒有簡單的方法可以知道要推送資源是否已經存在於使用者快取之中中,每個使用者訪問的時候都會推送資源。因此,你可能需要建立 HTTP/2 的快取感知伺服器推送機制。如果發現已存在,則可以嘗試根據快取中已有內容的索引從快取中獲取它們,從而避免伺服器的全量推送。

但請記住,新的 cache-digest 規範否定了手動構建此類“快取感知”伺服器的需要,只需要在 HTTP/2 中宣告一個新的幀型別,就可以傳達該域名下快取中已有的內容。因此,它對 CDN 也特別有用。

對於動態內容,當伺服器需要一些時間來生成響應時,瀏覽器無法發出任何請求,因為它不知道頁面可能引用的任何子資源。對於這種情況,我們可以預熱連線並增加 TCP 擁塞視窗的數量,以便可以更快地完成將來的請求。此外,所有內聯資源通常都是伺服器推送的良好候選者。事實上,Inian Parameshwaran 針對 HTTP/2 推送與 HTTP 預載入做了很棒的比較的研究,這份高質量的資料包括了你可能想了解的各種細節。是否選擇伺服器推送?Colin Bendell 的我是否應該進行伺服器推送?可能會為你指明方向。

一句話:正如 Sam Saccone 所說預載入適用於將資源的開始下載時間向初始請求靠攏,伺服器推送適用於刪除完整的 RTT(,具體取決於伺服器的響應時間)- 前提是你得有一個 service worker 用來避免不必要的推送。

43. 嘗試重組 CSS 規則

我們已經習慣了關鍵的 CSS,但還有一些優化可以超越這一點。Harry Roberts 進行了一項非凡的研究,得出了相當驚人的結果。例如,將主 CSS 檔案拆分為單獨的媒體查詢可能是個好主意。這樣,瀏覽器將檢索具有高優先順序的關鍵 CSS,以及其他具有低優先順序的所有內容 —— 最終完全脫離關鍵路徑。

另外,避免將 <link rel="stylesheet" /> 放在 async 標籤之前。如果指令碼不依賴於樣式表,請考慮將阻塞指令碼放在阻塞樣式之前。如果指令碼依賴樣式,請將該 JavaScript 一分為二,然後對應將其載入到 CSS 的前後。

Scott Jehl 通過使用 service worker 快取內聯 CSS 檔案解決了另一個有趣的問題,這是使用關鍵 CSS 時常見的問題。基本上,我們將 ID 屬性新增到 style 元素中,以便使用 JavaScript 時可以輕鬆找到它,然後一小塊 JavaScript 發現 CSS 並使用快取 API 將其儲存在本地瀏覽器快取中(其內容型別為 text/css),以便在後續頁面中使用。為了不在後續頁面上內聯引用,而是從外部引用快取的資源,我們在第一次訪問站點時設定了一個 cookie。瞧!

我們是否以流的方式進行響應了?使用流,在初始導航請求期間呈現的 HTML 可以充分利用瀏覽器的流 HTML 解析器。

44. 你有沒有將請求設為 stream?

經常被遺忘和忽略的是 Streams提供了一個讀或寫非同步資料塊的介面,在任何給定的時間裡,記憶體中可能只有一部分資料塊可用。基本上,它們允許發出原始請求的頁面在第一塊資料可用時立即開始處理響應,並使用針對流優化的解析器逐步顯示內容。

我們可以從多個來源建立一個流。例如,可以讓 service worker 構造一個流,其中 shell 來自快取,但主體來自網路,而不是提供一個空的 UI shell 並讓 JavaScript 填充它。正如 Jeff Posnick 所說,如果你的 Web 應用程式由 CMS 提供支援,該 CMS 通過將部分模板縫合在一起呈現 HTML,則可以將該模型直接轉換為使用流響應,模板邏輯將複製到 service worker而不是你的伺服器中。Jake Archibald 的 Web Streams 之年文章重點介紹瞭如何準確地構建它。可以為效能帶來相當明顯的提升

流式處理整個 HTML 響應的一個重要優點是,在初始導航請求期間呈現的 HTML 可以充分利用瀏覽器的流式 HTML 解析器。頁面載入後插入到文件中的 HTML 塊(這在通過 JavaScript 填充的內容中很常見)則無法享受這種優化。

瀏覽器支援怎麼樣呢?主流瀏覽器,Chrome 52+、Firefox 57+、Safari 和 Edge 均支援該 API,而所有的現代瀏覽器中都支援 Service Workers。

45. 考慮使元件具有連線感知能力

隨著不斷增長的負載,資料的開銷可能變得很大,我們需要尊重選擇在訪問我們的網站或應用程式時希望節省流量的使用者。Save-Data 客戶端提示請求頭允許我們為受成本和效能限制的使用者定製應用程式及其負載。事實上,你可以將高 DPI 影像的請求重寫為低 DPI 影像請求,刪除 Web 字型、花哨的視差效果、預覽縮圖和無限滾動、關閉視訊自動播放、伺服器推送、減少顯示專案的數量並降低影像質量,甚至改變交付標記的方式。Tim Vereecke 發表了一篇關於 data-s(h)aver 策略的非常詳細的文章,其中介紹了許多用於資料儲存的選項。

目前,只有 Chromium、Android 版本的 Chrome 或桌面裝置上的 Data Saver 擴充套件才支援標識頭。最後,你還可以使用 Network Information API 根據網路型別提供高/低解析度的影像 和視訊。Network Information API,特別是navigator.connection.effectiveType(Chrome62+)使用 RTTdownlinkeffectiveType(以及一些其他值)來為使用者提供可處理的連線和資料表示。

在這種情況下,Max Stoiber 談到連線感知元件。例如,使用 React 時,我們可以編寫一個為不同連線型別呈現不同元素的元件。正如 Max 建議的那樣,新聞文章中的 <Media /> 元件或許應該輸出為下列的幾種形式:

  • Offline:帶有 alt 文字的佔位符,
  • 2G / 省流 模式:低解析度影像,
  • 非視網膜屏的 3G:中等解析度影像,
  • 視網膜屏的 3G:高解析度視網膜影像,
  • 4G:高清視訊。

DeanHume 提供了一個使用 service worker 的類似邏輯的實現。對於視訊,我們可以在預設情況下顯示視訊海報,然後顯示“播放”圖示,在網路更好的情況下顯示視訊播放器外殼、視訊後設資料等。作為瀏覽器不相容的降級方案,我們可以監聽 canplaythrough 事件,並在 canplaythrough 事件 2 秒內未觸發的情況下使用 Promise.race() 來觸發資源載入超時。

46. 考慮使元件具有裝置記憶體感知能力

儘管如此,網路連線也只是為我們提供了關於使用者上下文的一個視角。更進一步,你還可以動態地根據可用裝置記憶體調整資源,使用 Device Memory API(Chrome63+)。navigator.deviceMemory 返回裝置的RAM容量(以 GB 為單位),四捨五入到最近的 2 次方。該 API 還具有客戶端提示標頭 Device-Memory,該標頭可以提供相同的值。

DevTools 中的“優先順序”列

DevTools 中的“優先順序”列。圖片來源:Ben Schwarz,關鍵請求

47. 做好連線的熱身準備以加速交付

使用資源提示來節省 dns-prefch(在後臺執行 DNS 查詢)的時間。preconnect 要求瀏覽器在後臺啟動連線握手(DNS、TCP、TLS),prefetch(要求瀏覽器請求資源)和 preload(除此之外,它並不需要執行它們即可預獲取資源)。

現在大部分時間裡,我們至少會使用 preconnectdns-prefetch,並且我們會謹慎地使用 prefetchpreload;只有當您對使用者下一步需要哪些資源(例如,當使用者處於購買漏斗模型中時)有信心時,才應該使用前者。

請注意,即使使用 preconnectdns-prefetch,瀏覽器對要並行查詢/連線到的主機數量也有限制,因此基於優先順序對它們進行排序是安全的(感謝 Philip!)。

事實上,使用資源提示可能是提高效能的最簡單的方法,而且它確實很有效。什麼時候用什麼?正如 Addy Osmani 曾解釋過的,我們應該預先載入我們高度信任的資源,以便在當前頁面中使用這些資源。預獲取資源可能會用於未來跨邊界的導航,例如使用者尚未訪問的頁面所需的 webpack bundles。

Addy 關於[“在 Chrome 中載入優先順序”]的文章(medium.com/reloading/p…)準確地展示了 Chrome 是如何解釋資源提示的,因此一旦確定了哪些資源對於渲染至關重要,就可以為它們分配高優先順序。要檢視請求的優先順序,可以在 Chrome 的 DevTools 網路請求表(以及 Safari 的 Technology Preview)中啟用“優先順序”列。

例如,由於字型通常是頁面上的重要資源,使用請求瀏覽器下載字型preload 一直是個好主意。你還可以動態載入 JavaScript,有效地執行延遲載入。另外,由於 <link rel="preload"> 接受一個 media 屬性,因此可以基於 @media 查詢規則選擇可選的資源優先順序

一些要記住的點preload 有利於使資源的開始下載時間更接近初始請求,但是,預載入的資源會存在記憶體快取中,該快取繫結到發出請求的頁面上。preload 可以很好地處理 HTTP 快取:如果 HTTP 快取中已經存在該資源,則永遠不會針對該資源去傳送網路請求。

因此,對於最近發現的資源、通過後臺影像載入的主頁橫幅、內聯關鍵的 CSS(或 JavaScript)以及預載入 CSS(或 JavaScript)的其餘部分,它非常有用。此外,preload 標記只能在瀏覽器接收到來自伺服器的 HTML 並且先行解析器找到 preload 標記後才能啟動預載入。

通過 HTTP 報頭預載入要快一些,因為我們不需要等待瀏覽器解析 HTML 來啟動請求。預提示 將提供更多幫助,即使在傳送 HTML 的響應頭和優先順序提示即將釋出)之前就啟用預載入,將幫助我們指示指令碼的載入優先順序。

注意:如果你使用的是 preload預載入的內容 必須被定義 否則就不會載入任何內容,另外不使用預載入字型的話跨域屬性會兩次獲取資料

48. 使用 service workers 進行快取和網路降級

網路上的任何效能優化都趕不上從使用者計算機上本地儲存的快取中取資料快。如果你的網站基於 HTTPS 協議,請使用“Service Workers 的實用指南”將靜態資源快取到 service worker 快取中,並儲存離線回退(甚至離線頁),然後從使用者的計算機檢索它們,而不是轉向網路。此外,請檢視 Jake 的離線 Cookbook 和免費的 udacity 課程“離線 Web 應用”。

瀏覽器支援怎麼樣呢?如上所述,它得到了廣泛支援(Chrome、Firefox、Safari TP、三星瀏覽器、Edge 17+),降級的話就是去髮網路請求。它是否有助於提高效能呢?當然了,。而且它正在變得更好,例如通過後臺抓取,允許從 service worker 進行後臺上傳/下載等。Chrome71 中已釋出

service worker 有許多使用案例。例如,可以實現“離線儲存”功能處理已損壞影像,介紹選項卡之間的訊息傳遞根據請求型別提供不同的快取策略。一般來說,一種常見的可靠策略是將應用程式外殼與幾個關鍵頁面一起儲存在 service worker 的快取中,例如離線頁面、前端頁面以及對具體場景中可能重要的任何其他頁面。

儘管如此,還是有幾個問題需要記住。使用 service worker 時,我們需要注意 Safari 中的範圍請求(如果你使用的是 service worker 的工作框,它有一個範圍請求模組)。如果你在瀏覽器控制檯中偶然發現了 DOMException: Quota exceeded. 錯誤,那麼請檢視 Gerardo 的文章當 7KB 等於 7Mb

Gerardo 寫道:“如果你正在構建一個漸進式 Web 應用程式,並且使用 service worker 快取來自 CDN 的靜態資源,並正在經歷快取記憶體儲存膨脹,請確保跨域資源有適當的 CORS 響應頭存在不要快取不透明的響應,通過給 <img> 標籤設定 crossorigin 屬性,將跨域影像資源設為 CORS 模式“。

使用 service worker 的一個很好的起點是 workbox,這是一組專門為構建漸進式 Web 應用程式而構建的 service worker 庫。

49. 是否在 CDN/Edge 上使用了 service workers,例如,用於 A/B 測試?

在這一點上,我們已經習慣於在客戶端上執行 service worker,但是通過在 CDN 伺服器上使用它們,我們也可以實現用它們來調整邊緣效能。

例如,在 A/B 測試中,當 HTML 需要為不同的使用者改變其內容時,我們可以使用 CDN 伺服器上的 service worker 來處理邏輯。我們還可以通過重寫 HTML 流來加速使用谷歌字型的站點。

50. 優化渲染效能

使用CSS容器隔離開銷大的元件 —— 例如,限制瀏覽器樣式、畫布和畫圖用於畫布外導航或第三方小部件的範圍。請確保在滾動頁面或設定元素動畫時沒有延遲,並且始終達到每秒 60 幀。如果這無法實現,那麼至少使每秒的幀數保持一致,這比 60 到 15 之間的不定值更可取。使用 CSS 的 will-change 去通知瀏覽器哪些元素和屬性將更改。

此外,度量執行時渲染效能(例如,使用 DevTools 中的 rendering 工具)。想要快速上手,可以檢視 Paul Lewis 關於瀏覽器渲染優化的免費 udacity 課程和 Georgy Marchuk 關於瀏覽器繪製和 Web 效能思考的文章

如果你想深入探討這個話題,Nolan Lawson 在他的文章中分享了精確測量佈局效能的技巧,Jason Miller 也給出了替代技術的建議。 我們還有 Sergey Chikuyonok 撰寫的一篇關於如何正確製作 GPU 動畫的文章。快速提示:對 GPU 合成層的更改是開銷最小的,因此,如果你只通過 opacitytransform 觸發合成,那就對了。Anna Migas 在她關於除錯 UI 呈現效能的演講中也提供了很多實用的建議。

51. 是否優化了渲染體驗?

雖然元件在頁面上的顯示順序以及我們如何將資源提供給瀏覽器的策略很重要,但我們不應低估感知效能的作用。這一概念涉及到等待時的心理效應,基本上是讓顧客在其他事情發生的時候保持有事可做。這就是感知管理搶先啟動提前完成容忍度管理開始發揮作用。

這一切意味著什麼?在載入資源時,我們可以嘗試始終領先於客戶一步,這樣在後臺繁忙的時候,使用者依然感覺頁面速度很快。為了讓客戶參與進來,我們可以測試框架螢幕實現演示),而不是loading指示器。新增過渡/動畫,簡單的欺騙使用者體驗。不過,請注意:在部署之前應該對骨架螢幕進行測試,因為從各項指標來看,有些測試表明,骨架螢幕的效能最差

如果發現譯文存在錯誤或其他需要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可獲得相應獎勵積分。文章開頭的 本文永久連結 即為本文在 GitHub 上的 MarkDown 連結。


掘金翻譯計劃 是一個翻譯優質網際網路技術文章的社群,文章來源為 掘金 上的英文分享文章。內容覆蓋 AndroidiOS前端後端區塊鏈產品設計人工智慧等領域,想要檢視更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章