前言
Vue 框架通過數(shù)據(jù)雙向綁定和虛擬 DOM 技術(shù)侮腹,幫我們處理了前端開發(fā)中最臟最累的 DOM 操作部分梨与, 我們不再需要去考慮如何操作 DOM 以及如何最高效地操作 DOM固惯;但 Vue 項目中仍然存在項目首屏優(yōu)化、Webpack 編譯配置優(yōu)化等問題,所以我們?nèi)匀恍枰リP(guān)注 Vue 項目性能方面的優(yōu)化臭觉,使項目具有更高效的性能聪舒、更好的用戶體驗辨液。本文是作者通過實際項目的優(yōu)化實踐進行總結(jié)而來,希望讀者讀完本文箱残,有一定的啟發(fā)思考滔迈,從而對自己的項目進行優(yōu)化起到幫助。本文內(nèi)容分為以下三部分組成:
Vue 代碼層面的優(yōu)化;
webpack 配置層面的優(yōu)化亡鼠;
基礎(chǔ)的 Web 技術(shù)層面的優(yōu)化赏殃。
一、代碼層面的優(yōu)化
1.1间涵、v-if 和 v-show 區(qū)分使用場景
v-if 是 真正 的條件渲染仁热,因為它會確保在切換過程中條件塊內(nèi)的事件監(jiān)聽器和子組件適當?shù)乇讳N毀和重建;也是惰性的:如果在初始渲染時條件為假勾哩,則什么也不做——直到條件第一次變?yōu)檎鏁r抗蠢,才會開始渲染條件塊。
v-show 就簡單得多思劳, 不管初始條件是什么迅矛,元素總是會被渲染,并且只是簡單地基于 CSS 的 display 屬性進行切換潜叛。
所以秽褒,v-if 適用于在運行時很少改變條件,不需要頻繁切換條件的場景威兜;v-show 則適用于需要非常頻繁切換條件的場景销斟。
1.2、computed 和 watch 區(qū)分使用場景
computed: 是計算屬性椒舵,依賴其它屬性值蚂踊,并且 computed 的值有緩存,只有它依賴的屬性值發(fā)生改變笔宿,下一次獲取 computed 的值時才會重新計算 computed 的值犁钟;
watch: 更多的是「觀察」的作用,類似于某些數(shù)據(jù)的監(jiān)聽回調(diào) 泼橘,每當監(jiān)聽的數(shù)據(jù)變化時都會執(zhí)行回調(diào)進行后續(xù)操作涝动;
運用場景:
當我們需要進行數(shù)值計算,并且依賴于其它數(shù)據(jù)時侥加,應(yīng)該使用 computed捧存,因為可以利用 computed 的緩存特性,避免每次獲取值時担败,都要重新計算昔穴;
當我們需要在數(shù)據(jù)變化時執(zhí)行異步或開銷較大的操作時,應(yīng)該使用 watch提前,使用 watch 選項允許我們執(zhí)行異步操作 ( 訪問一個 API )吗货,限制我們執(zhí)行該操作的頻率,并在我們得到最終結(jié)果前狈网,設(shè)置中間狀態(tài)宙搬。這些都是計算屬性無法做到的笨腥。
1.3、v-for 遍歷必須為 item 添加 key勇垛,且避免同時使用 v-if
(1)v-for 遍歷必須為 item 添加 key
在列表數(shù)據(jù)進行遍歷渲染時脖母,需要為每一項 item 設(shè)置唯一 key 值,方便 Vue.js 內(nèi)部機制精準找到該條列表數(shù)據(jù)闲孤。當 state 更新時谆级,新的狀態(tài)值和舊的狀態(tài)值對比,較快地定位到 diff 讼积。
(2)v-for 遍歷避免同時使用 v-if
v-for 比 v-if 優(yōu)先級高肥照,如果每一次都需要遍歷整個數(shù)組,將會影響速度勤众,尤其是當之需要渲染很小一部分的時候舆绎,必要情況下應(yīng)該替換成 computed 屬性。
推薦:
<ul>
<li
v-for="user in activeUsers"
:key="user.id">
{{ user.name }}
</li>
</ul>
computed: {
activeUsers: function () {
return this.users.filter(function (user) {
return user.isActive
})
}
}
不推薦:
<ul>
<li
v-for="user in users"
v-if="user.isActive"
:key="user.id">
{{ user.name }}
</li>
</ul>
1.4们颜、長列表性能優(yōu)化
Vue 會通過 Object.defineProperty 對數(shù)據(jù)進行劫持吕朵,來實現(xiàn)視圖響應(yīng)數(shù)據(jù)的變化,然而有些時候我們的組件就是純粹的數(shù)據(jù)展示窥突,不會有任何改變边锁,我們就不需要 Vue 來劫持我們的數(shù)據(jù),在大量數(shù)據(jù)展示的情況下波岛,這能夠很明顯的減少組件初始化的時間,那如何禁止 Vue 劫持我們的數(shù)據(jù)呢音半?可以通過 Object.freeze 方法來凍結(jié)一個對象则拷,一旦被凍結(jié)的對象就再也不能被修改了。
export default {
data: () => ({
users: {}
}),
async created() {
const users = await axios.get("/api/users");
this.users = Object.freeze(users);
}
};
1.5曹鸠、事件的銷毀
Vue 組件銷毀時煌茬,會自動清理它與其它實例的連接,解綁它的全部指令及事件監(jiān)聽器彻桃,但是僅限于組件本身的事件坛善。如果在 js 內(nèi)
created() {
addEventListener('click', this.click, false)
},
beforeDestroy() {
removeEventListener('click', this.click, false)
}
1.6、圖片資源懶加載
對于圖片過多的頁面邻眷,為了加速頁面加載速度眠屎,所以很多時候我們需要將頁面內(nèi)未出現(xiàn)在可視區(qū)域內(nèi)的圖片先不做加載, 等到滾動到可視區(qū)域后再去加載肆饶。這樣對于頁面加載性能上會有很大的提升改衩,也提高了用戶體驗。我們在項目中使用 Vue 的 vue-lazyload 插件:
(1)安裝插件
npm install vue-lazyload --save-dev
(2)在入口文件 man.js 中引入并使用
import VueLazyload from 'vue-lazyload'
然后再 vue 中直接使用
Vue.use(VueLazyload)
或者添加自定義選項
Vue.use(VueLazyload, {
preLoad: 1.3,
error: 'dist/error.png',
loading: 'dist/loading.gif',
attempt: 1
})
(3)在 vue 文件中將 img 標簽的 src 屬性直接改為 v-lazy 驯镊,從而將圖片顯示方式更改為懶加載顯示:
<img v-lazy="/static/img/1.png">
以上為 vue-lazyload 插件的簡單使用葫督,如果要看插件的更多參數(shù)選項竭鞍,可以查看 vue-lazyload 的 github 地址。
1.7橄镜、路由懶加載
Vue 是單頁面應(yīng)用偎快,可能會有很多的路由引入 ,這樣使用 webpcak 打包后的文件很大洽胶,當進入首頁時晒夹,加載的資源過多,頁面會出現(xiàn)白屏的情況妖异,不利于用戶體驗惋戏。如果我們能把不同路由對應(yīng)的組件分割成不同的代碼塊,然后當路由被訪問的時候才加載對應(yīng)的組件他膳,這樣就更加高效了响逢。這樣會大大提高首屏顯示的速度,但是可能其他的頁面的速度就會降下來棕孙。
路由懶加載:
const Foo = () => import('./Foo.vue')
const router = new VueRouter({
routes: [
{ path: '/foo', component: Foo }
]
})
1.8舔亭、第三方插件的按需引入
我們在項目中經(jīng)常會需要引入第三方插件,如果我們直接引入整個插件蟀俊,會導(dǎo)致項目的體積太大钦铺,我們可以借助 babel-plugin-component ,然后可以只引入需要的組件肢预,以達到減小項目體積的目的矛洞。以下為項目中引入 element-ui 組件庫為例:
(1)首先,安裝 babel-plugin-component :
npm install babel-plugin-component -D
(2)然后烫映,將 .babelrc 修改為:
{
"presets": [["es2015", { "modules": false }]],
"plugins": [
[
"component",
{
"libraryName": "element-ui",
"styleLibraryName": "theme-chalk"
}
]
]
}
(3)在 main.js 中引入部分組件:
import Vue from 'vue';
import { Button, Select } from 'element-ui';
Vue.use(Button)
Vue.use(Select)
1.9沼本、優(yōu)化無限列表性能
如果你的應(yīng)用存在非常長或者無限滾動的列表,那么需要采用 窗口化 的技術(shù)來優(yōu)化性能锭沟,只需要渲染少部分區(qū)域的內(nèi)容抽兆,減少重新渲染組件和創(chuàng)建 dom 節(jié)點的時間。你可以參考以下開源項目 vue-virtual-scroll-list 和 vue-virtual-scroller 來優(yōu)化這種無限列表的場景的族淮。
1.10辫红、服務(wù)端渲染 SSR or 預(yù)渲染
服務(wù)端渲染是指 Vue 在客戶端將標簽渲染成的整個 html 片段的工作在服務(wù)端完成,服務(wù)端形成的 html 片段直接返回給客戶端這個過程就叫做服務(wù)端渲染祝辣。
(1)服務(wù)端渲染的優(yōu)點:
更好的 SEO:因為 SPA 頁面的內(nèi)容是通過 Ajax 獲取贴妻,而搜索引擎爬取工具并不會等待 Ajax 異步完成后再抓取頁面內(nèi)容,所以在 SPA 中是抓取不到頁面通過 Ajax 獲取到的內(nèi)容蝙斜;而 SSR 是直接由服務(wù)端返回已經(jīng)渲染好的頁面(數(shù)據(jù)已經(jīng)包含在頁面中)揍瑟,所以搜索引擎爬取工具可以抓取渲染好的頁面;
更快的內(nèi)容到達時間(首屏加載更快):SPA 會等待所有 Vue 編譯后的 js 文件都下載完成后乍炉,才開始進行頁面的渲染绢片,文件下載等需要一定的時間等滤馍,所以首屏渲染需要一定的時間;SSR 直接由服務(wù)端渲染好頁面直接返回顯示底循,無需等待下載 js 文件及再去渲染等巢株,所以 SSR 有更快的內(nèi)容到達時間;
(2)服務(wù)端渲染的缺點:
更多的開發(fā)條件限制:例如服務(wù)端渲染只支持 beforCreate 和 created 兩個鉤子函數(shù)熙涤,這會導(dǎo)致一些外部擴展庫需要特殊處理阁苞,才能在服務(wù)端渲染應(yīng)用程序中運行;并且與可以部署在任何靜態(tài)文件服務(wù)器上的完全靜態(tài)單頁面應(yīng)用程序 SPA 不同祠挫,服務(wù)端渲染應(yīng)用程序那槽,需要處于 Node.js server 運行環(huán)境;
更多的服務(wù)器負載:在 Node.js 中渲染完整的應(yīng)用程序等舔,顯然會比僅僅提供靜態(tài)文件的 server 更加大量占用CPU 資源骚灸,因此如果你預(yù)料在高流量環(huán)境下使用,請準備相應(yīng)的服務(wù)器負載慌植,并明智地采用緩存策略甚牲。
如果你的項目的 SEO 和 首屏渲染是評價項目的關(guān)鍵指標,那么你的項目就需要服務(wù)端渲染來幫助你實現(xiàn)最佳的初始加載性能和 SEO蝶柿,具體的 Vue SSR 如何實現(xiàn)丈钙,可以參考作者的另一篇文章《Vue SSR 踩坑之旅》。如果你的 Vue 項目只需改善少數(shù)營銷頁面(例如 /交汤, /about雏赦, /contact 等)的 SEO,那么你可能需要預(yù)渲染芙扎,在構(gòu)建時 (build time) 簡單地生成針對特定路由的靜態(tài) HTML 文件喉誊。優(yōu)點是設(shè)置預(yù)渲染更簡單,并可以將你的前端作為一個完全靜態(tài)的站點纵顾,具體你可以使用 prerender-spa-plugin 就可以輕松地添加預(yù)渲染 。
二栋盹、Webpack 層面的優(yōu)化
2.1施逾、Webpack 對圖片進行壓縮
在 vue 項目中除了可以在 webpack.base.conf.js 中 url-loader 中設(shè)置 limit 大小來對圖片處理,對小于 limit 的圖片轉(zhuǎn)化為 base64 格式例获,其余的不做操作汉额。所以對有些較大的圖片資源,在請求資源的時候榨汤,加載會很慢蠕搜,我們可以用 image-webpack-loader來壓縮圖片:
(1)首先,安裝 image-webpack-loader :
npm install image-webpack-loader --save-dev
(2)然后收壕,在 webpack.base.conf.js 中進行配置:
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
use:[
{
loader: 'url-loader',
options: {
limit: 10000,
name: utils.assetsPath('img/[name].[hash:7].[ext]')
}
},
{
loader: 'image-webpack-loader',
options: {
bypassOnDebug: true,
}
}
]
}
2.2妓灌、減少 ES6 轉(zhuǎn)為 ES5 的冗余代碼
Babel 插件會在將 ES6 代碼轉(zhuǎn)換成 ES5 代碼時會注入一些輔助函數(shù)轨蛤,例如下面的 ES6 代碼:
class HelloWebpack extends Component{...}
這段代碼再被轉(zhuǎn)換成能正常運行的 ES5 代碼時需要以下兩個輔助函數(shù):
babel-runtime/helpers/createClass // 用于實現(xiàn) class 語法
babel-runtime/helpers/inherits // 用于實現(xiàn) extends 語法
在默認情況下, Babel 會在每個輸出文件中內(nèi)嵌這些依賴的輔助函數(shù)代碼虫埂,如果多個源代碼文件都依賴這些輔助函數(shù)祥山,那么這些輔助函數(shù)的代碼將會出現(xiàn)很多次,造成代碼冗余掉伏。為了不讓這些輔助函數(shù)的代碼重復(fù)出現(xiàn)缝呕,可以在依賴它們時通過 require('babel-runtime/helpers/createClass') 的方式導(dǎo)入,這樣就能做到只讓它們出現(xiàn)一次斧散。babel-plugin-transform-runtime 插件就是用來實現(xiàn)這個作用的供常,將相關(guān)輔助函數(shù)進行替換成導(dǎo)入語句,從而減小 babel 編譯出來的代碼的文件大小鸡捐。
(1)首先栈暇,安裝 babel-plugin-transform-runtime :
npm install babel-plugin-transform-runtime --save-dev
(2)然后,修改 .babelrc 配置文件為:
"plugins": [
"transform-runtime"
]
如果要看插件的更多詳細內(nèi)容闯参,可以查看babel-plugin-transform-runtime 的 詳細介紹瞻鹏。
2.3、提取公共代碼
如果項目中沒有去將每個頁面的第三方庫和公共模塊提取出來鹿寨,則項目會存在以下問題:
相同的資源被重復(fù)加載新博,浪費用戶的流量和服務(wù)器的成本。
每個頁面需要加載的資源太大脚草,導(dǎo)致網(wǎng)頁首屏加載緩慢赫悄,影響用戶體驗。
所以我們需要將多個頁面的公共代碼抽離成單獨的文件馏慨,來優(yōu)化以上問題 埂淮。Webpack 內(nèi)置了專門用于提取多個Chunk 中的公共部分的插件 CommonsChunkPlugin,我們在項目中 CommonsChunkPlugin 的配置如下:
// 所有在 package.json 里面依賴的包写隶,都會被打包進 vendor.js 這個文件中倔撞。
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
minChunks: function(module, count) {
return (
module.resource &&
/\.js$/.test(module.resource) &&
module.resource.indexOf(
path.join(__dirname, '../node_modules')
) === 0
);
}
}),
// 抽取出代碼模塊的映射關(guān)系
new webpack.optimize.CommonsChunkPlugin({
name: 'manifest',
chunks: ['vendor']
})
如果要看插件的更多詳細內(nèi)容,可以查看 CommonsChunkPlugin 的 詳細介紹慕趴。
2.4痪蝇、模板預(yù)編譯
當使用 DOM 內(nèi)模板或 JavaScript 內(nèi)的字符串模板時,模板會在運行時被編譯為渲染函數(shù)冕房。通常情況下這個過程已經(jīng)足夠快了躏啰,但對性能敏感的應(yīng)用還是最好避免這種用法。
預(yù)編譯模板最簡單的方式就是使用單文件組件——相關(guān)的構(gòu)建設(shè)置會自動把預(yù)編譯處理好耙册,所以構(gòu)建好的代碼已經(jīng)包含了編譯出來的渲染函數(shù)而不是原始的模板字符串给僵。
如果你使用 webpack,并且喜歡分離 JavaScript 和模板文件详拙,你可以使用 vue-template-loader帝际,它也可以在構(gòu)建過程中把模板文件轉(zhuǎn)換成為 JavaScript 渲染函數(shù)蔓同。
2.5、提取組件的 CSS
當使用單文件組件時胡本,組件內(nèi)的 CSS 會以 style 標簽的方式通過 JavaScript 動態(tài)注入牌柄。這有一些小小的運行時開銷,如果你使用服務(wù)端渲染侧甫,這會導(dǎo)致一段 “無樣式內(nèi)容閃爍 (fouc) ” 珊佣。將所有組件的 CSS 提取到同一個文件可以避免這個問題,也會讓 CSS 更好地進行壓縮和緩存披粟。
查閱這個構(gòu)建工具各自的文檔來了解更多:
webpack + vue-loader ( vue-cli 的 webpack 模板已經(jīng)預(yù)先配置好)
Browserify + vueify
Rollup + rollup-plugin-vue
2.6咒锻、優(yōu)化 SourceMap
我們在項目進行打包后,會將開發(fā)中的多個文件代碼打包到一個文件中守屉,并且經(jīng)過壓縮惑艇、去掉多余的空格、babel編譯化后拇泛,最終將編譯得到的代碼會用于線上環(huán)境滨巴,那么這樣處理后的代碼和源代碼會有很大的差別,當有 bug的時候俺叭,我們只能定位到壓縮處理后的代碼位置恭取,無法定位到開發(fā)環(huán)境中的代碼,對于開發(fā)來說不好調(diào)式定位問題熄守,因此 sourceMap 出現(xiàn)了蜈垮,它就是為了解決不好調(diào)式代碼問題的。
SourceMap 的可選值如下(+ 號越多裕照,代表速度越快攒发,- 號越多,代表速度越慢, o 代表中等速度 )
開發(fā)環(huán)境推薦:cheap-module-eval-source-map
生產(chǎn)環(huán)境推薦:cheap-module-source-map
原因如下:
cheap:源代碼中的列信息是沒有任何作用晋南,因此我們打包后的文件不希望包含列相關(guān)信息惠猿,只有行信息能建立打包前后的依賴關(guān)系。因此不管是開發(fā)環(huán)境或生產(chǎn)環(huán)境负间,我們都希望添加 cheap 的基本類型來忽略打包前后的列信息偶妖;
module :不管是開發(fā)環(huán)境還是正式環(huán)境,我們都希望能定位到bug的源代碼具體的位置唉擂,比如說某個 Vue 文件報錯了,我們希望能定位到具體的 Vue 文件檀葛,因此我們也需要 module 配置玩祟;
soure-map :source-map 會為每一個打包后的模塊生成獨立的 soucemap 文件 ,因此我們需要增加source-map 屬性屿聋;
eval-source-map:eval 打包代碼的速度非晨赵快藏鹊,因為它不生成 map 文件,但是可以對 eval 組合使用 eval-source-map 使用會將 map 文件以 DataURL 的形式存在打包后的 js 文件中转锈。在正式環(huán)境中不要使用 eval-source-map, 因為它會增加文件的大小盘寡,但是在開發(fā)環(huán)境中,可以試用下撮慨,因為他們打包的速度很快竿痰。
2.7、構(gòu)建結(jié)果輸出分析
Webpack 輸出的代碼可讀性非常差而且文件非常大砌溺,讓我們非常頭疼影涉。為了更簡單、直觀地分析輸出結(jié)果规伐,社區(qū)中出現(xiàn)了許多可視化分析工具蟹倾。這些工具以圖形的方式將結(jié)果更直觀地展示出來,讓我們快速了解問題所在猖闪。接下來講解我們在 Vue 項目中用到的分析工具:webpack-bundle-analyzer 鲜棠。
我們在項目中 webpack.prod.conf.js 進行配置:
if (config.build.bundleAnalyzerReport) {
var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
webpackConfig.plugins.push(new BundleAnalyzerPlugin());
}
執(zhí)行 $ npm run build --report 后生成分析報告如下:
2.8、Vue 項目的編譯優(yōu)化
如果你的 Vue 項目使用 Webpack 編譯培慌,需要你喝一杯咖啡的時間豁陆,那么也許你需要對項目的 Webpack 配置進行優(yōu)化,提高 Webpack 的構(gòu)建效率检柬。具體如何進行 Vue 項目的 Webpack 構(gòu)建優(yōu)化献联,可以參考作者的另一篇文章《 Vue 項目 Webpack 優(yōu)化實踐》
三、基礎(chǔ)的 Web 技術(shù)優(yōu)化
3.1何址、開啟 gzip 壓縮
gzip 是 GNUzip 的縮寫里逆,最早用于 UNIX 系統(tǒng)的文件壓縮。HTTP 協(xié)議上的 gzip 編碼是一種用來改進 web 應(yīng)用程序性能的技術(shù)用爪,web 服務(wù)器和客戶端(瀏覽器)必須共同支持 gzip原押。目前主流的瀏覽器,Chrome偎血,firefox诸衔,IE等都支持該協(xié)議。常見的服務(wù)器如 Apache颇玷,Nginx笨农,IIS 同樣支持,gzip 壓縮效率非常高帖渠,通弛艘啵可以達到 70% 的壓縮率,也就是說,如果你的網(wǎng)頁有 30K份招,壓縮之后就變成了 9K 左右
以下我們以服務(wù)端使用我們熟悉的 express 為例切揭,開啟 gzip 非常簡單,相關(guān)步驟如下:
- 安裝:
npm install compression --save
- 添加代碼邏輯:
var compression = require('compression');
var app = express();
app.use(compression())
- 重啟服務(wù)锁摔,觀察網(wǎng)絡(luò)面板里面的 response header廓旬,如果看到如下紅圈里的字段則表明 gzip 開啟成功
3.2、瀏覽器緩存
為了提高用戶加載頁面的速度谐腰,對靜態(tài)資源進行緩存是非常必要的孕豹,根據(jù)是否需要重新向服務(wù)器發(fā)起請求來分類,將 HTTP 緩存規(guī)則分為兩大類(強制緩存怔蚌,對比緩存)巩步,如果對緩存機制還不是了解很清楚的,可以參考作者寫的關(guān)于 HTTP 緩存的文章《深入理解HTTP緩存機制及原理》桦踊,這里不再贅述椅野。
3.3、CDN 的使用
瀏覽器從服務(wù)器上下載 CSS籍胯、js 和圖片等文件時都要和服務(wù)器連接竟闪,而大部分服務(wù)器的帶寬有限,如果超過限制杖狼,網(wǎng)頁就半天反應(yīng)不過來炼蛤。而 CDN 可以通過不同的域名來加載文件,從而使下載文件的并發(fā)連接數(shù)大大增加蝶涩,且CDN 具有更好的可用性理朋,更低的網(wǎng)絡(luò)延遲和丟包率 。
3.4绿聘、使用 Chrome Performance 查找性能瓶頸
Chrome 的 Performance 面板可以錄制一段時間內(nèi)的 js 執(zhí)行細節(jié)及時間嗽上。使用 Chrome 開發(fā)者工具分析頁面性能的步驟如下。
打開 Chrome 開發(fā)者工具熄攘,切換到 Performance 面板
點擊 Record 開始錄制
刷新頁面或展開某個節(jié)點
點擊 Stop 停止錄制
原文鏈接:https://blog.csdn.net/qq_37939251/article/details/100031285