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

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

目錄

交付優化

39. 是否全部的 JavaScript 庫都採用了異步加載?

當用戶請求頁面時,瀏覽器獲取 HTML 並構造 DOM,而後獲取 CSS 並構造 CSSOM,而後經過匹配 DOM 和 CSSOM 生成渲染樹。一旦出現須要解析的 JavaScript,瀏覽器將中止渲染,直到 JavaScript 被解析完成,從而形成渲染延遲。做爲開發人員,咱們必須明確告訴瀏覽器不要等待 JS 解析,直接渲染頁面。對腳本執行此操做的方法是使用 HTML 中的 deferasync 屬性。css

在實踐中,事實證實咱們應該更傾向於使用 defer。使用 async 的話,Internet Explorer 9 及其以前的版本有兼容性問題,可能會破壞它們的腳本。根據Steve Souders 的講述,一旦 async 腳本加載完成,它們就會當即執行。若是這種狀況發生得很是快,例如當腳本處於緩存中時,它實際上能夠阻止 HTML 解析器。使用 defer 的話,瀏覽器在解析 HTML 以前不會執行腳本。所以,除非在開始渲染以前須要執行 JavaScript,不然最好使用 deferhtml

此外,如上所述,限制第三方庫和腳本的可能形成的影響,尤爲是社交分享按鈕和嵌入式 <iframe>(如地圖)。Size Limit 庫能夠幫助防止 JavaScript 庫過大 :若是不當心添加了一個大的依賴項,該工具將通知你並拋出錯誤。可使用靜態的社交分享按鈕(例如 SSBG)和交互式地圖的靜態連接前端

也能夠試着修改非阻塞腳本加載器以實現 CSP 合規性java

40. 使用 IntersectionObserver 加載大型組件

通常來講,延遲加載全部大型組件是一個好主意,例如大致積的 JavaScript、視頻、iframe、小部件和潛在的圖像。最高效的方法是使用Intersection Observer API,它對具備祖先元素或頂級文檔視口的目標元素提供了一種異步觀察交叉點變化的方法。基本用法是,建立一個新的 IntersectionObserver 對象,該對象接收回調函數和配置對象。而後再添加一個觀察目標就能夠了。android

回調函數在目標變爲可見或不可見時執行,所以當它截取視窗時,能夠在元素變爲可見以前開始執行某些操做。實際上,咱們使用了 rootMargin(根周圍的邊距)和 threshold(單個數字或數字數組,表示目標可見性的百分比)對什麼時候調用回調函數進行精確控制。webpack

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

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

41. 漸進式加載圖片

你甚至能夠經過向頁面添加漸進式圖像加載技術將延遲加載提高到新的水平。與 Facebook,Pinterest 和 Medium 相似,能夠先加載質量較差甚至模糊的圖像,而後在頁面繼續加載時,使用 Guy Podjarny 提出的 LQIP(低質量圖像佔位符)技術將其替換爲原圖。github

對於這項技術是否提高了用戶體驗,你們各執一詞,但它必定縮短了第一次有效的繪圖時間。咱們甚至可使用 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前端後端區塊鏈產品設計人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章
相關標籤/搜索