您好,登錄后才能下訂單哦!
本篇內容介紹了“優化Web應用程序性能的方案及其優缺點說明”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!
Chrome Corverage 分析代碼覆蓋率
在講解這些機制前,先來談一個 Chrome 工具 Corverage。該工具可以幫助查找在當前頁面使用或者未使用的 JavaScript 和 CSS 代碼。
工具的打開流程為:
打開瀏覽器控制臺 console
ctrl+shift+p 打開命令窗口
在命令窗口輸入 show Coverage 顯示選項卡
webpackjs
其中如果想要查詢頁面加載時候使用的代碼,請點擊 reload button
如果您想查看與頁面交互后使用的代碼,請點擊record buton
這里以淘寶網為例子,介紹一下如何使用
上面兩張分別為 reload 與 record 點擊后的分析。
其中從左到右分別為
所需要的資源 URL
資源中包含的 js 與 css
總資源大小
當前未使用的資源大小
左下角有一份總述。說明在當前頁面加載的資源大小以及沒有使用的百分比。可以看到淘寶網對于首頁代碼的未使用率僅僅只有 36%。
介紹該功能的目的并不是要求各位重構代碼庫以便于每個頁面僅僅只包含所需的 js 與 css。這個是難以做到的甚至是不可能的。但是這種指標可以提升我們對當前項目的認知以便于性能提升。
提升代碼覆蓋率的收益是所有性能優化機制中最高的,這意味著可以加載更少的代碼,執行更少的代碼,消耗更少的資源,緩存更少的資源。
webpack externals 獲取外部 CDN 資源
一般來說,我們基本上都會使用 Vue,React 以及相對應的組件庫來搭建 SPA 單頁面項目。但是在構建時候,把這些框架代碼直接打包到項目中,并非是一個十分明智的選擇。
我們可以直接在項目的 index.html 中添加如下代碼
<script src="//cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.runtime.min.js" crossorigin="anonymous"></script> <script src="//https://cdn.jsdelivr.net/npm/vue-router@3.1.3/dist/vue-router.min.js" crossorigin="anonymous"></script>
然后可以在 webpack.config.js 中這樣配置
module.exports = { //... externals: { 'vue': 'Vue', 'vue-router': 'VueRouter', } };
webpack externals 的作用是 不會在構建時將 Vue 打包到最終項目中去,而是在運行時獲取這些外部依賴項。這對于項目初期沒有實力搭建自身而又需要使用 CDN 服務的團隊有著不錯的效果。
原理
這些項目被打包成為第三方庫的時候,同時還會以全局變量的形式導出。從而可以直接在瀏覽器的 window 對象上得到與使用。即是
window.Vue // ƒ bn(t){this._init(t)}
這也就是為什么我們直接可以在 html 頁面中直接使用
<div id="app"> {{ message }} </div> // Vue 就是 掛載到 window 上的,所以可以直接在頁面使用 var app = new Vue({ el: '#app', data: { message: 'Hello Vue!' } })
此時我們可以通過 webpack Authoring Libraries 來了解如何利用 webpack 開發第三方包。
優勢與缺陷
優勢
對于這種既無法進行代碼分割又無法進行 Tree Shaking 的依賴庫而言,把這些需求的依賴庫放置到公用 cdn 中,收益是非常大的。
缺陷
對于類似 Vue React 此類庫而言,CDN 服務出現問題意味著完全無法使用項目。需要經常瀏覽所使用 CDN 服務商的公告(不再提供服務等公告),以及在代碼中添加類似的出錯彌補方案。
<script src="//cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.runtime.min.js" crossorigin="anonymous"></script> <script>window.Vue || ...其他處理 </script>
webpack dynamic import 提升代碼覆蓋率
我們可以利用 webpack 動態導入,可以在需要利用代碼時候調用 getComponent。在此之前,需要對 webpack 進行配置。具體參考 webpack dynamic-imports。
在配置完成之后,我們就可以寫如下代碼。
async function getComponent() { const element = document.createElement('div'); /** webpackChunkName,相同的名稱會打包到一個 chunk 中 */ const { default: _ } = await import(/* webpackChunkName: "lodash" */ 'lodash'); element.innerHTML = _.join(['Hello', 'webpack'], ' '); return element; } getComponent().then(component => { document.body.appendChild(component); });
優勢與缺陷
優勢
通過動態導入配置,可以搞定多個 chunk,在需要時候才會加載而后執行。對于該用戶不會使用的資源(路由控制,權限控制)不會進行加載,從而直接提升了代碼的覆蓋率。
缺陷
Tree Shaking,可以理解為死代碼消除,即不需要的代碼不進行構建與打包。但當我們使用動態導入時候,無法使用 Tree Shaking 優化,因為兩者直接按存在著兼容性問題。因為 webpack 無法假設用戶如何使用動態導入的情況。
基礎代碼X 模塊A 模塊B ----------------------------------- 業務代碼A 業務代碼B 業務代碼...
當在業務中使用多個異步塊時后,業務代碼A 需求 模塊A,業務代碼 B 需求 模塊B,但是 webpack 無法去假設用戶在代碼中 A 與 B 這兩個模塊在同一時間是互斥還是互補。所以必然會假設同時可以加載模塊 A 與 B,此時基礎代碼 X 出現兩個導出狀態,這個是做不到的!從這方面來說,動態導入和 Tree Shaking 很難兼容。具體可以參考 Document why tree shaking is not performed on async chunks 。
當然,利用動態導入,也會有一定的性能降低,畢竟一個是本地函數調用,另一個涉及網絡請求與編譯。但是與其說這是一種缺陷,倒不如說是一種決策。究竟是哪一種對自身的項目幫助更大?
使用 loadjs 來輔助加載第三方 cdn 資源
在普通的業務代碼我們可以使用動態導入,在當今的前端項目中,總有一些庫是我們必需而又使用率很低的庫,比如在只會在統計模塊出現的 ECharts 數據圖表庫,或者只會在文檔或者網頁編輯時候出現的富文本編輯器庫。
對于這些苦庫其實我們可以使用頁面或組件掛載時候 loadjs 加載。因為使用動態導入這些第三方庫沒有 Tree shaking 增強,所以其實效果差不多,但是 loadjs 可以去取公用 CDN 資源。具體可以參考 github loadjs 來進行使用。因為該庫較為簡單,這里暫時就不進行深入探討。
使用 output.publicPath 托管代碼
因為無論是使用 webpack externals 或者 loadjs 來使用公用 cdn 都是一種折衷方案。如果公司可以花錢購買 oss + cdn 服務的話,就可以直接將打包的資源托管上去。
module.exports = { //... output: { // 每個塊的前綴 publicPath: 'https://xx/', chunkFilename: '[id].chunk.js' } }; // 此時打包出來的數據前綴會變為 <script src=https://xx/js/app.a74ade86.js></script>
此時業務服務器僅僅只需要加載 index.html。
利用 prefetch 在空缺時間加載資源
如果不需要在瀏覽器的首屏中使用腳本。可以利用瀏覽器新增的 prefetch 延時獲取腳本。
下面這段代碼告訴瀏覽器,echarts 將會在未來某個導航或者功能中要使用到,但是資源的下載順序權重比較低。也就是說prefetch通常用于加速下一次導航。被標記為 prefetch 的資源,將會被瀏覽器在空閑時間加載。
<link rel="prefetch" href="https://cdn.jsdelivr.net/npm/echarts@4.3.0/dist/echarts.min.js"></link>
該功能也適用于 html 以及 css 資源的預請求。
利用 instant.page 來提前加載資源
instant.page 是一個較新的功能庫,該庫小而美。并且無侵入式。 只要在項目的 </body> 之前加入以下代碼,便會得到收益。
<script src="//instant.page/2.0.1" type="module" defer integrity="sha384-4Duao6N1ACKAViTLji8I/8e8H5Po/i/04h5rS5f9fQD6bXBBZhqv5am3/Bf/xalr"></script>
該方案不適合單頁面應用,但是該庫很棒的運用了 prefetch,是在你懸停于鏈接超過65ms 時候,把已經放入的 head 最后的 link 改為懸停鏈接的 href。
下面代碼是主要代碼
// 加載 prefetcher const prefetcher = document.createElement('link') // 查看是否支持 prefetcher const isSupported = prefetcher.relList && prefetcher.relList.supports && prefetcher.relList.supports('prefetch') // 懸停時間 65 ms let delayOnHover = 65 // 讀取設定在 腳本上的 instantIntensity, 如果有 修改懸停時間 const milliseconds = parseInt(document.body.dataset.instantIntensity) if (!isNaN(milliseconds)) { delayOnHover = milliseconds } // 支持 prefetch 且 沒有開啟數據保護模式 if (isSupported && !isDataSaverEnabled) { prefetcher.rel = 'prefetch' document.head.appendChild(prefetcher) ... // 鼠標懸停超過 instantIntensit ms || 65ms 改變 href 以便預先獲取 html mouseoverTimer = setTimeout(() => { preload(linkElement.href) mouseoverTimer = undefined }, delayOnHover) ... function preload(url) { prefetcher.href = url }
延時 prefetch ? 還是在鼠標停留的時候去加載。不得不說,該庫利用了很多瀏覽器新的的機制。包括使用 type=module 來拒絕舊的瀏覽器執行,利用 dataset 讀取 instantIntensity 來控制延遲時間。
optimize-js 跳過 v8 pre-Parse 優化代碼性能
認識到這個庫是在 v8 關于新版本的文章中,在 github 中被標記為 UNMAINTAINED 不再維護,但是了解與學習該庫仍舊有其的價值與意義。該庫的用法十分簡單粗暴。居然只是把函數改為 IIFE(立即執行函數表達式)。 用法如下:
optimize-js input.js > output.js
Example input:
!function (){}() function runIt(fun){ fun() } runIt(function (){})
Example output:
!(function (){})() function runIt(fun){ fun() } runIt((function (){}))
原理
在 v8 引擎內部(不僅僅是 V8,在這里以 v8 為例子),位于各個編譯器的前置Parse 被分為 Pre-Parse 與 Full-Parse,Pre-Parse 會對整個 Js 代碼進行檢查,通過檢查可以直接判定存在語法錯誤,直接中斷后續的解析,在此階段,Parse 不會生成源代碼的AST結構。
// This is the top-level scope. function outer() { // preparsed 這里會預分析 function inner() { // preparsed 這里會預分析 但是不會 全分析和編譯 } } outer(); // Fully parses and compiles `outer`, but not `inner`.
但是如果使用 IIFE,v8 引擎直接不會進行 Pre-Parsing 操作,而是立即完全解析并編譯函數。可以參考Blazingly fast parsing, part 2: lazy parsing
優勢與缺陷
優勢
快!即使在較新的 v8 引擎上,我們可以看到 optimize-js 的速度依然是最快的。更不用說在國內瀏覽器的版本遠遠小于 v8 當前版本。與后端 node 不同,前端的頁面生命周期很短,越快執行越好。
缺陷
但是同樣的,任何技術都不是銀彈,直接完全解析和編譯也會造成內存壓力,并且該庫也不是 js 引擎推薦的用法。相信在不遠的未來,該庫的收益也會逐漸變小,但是對于某些特殊需求,該庫的確會又一定的助力。
再聊代碼覆蓋率
此時我們在談一次代碼覆蓋率。如果我們可以在首屏記載的時候可以達到很高的代碼覆蓋率。直接執行便是更好的方式。在項目中代碼覆蓋率越高,越過 Pre-Parsing 讓代碼盡快執行的收益也就越大。
Polyfill.io 根據不同的瀏覽器確立不同的 polyfill
如果寫過前端,就不可能不知道 polyfill。各個瀏覽器版本不同,所需要的 polyfill 也不同,
Polyfill.io是一項服務,可通過選擇性地填充瀏覽器所需的內容來減少 Web 開發的煩惱。Polyfill.io讀取每個請求的User-Agent 標頭,并返回適合于請求瀏覽器的polyfill。
如果是最新的瀏覽器且具有 Array.prototype.filter
https://polyfill.io/v3/polyfill.min.js?features=Array.prototype.filter /* Disable minification (remove `.min` from URL path) for more info */
如果沒有 就會在 正文下面添加有關的 polyfill。
國內的阿里巴巴也搭建了一個服務,可以考慮使用,網址為 https://polyfill.alicdn.com/polyfill.min.js
type='module' 輔助打包與部署 es2015+ 代碼
使用新的 DOM API,可以有條件地加載polyfill,因為可以在運行時檢測。但是,使用新的 JavaScript 語法,這會非常棘手,因為任何未知的語法都會導致解析錯誤,然后所有代碼都不會運行。
該問題的解決方法是
<script type="module">。
早在 2017 年,我便知道 type=module 可以直接在瀏覽器原生支持模塊的功能。具體可以參考 JavaScript modules 模塊。但是當時感覺只是這個功能很強大,并沒有對這個功能產生什么解讀。但是卻沒有想到可以利用該功能識別你的瀏覽器是否支持 ES2015。
每個支持 type="module" 的瀏覽器都支持你所熟知的大部分 ES2015+ 語法!!!!!
例如
async await 函數原生支持
箭頭函數 原生支持
Promises Map Set 等語法原生支持
因此,利用該特性,完全可以去做優雅降級。在支持 type=module 提供所屬的 js,而在 不支持的情況下 提供另一個js。具體可以參考 Phillip Walton 精彩的博文,這里也有翻譯版本 https://jdc.jd.com/archives/4911.
Vue CLI 現代模式
如果當前項目已經開始從 webpack 陣營轉到 Vue CLI 陣營的話,那么恭喜你,上述解決方案已經被內置到 Vue CLI 當中去了。只需要使用如下指令,項目便會產生兩個版本的包。
vue-cli-service build --modern
具體可以參考 Vue CLI 現代模式
優勢與缺陷
優勢
提升代碼覆蓋率,直接使用原生的 await 等語法,直接減少大量代碼。
提升代碼性能。之前 v8 用的時 Crankshaft 編譯器,隨著時間的推移,該編譯器因為無法優化現代語言特性而被拋棄,之后 v8 引入了新的 Turbofan 編譯器來對新語言特性進行支持與優化,之前在社區中談論的 try catch, await,JSON 正則等性能都有了很大的提升。具體可以時常瀏覽 v8 blog 來查看功能優化。
Writing ES2015 code is a win for developers, and deploying ES2015 code is a win for users.
“優化Web應用程序性能的方案及其優缺點說明”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。