JavaScript 的時間消耗

隨着咱們的網站愈來愈依賴 JavaScript, 咱們有時會(無心)用一些不易追蹤的方式來傳輸一些(耗時的)東西. 在這篇文章中, 我會介紹一些能讓你的網站在移動設備上快速加載且可交互的方式.瀏覽器

摘要: 更少的代碼 = 更少的解析/編譯(時間) + 更少的傳輸(時間) + 更少的解壓(時間)緩存

網絡

大多數開發者考慮 JavaScript 的時間消耗時, 都會首先考慮到 JavaScript 的下載和執行消耗. 腳本傳輸的字節越多, 花費的時間越長, 用戶鏈接的就越慢.babel

即便在網絡發達的國家, 這也是須要面對的一個問題, 由於用戶有效的網絡鏈接類型不必定就是 3G、4G 或者 Wifi. 你能夠鏈接咖啡店的 Wifi, 也可能鏈接上一個 2G 網絡的蜂窩熱點.網絡

於是, 開發者須要想辦法減小 JavaScript 在網絡上的傳輸時間. 我這提供一些參考的方式:app

  • 經過代碼分割(Code Splitting), 只傳輸用戶須要的代碼.性能

  • 減小代碼體積(對於 ES5 可使用 Uglify; 對於 ES2015, 可使用 babel-minify 或 uglify-es)測試

  • 壓縮代碼(可使用 Brotli ~ q11, Zopfli 或 gzip). Brotli 在壓縮比上優於 gzip. 這種方式幫助 CertSimple 網站把腳本體積減小了 17%, 並幫助 LinkedIn 節省了 4% 的腳本加載時間.優化

  • 移除無用的代碼. 能夠經過 DevTools 查看代碼覆蓋率狀況. 對於代碼分離, 能夠了解 tree-shaking、Closure Compiler等高級優化方式. 對於公共庫則可使用一些代碼優化插件, 如針對 lodash 的代碼優化插件 lodash-babel-plugin, 可用於像 Moment.js 一類庫的優化插件 ContextReplacementPlugin. 此外, 使用 babel-preset-env & browserlist 能夠避免編譯現代瀏覽器已經支持的功能. 部分更高級的開發者可能會細心分析 Webpack bundles 來幫助肯定沒必要要的依賴.網站

  • 經過緩存來優化網絡傳輸. 經過 max-age 和 Etag 等方式來緩存腳本, 減小字節的傳輸. Service Worker 緩存技術能使你的應用具有網絡彈性, 而且能使用像 V8 code cache 同樣的特性. 同時, 也能夠了解下經過 文件哈希名 實現長久緩存.this

 

解析/編譯

腳本下載以後, JavaScript 最消耗時間的地方就是 JS 引擎對代碼的解析/編譯. 在 Chrome DevTools 的性能面板中, JS 的解析和編譯是 Scripting time 中的黃色部分.

 

從 Bottom-Up/Call Tree 能夠看到更精確的解析/編譯時間.

 

可是, 爲何會這樣呢?

 

花費很長時間去解析/編譯代碼會嚴重延遲用戶在網站上的可交互時間. 傳輸的腳本越多, 在網站可交互以前, 就會花費更多的時間去解析/編譯代碼.

和腳本相比, 瀏覽器也會花費不少時間來處理同等大小的圖片(圖片仍須要被解碼). 可是在大多數移動設備上, JS 更有可能對頁面的交互性產生負面影響.

當咱們談論腳本的解析和編譯很慢時, 上下文是很重要的–咱們說的是普通的手機設備. 普通用戶的手機是配置低配的 CPU 和 GPU, 可能因爲手機內存的限制, 也沒有 L2/L3 級緩存設置.

在 JavaScript 性能 一文中, 我注意到在低配手機和高配手機上解析約 1M 被解壓後的腳本文件所用的時間是不一樣的. 對於市面上解析最快的手機和普通手機之間, 大約有 2~5x 的時間差別.

那麼不一樣配置的手機訪問 CNN.com 又會是怎麼樣的呢?

