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