溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

優(yōu)化Web應(yīng)用程序性能的方案及其優(yōu)缺點(diǎn)說明

發(fā)布時(shí)間:2021-09-04 09:21:50 來源:億速云 閱讀:123 作者:chen 欄目:web開發(fā)

本篇內(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)卡

優(yōu)化Web應(yīng)用程序性能的方案及其優(yōu)缺點(diǎn)說明

webpackjs

  •  其中如果想要查詢頁面加載時(shí)候使用的代碼,請點(diǎn)擊 reload button

  •  如果您想查看與頁面交互后使用的代碼,請點(diǎn)擊record buton

這里以淘寶網(wǎng)為例子,介紹一下如何使用

優(yōu)化Web應(yīng)用程序性能的方案及其優(yōu)缺點(diǎn)說明

優(yōu)化Web應(yīng)用程序性能的方案及其優(yōu)缺點(diǎn)說明

上面兩張分別為 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   // &fnof; 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)勢

優(yōu)化Web應(yīng)用程序性能的方案及其優(yōu)缺點(diǎn)說明

快!即使在較新的 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í)用文章!

向AI問一下細(xì)節(jié)

免責(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)容。

web
AI