與普通手機(Moto G4) 須要花費約 13s 來解析/編譯 CNN 網站的 JS 相比, 高配 iPhone 8 僅須要約 4s 時間.這能夠顯著地影響用戶與該站點徹底交互的速度.

這突出了測試普通手機設備(如 Moto G4)的重要性而不只僅是你口袋裏的手機設備. 然而, 上下文關係也很重要: 優化網站用戶的硬件設備和網絡環境.

深刻分析真實用戶訪問你的網站所使用的移動設備類型, 這樣纔可能明白他們真實的 CPU/GPU 等硬件約束.

另外一方面, 也須要反思咱們是否真的傳輸了太多的腳本?

經過 HTTP Archive 分析約前 500K 網站在移動設備上傳輸的腳本大小, 能夠發現 50% 的網站須要佔據 14s, 用戶才能夠與網站交互, 可是這些網站僅用 4s 時間來解析和編譯 JS.

在獲取和處理 JS 以及其餘資源所需的時間中, 用戶須要在頁面可交互以前等待一段時間, 這一點也不奇怪, 但咱們能夠在這裏作得更好.

移除頁面上的非關鍵腳本不只能減小傳輸時間, 也能減小 CPU 的解析/編譯時間和潛在的內存開銷, 這可提升頁面可交互的速度.

執行時間

不只腳本的解析和編譯須要時間, 腳本的執行也須要時間. 長時間的執行時間也會延遲用戶與站點的交互速度.

若是腳本的執行時間超過 50ms, 那麼可交互時間的延遲將是腳本下載、編譯和執行腳本所花費時間的總和. — Alex Russell

爲減小腳本的執行時間, 能夠將腳本分紅小塊來執行, 以免鎖住主線程. 能夠考慮是否能減小腳本在執行過程當中須要完成的工做量, 若是工做量不少, 就將腳本分紅小塊來分解工做量, 以提升頁面可交互的速度.

下降 JavaScript 交付成本的模式

當你嘗試着下降 JavaScript 的解析/編譯和網絡傳輸時間時, 也能夠試試基於路由的代碼分割或 PRPL 模式來下降 JavaScript 的交付成本.

PRPL 是一種經過代碼分割和緩存來優化頁面交互的模式:

經過 V8’s Runtime Call Stats, 咱們能夠分析一些受歡迎移動站以及 PWA 應用的加載時間. 從下圖能夠看出, 腳本解析所須要的時間(橙色部分)是頁面加載中最耗時的一部分:

其它消耗

除上述方式外, JavaScript 還能經過以下方式影響頁面性能:

  • 內存. 因爲 GC(garbage collection), 頁面可能會頻繁的出現閃現或者卡頓. 當瀏覽器回收內存時, JS 的執行會被暫停, 因此 JS 被暫停執行的頻率和瀏覽器回收內存的頻率是正相關的, 所以須要避免內存泄漏和頻繁的內存回收致使的 JS 執行暫停, 保持頁面的流暢度.

  • 在運行期間, 長時間的腳本執行會阻塞主線程而致使頁面沒有響應. 將腳本的工做量分紅多個小塊來執行(使用 requestAnimationFrame() 或 requestIdleCallback() 進行任務調度)能夠最小化響應性問題.

 

Progressive Bootstrapping(逐步引導)

由於優化交互性的成本比較高, 許多網站會考慮去優化內容的可見性. 當 JavaScript Bundles 很大時, 爲了減小白屏時間(First paint time), 一些開發者會採用服務端渲染的方式, 當 JS 處理完成以後再將其 「升級」 爲事件處理.

但這種方式也是有時間消耗的: 1) 一般會發送一個很大的 HTML 文件做爲響應, 2) 在 JavaScript 完成處理以前, 頁面可能只有一部分是可交互的.

於是逐步引導多是一個更好的方式. 瀏覽請請求一個最小化的功能頁面(僅由當前路由須要的 HTML/JS/CSS 組成), 當有更多資源請求時, 應用能夠進行資源懶加載, 而後逐步解鎖更多功能.

 

Loading code proportionate to what’s in view is the holy grail. PRPL and Progressive Bootstrapping are patterns that can help accomplish this.

相關文章
相關標籤/搜索