??????眾所周知择卦,Vue幫我們做了很多"臟活累活",例如:數(shù)據(jù)的雙向綁定衫仑;Virtual Dom技術等,我們可以把大部分的時間抽出來去實現(xiàn)我們的業(yè)務邏輯。但是凰锡,我們?nèi)匀恍枰P注vue的首屏優(yōu)化未舟,webpack的配置優(yōu)化,資源loading的快慢寡夹,vue項目運行時的性能優(yōu)化等等处面。為了更好地提升我們的網(wǎng)站用戶體驗,下面我將通過實際項目的優(yōu)化實踐分模塊總結:
- 基礎web技術層面的優(yōu)化
- webpack打包配置方面的優(yōu)化
- vue代碼層面的優(yōu)化
- 其他優(yōu)化
為了更了解整個性能優(yōu)化過程菩掏,我們先來梳理一件事: 從輸入URL 到頁面加載完成發(fā)生了什么事魂角?
- 用戶輸入URL
- 瀏覽器先檢查本地是否有對應的IP地址,若找到則返回對應的IP地址智绸。若沒找到則請求上級DNS服務器野揪,直至找到或到根節(jié)點
- TCP連接進行三次握手
- 瀏覽器發(fā)送HTTP請求資源/數(shù)據(jù)
- 服務端處理請求進行回應
- 瀏覽器接收HTTP響應
- 瀏覽器渲染頁面访忿,構建DOM樹
- 瀏覽器關閉TCP連接(四次揮手)
??????從以上過程可以看出整個處理響應過程其實是三部分: 客戶端請求,服務端響應斯稳,客戶端接收響應海铆,如此可以發(fā)現(xiàn)前端能做的優(yōu)化其實是第一和第三部分:讓客戶端做出更有效且高效的請求,讓客戶端接收響應后更快速的渲染頁面挣惰,實現(xiàn)功能卧斟。下面我們具體分析如何進行優(yōu)化:
基礎web技術層面的優(yōu)化
頁面渲染性能的優(yōu)化
以下是瀏覽器渲染頁面的過程:
- DOMTree: 解析html構建DOM樹。
- CSSOMTree : 解析CSS生成CSSOM規(guī)則樹憎茂。
- RenderObjectTree: 將DOM樹與CSSOM規(guī)則樹合并在一起生成渲染對象樹珍语。
- Layout: 遍歷渲染樹開始布局(layout),計算每個節(jié)點的位置大小信息竖幔。
- Painting: 將渲染樹每個節(jié)點繪制到屏幕板乙。
具體的一些實踐做法:
1.防止阻塞渲染
??????由于CSS和JS會影響DOM樹和CSSOM的構建,所以瀏覽器在加載CSS和JS文件時會阻塞HTML的解析拳氢,為了避免阻塞募逞,我們可以做以下優(yōu)化:
- css 放在head標簽內(nèi),提前加載馋评。這樣做的原因是: 通常情況下 CSS 被認為是阻塞渲染的資源放接,在CSSOM 構建完成之前,頁面不會被渲染留特,放在頂部讓樣式表能夠盡早開始加載透乾。但如果把引入樣式表的 link 放在文檔底部,頁面雖然能立刻呈現(xiàn)出來磕秤,但是頁面加載出來的時候會是沒有樣式的,是混亂的捧韵。當后來樣式表加載進來后市咆,頁面會立即進行重繪,很可能會造成頁面閃爍再来。
- js文件放在body底部蒙兰,防止阻塞解析
- 首頁不使用或者不改變dom和css的js文件使用 defer 和 async 屬性進行異步加載,不阻塞解析
2.減少重繪和回流
- 盡量少用js訪問dom節(jié)點和css屬性,能用css解決的問題就不要用js去做
- 可能會涉及動畫的HTML元素可以使用使用fixed或absolute的定位芒篷,修改對應的CSS樣式就不會產(chǎn)生回流了
- img標簽設置高寬搜变,以減少重繪重排
- 盡量用 transform 來做形變和位移,減少使用left,top,這樣不會造成回流
3.減少DOM和CSSOM的構建時間
- DOM的層級盡量不要太深针炉,否則會增加DOM樹構建的時間挠他,js訪問深層的DOM也會造成更大的負擔。
- 減少 CSS 嵌套層級和選擇適當?shù)倪x擇器
需要服務端配合的操作:
GZIP壓縮
??????gzip 是 GNUzip 的縮寫篡帕,最早用于 UNIX 系統(tǒng)的文件壓縮殖侵。HTTP 協(xié)議上的 gzip 編碼是一種用來改進 web 應用程序性能的技術贸呢,web 服務器和客戶端(瀏覽器)必須共同支持 gzip。目前主流的瀏覽器拢军,Chrome楞陷,firefox,IE等都支持該協(xié)議茉唉。常見的服務器如 Apache固蛾,Nginx,IIS 同樣支持度陆,gzip 壓縮效率非常高艾凯,通常可以達到 70% 的壓縮率坚芜,也就是說览芳,如果你的網(wǎng)頁有 30K,壓縮之后就變成了 9K 左右鸿竖。重啟服務之后可以看到:
vue2.0使用webpack打包沧竟,會幫我們安裝好compression-webpack-plugin插件,并生成好對應的代碼:
if (config.build.productionGzip) {
const CompressionWebpackPlugin = require('compression-webpack-plugin')
webpackConfig.plugins.push(
new CompressionWebpackPlugin({
asset: '[path].gz[query]',
algorithm: 'gzip',
test: new RegExp(
'\\.(' +
config.build.productionGzipExtensions.join('|') +
')$'
),
threshold: 10240,
minRatio: 0.8
})
)
}
在index.js中開啟GZIP開關缚忧,剩下的就是要服務器去支持GZIP了
HTTP緩存
??????緩存的目的是簡化資源的請求路徑悟泵,比如某些靜態(tài)資源在客戶端已經(jīng)緩存了,再次請求這個資源闪水,只需要使用本地的緩存糕非,而無需走網(wǎng)絡請求去服務端獲取。具體的緩存規(guī)則服務器會將其放入http響應報文的response headers中與請求結果一起返回給瀏覽器球榆。
緩存類型:
緩存過程:
CDN使用
??????瀏覽器從服務器上下載 CSS朽肥、js 和圖片等文件時都要和服務器連接,而大部分服務器的帶寬有限持钉,如果超過限制衡招,網(wǎng)頁就半天反應不過來。而 CDN 可以通過不同的域名來加載文件每强,從而使下載文件的并發(fā)連接數(shù)大大增加始腾,且CDN 具有更好的可用性,更低的網(wǎng)絡延遲和丟包率
使用 Chrome Performance 查找性能瓶頸
chrome開發(fā)者工具性能分析工具 Performance 可以幫助我們監(jiān)控并分析頁面的性能情況空执,進而去采取對應的優(yōu)化措施:
- 打開 Chrome 開發(fā)者工具浪箭,切換到 Performance 面板
- 點擊 Record 開始錄制
- 刷新頁面或展開某個節(jié)點
- 點擊 Stop 停止錄制
webpack打包配置方面的優(yōu)化
ebpack-bundle-analyzer:查看資源樹 方便后續(xù)針對性的優(yōu)化
- 安裝
npm i webpack-bundle-analyzer -D
- 使用
//在webpack.prod.conf.js文件中加入以下代碼
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
new BundleAnalyzerPlugin({
analyzerMode: 'server', //server | static | disabled
analyzerHost: '127.0.0.1', // 默認值:127.0.0.1。 將在服務器模式下用于啟動HTTP服務器的主機辨绊。
analyzerPort: 8889, // 默認值:8888奶栖。將在服務器模式下用于啟動HTTP服務器的端口。
reportFilename: 'report.html', // 默認值:report.html。 捆綁將在靜態(tài)模式下生成的報告文件的路徑驼抹。 相對于bundle輸出目錄(在webpack配置中是output.path)桑孩。
defaultSizes: 'parsed', // 默認值:已解析。 默認情況下在報告中顯示的模塊大小框冀。 大小定義部分描述了這些值的含義流椒。
openAnalyzer: true, // 默認值:true。 在默認瀏覽器中自動打開報告明也。
generateStatsFile: false, // 默認值:false宣虾。 如果為true,將在bundle輸出目錄中生成webpack stats JSON文件
statsFilename: 'stats.json', // 默認值:stats.json温数。 如果generateStatsFile為true绣硝,將生成的webpack stats JSON文件的名稱。 相對于bundle輸出目錄撑刺。
statsOptions: null, // 默認值:null鹉胖。 stats.toJson()方法的選項。 例如够傍,您可以使用source:false選項從stats文件中排除模塊的源甫菠。 在這里查看更多選項。
logLevel: 'info' // 默認值:info冕屯, 用于控制插件輸出的詳細信息寂诱。
})
-
npm run build
image (2).png
下面是針對上述依賴圖進行的優(yōu)化:
第三方插件的按需引入
??????在我們的項目中引入了element-ui組件庫,在首屏需要加載依賴包安聘,其中element-ui就占據(jù)了553k,原本是直接引入整個插件痰洒,會導致項目的體積太大。現(xiàn)在對其改造浴韭,只引入需要的組件:
1.安裝babel-plugin-component:
npm install babel-plugin-component -D
- 修改.babelrc 文件:
{
"presets": [
["env", {
"modules": false,
"targets": {
"browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
}
}],
"stage-2"
],
"plugins": [
[
"component",
{
"libraryName": "element-ui",
"styleLibraryName": "theme-chalk"
}
]
]
}
- 修改main.js文件:
// 全局引入方式丘喻,打包后會放在vendor.js文件中,在首屏加載
import Vue from 'vue';
import { Button } from 'element-ui';
Vue.use(Button)
// 單文件引入方式念颈,打包后會放在各自路由的js文件中仓犬,跳轉到具體頁面才會加載對應的js文件,不會打包到vendor.js中
import { Table, TableColumn, Dialog, Button } from 'element-ui'
<script>
export default {
name: 'userCenter',
components: {
elTable: Table,
elTableColumn: TableColumn,
elDialog: Dialog,
elButton: Button
}
}
</script>
最后項目采用的是單文件引入所需要的組件舍肠,vendor.js的文件大小減小到267k
使用 CDN加載外部資源
??????項目中引用的第三方資源或者組件庫很多,比如vue,vue-router,axios,swiper等等窘面,在很多vue-處理搭建的項目都會用到翠语,我們可以采用cdn引入,從別的服務器上加載第三方庫而非自己的服務器财边,既能節(jié)省自己服務器的貸款肌括,又能減少vendor.js文件的大小,會比原來webpack打包后加載快不少。
// index.html
<head>
<!-- 引入樣式 -->
<link rel="stylesheet" >
<link rel="stylesheet" >
</head>
<body>
<div id="app></div>
<!-- 引入組件庫 -->
<script src="https://unpkg.com/element-ui/lib/index.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/axios/axios.min.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Swiper/4.5.0/js/swiper.min.js"></script>
</body>
// webpack.base.conf.js 添加externals對象谍夭,告訴webpack以下第三方庫不需要打包
module.exports = {
......
externals: { // 鍵: 庫的名稱黑滴, 值: 在項目中的別名,
'swiper': 'Swiper',
'vue': 'Vue',
'vue-router': 'VueRouter',
'axios': 'axios',
'element-ui': 'ELEMENT'
},
}
對圖片進行壓縮
??????在 vue 項目中除了可以在 webpack.base.conf.js 中 url-loader 中設置 limit 大小來對圖片處理,對小于 limit 的圖片轉化為 base64 格式紧索,其余的不做操作袁辈。我們可以用 image-webpack-loader來壓縮處理較大的圖片資源:
- 安裝 image-webpack-loader :
npm install image-webpack-loader -D
- 使用
// webpack.base.conf.js
module: {
rules: [
{
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: {
mozjpeg: { // jpeg壓縮
progressive: true,
quality: 65
},
// optipng.enabled: false will disable optipng
optipng: {//png壓縮
enabled: false,
},
pngquant: { // png壓縮
quality: [0.65, 0.90],
speed: 4
},
gifsicle: { // gif壓縮
interlaced: false,
}
// the webp option will enable WEBP
//webp: {
// quality: 75
//}
}
}
]
}
]
},
使用image-webpack-loader之后處理前后的對比:
減少 ES6 轉為 ES5 的冗余代碼
??????默認情況下, Babel 會在每個輸出文件中內(nèi)嵌一些依賴的輔助函數(shù)代碼珠漂,如果多個源代碼文件都依賴這些輔助函數(shù)晚缩,那么這些輔助函數(shù)的代碼將會出現(xiàn)很多次,造成代碼冗余媳危。為了不讓這些輔助函數(shù)的代碼重復出現(xiàn)荞彼,可以使用babel-plugin-transform-runtime 插件,通過 require('babel-runtime/helpers/createClass') 的方式導入待笑,做到只引入一次鸣皂。
- 安裝 babel-plugin-transform-runtime
npm install babel-plugin-transform-runtime --save-dev
- 修改 .babelrc 配置文件:
"plugins": [
"transform-runtime",
[
"component",
{
"libraryName": "element-ui",
"styleLibraryName": "theme-chalk"
}
]
]
以下是vue2.0X使用webpack打包前置幫我們安裝好的插件
-
extract-text-webpack-plugin
: 把css代碼從js文件中抽離出來暮蹂,單獨出一個模塊 -
optimize-css-assets-webpack-plugin
: 壓縮css文件 -
uglifyjs-webpack-plugin
: 壓縮js文件
vue代碼層面的優(yōu)化
路由懶加載
??????vue是單頁面應用寞缝,而我們的網(wǎng)站通常又是有多個頁面組成,所以會引入很多路由椎侠,如果統(tǒng)一都在首屏加載第租,那么經(jīng)過webpack 打包之后文件會很大,減緩首屏加載速度我纪,降低用戶體驗慎宾。因此,我們要使用路由懶加載浅悉,將不同路由對應的組件分割成不同的代碼塊趟据,當路由被訪問的時候才加載對應的組件。
{
path: '/index',
name: '首頁',
component: r =>
require.ensure([], () => r(require('@/page/index')), 'index'),
meta: {
type: 'index',
title: 'XXX'
}
}
v-if和v-show的應用
??????v-if 是 真正 的條件渲染术健,需要操作dom元素汹碱,有更高的切換消耗;v-show控制的元素總是會被渲染荞估,簡單地基于 CSS 的 display 屬性進行切換咳促。因此,如果需要非常頻繁的切換勘伺,建議使用v-show跪腹,如果在運行時條件很少改變,則使用v-if飞醉。
長列表/無限列表渲染
??????在我們的數(shù)據(jù)平臺或者沉淀多年的業(yè)務數(shù)據(jù)可能會有幾十萬冲茸,上百萬條數(shù)據(jù),這時我們出了要應用分頁,無限滾動的思路轴术,最好做窗口化渲染來優(yōu)化性能难衰,只渲染可視區(qū)域內(nèi)的內(nèi)容,減少重新渲染組件和創(chuàng)建 dom 節(jié)點的時間逗栽。具體可以參考使用vue-virtual-scroll-list 和 vue-virtual-scroller插件來實現(xiàn)盖袭。
條件語句優(yōu)化
??????在我們的項目中經(jīng)常會遇到有四五個判斷條件甚至更多的情況,這時如果嵌套過多過深祭陷,就會導致代碼難以理解苍凛,維護困難,也會降低運行時性能兵志。
- 我們可以使用return優(yōu)先返回錯誤語句而不使用 if else模塊:
if(res.code === -1) return false
...... //其他需要進行的操作
- 也可以利用Map數(shù)據(jù)結構來判斷,減少循環(huán)和更多的判斷:
let map = new Map();
let s = 'abbgfffklisfb'
let a = 0
let b = 0
for(let i=0; i<s.length; i++){
if(!map.has(s[i])){ // 判斷是否已經(jīng)存在
a++
} else {
let temp = map.get(s[i]); // 獲取對應的鍵值
a = temp > a ? temp: a
b++
}
map.set(s[i], i); // 將某個字符賦予值
}
其他優(yōu)化
壓縮圖片
??????在做官網(wǎng)或者其他視效豐富的頁面時包含大量圖片醇蝴,如果是用PSD切下來的圖直接提到線上,肯定是大大影響首屏資源加載和頁面渲染的想罕,所以我們需要對其進行壓縮悠栓。推薦采用 熊貓壓縮,基本上是最大程度的壓縮按价,另外惭适,推薦用jpg,占用內(nèi)存比png格式的小楼镐。
圖片資源預加載
項目是否需要預加載取決于開發(fā)者癞志,用預加載一定會有一個從0到100的資源loading的過程。
<template>
<div
class="page-container"
style="text-align: center;"
>
<div id="loading-panel">
<p><img
src="../../static/logo.png"
alt=""
></p>
<h1>Loading...</h1>
<h2>{{percent}}</h2>
</div>
</div>
</template>
<script>
export default {
data() {
return {
count: 0,
percent: "",
};
},
mounted() {
this.preload();
console.log('hrthrth')
},
created (){
let script = document.createElement('script')
script.src = '../utils/'
},
methods: {
preload() {
let imgs = [
"static/img/card1.png",
"static/img/card2.png",
"static/img/card3.png",
"static/img/card4.png",
"static/img/card5.png",
"static/img/devil1.png",
"static/img/devil2.png",
"static/img/earth.png",
"static/img/earth1.png",
"static/img/earth2.png",
"static/img/female-as.png",
"static/img/female-de.png",
"static/img/female-h.png",
"static/img/404.png",
"static/img/404_clond.png",
"static/img/app.png",
"static/img/fb.png",
"static/img/bg.jpg"
];
for (let img of imgs) {
let image = new Image();
image.src = img;
const that = this
image.onload = function(e) {
that.count++;
// 計算圖片加載的百分數(shù)框产,綁定到percent變量
let percentNum = Math.floor((that.count / 14) * 100);
that.percent = `${percentNum}%`;
};
}
}
},
watch: {
count: function (val) {
if (val === 18) {
// 圖片加載完成后跳轉到首頁
this.$router.push({ path: "index" });
}
},
},
};
</script>
快捷一點的方式是使用第三方插件 Preload.js,可以預加載音視頻和圖片等資源凄杯。首先在index.html中引入preload.js
<script src="https://code.createjs.com/1.0.0/preloadjs.min.js"></script>
然后新建一個loading.vue文件:
<template>
<div>
<div id="preload_panel">
<p><img
src="../../static/logo.png"
alt=""
></p>
<h1>Loading...{{percent}} %</h1>
</div>
</div>
</template>
<script>
export default {
name: "preload",
data() {
return {
percent: "",
};
},
mounted() {
this.preLoad()
},
methods: {
preLoad() {
var mainfest = [
{ src: "static/img/card1.png" },
{ src: "static/img/card2.png" },
{ src: "static/img/card3.png" },
{ src: "static/img/card4.png" },
{ src: "static/img/card5.png" },
{ src: "static/img/devil1.png" },
{ src: "static/img/devil2.png" },
{ src: "static/img/earth.png" },
{ src: "static/img/earth1.png" },
{ src: "static/img/earth2.png" },
{ src: "static/img/female-as.png" },
{ src: "static/img/female-de.png" },
{ src: "static/img/female-h.png" },
{ src: "static/img/404.png" },
{ src: "static/img/404_clond.png" },
{ src: "static/img/app.png" },
{ src: "static/img/fb.png" },
{ src: "static/img/bg.jpg" },
];
const that = this
var preload = {
// 預加載函數(shù)
startPreload: function () {
var preload = new createjs.LoadQueue(true);
//為preloaded添加整個隊列變化時展示的進度事件
preload.addEventListener("progress", this.handleFileProgress);
//注意加載音頻文件需要調(diào)用如下代碼行
// preload.installPlugin(createjs.SOUND);
//為preloaded添加當隊列完成全部加載后觸發(fā)事件
preload.addEventListener("complete", this.loadComplete);
//設置最大并發(fā)連接數(shù) 最大值為10
preload.setMaxConnections(1);
preload.loadManifest(mainfest);
},
// 當整個隊列變化時展示的進度事件的處理函數(shù)
handleFileProgress: function (event) {
that.percent = Math.ceil(event.loaded * 100);
},
// 處理preload添加當隊列完成全部加載后觸發(fā)事件
loadComplete: function () {
that.$router.push("/index"); // 加載完成后跳轉到首頁
},
};
preload.startPreload();
},
},
};
</script>
<style>
</style>
圖片資源懶加載
??????為了加速頁面加載速度,我們也可以將未出現(xiàn)在可視區(qū)域內(nèi)的圖片先不做加載秉宿,等到滾動到可視區(qū)域后再去加載戒突。這樣對于頁面加載性能上會有很大的提升,也提高了用戶體驗描睦。這里采用的是第三方插件vue-lazyload:
- 安裝插件
npm install vue-lazyload
2.main.js中全局引入
import VueLazyLoad from 'vue-lazyload'
// 第二個參數(shù)對象是自定義對象可有可無
Vue.use(VueLazyload,{
preLoad: 1.3,
error: 'dist/error.png',
loading: 'dist/loading.gif',
attempt: 1
})
3.組件中使用,將 :src 屬性直接改為v-lazy
<img v-lazy="item.src'>
網(wǎng)絡請求的優(yōu)化
??????1.除非首屏渲染需要用到或者是第三方埋點的sdk膊存,其他不影響初次渲染的資源可以考慮延遲或異步加載啃擦,減少資源請求數(shù)荷荤,加快首屏渲染速度。比如FaceBook 的SDK在首頁渲染時不需要用到弄捕,那我只需要在登錄頁面再去加載即可韵丑,在login.vue文件中:
let fbDiv = document.createElement('script')
fbDiv.setAttribute('async', 'async')
fbDiv.setAttribute('defer', 'defer')
fbDiv.setAttribute('crossorigin', 'anonymous')
fbDiv.setAttribute('src', 'https://connect.facebook.net/zh_TW/sdk.js#xfbml=1&version=v6.0&appId=xxxxxxxxx&autoLogAppEvents=1')
document.querySelector('body').appendChild(fbDiv)
第三方埋點SDK如百度統(tǒng)計或者Google Analysics則一定要在index.html中就引入相應的代碼:
<script>
(function(i, s, o, g, r, a, m) {
i['GoogleAnalyticsObject'] = r;
i[r] = i[r] || function() {
(i[r].q = i[r].q || []).push(arguments)
}, i[r].l = 1 * new Date();
a = s.createElement(o),
m = s.getElementsByTagName(o)[0];
a.async = true;
a.src = g;
m.parentNode.insertBefore(a, m);
})(window, document, 'script', 'https://www.google-analytics.com/analytics.js', 'ga');
ga( 'create', 'UA-xxxxx-x', 'auto' );
ga( 'send', 'pageview' );
</script>
- 由于HTTP的限制仍稀,在建立一個tcp請求時會有一定的耗時,所以埂息,我們要盡量減少請求的次數(shù),對資源進行合并、壓縮千康,其目的是減少http請求數(shù)和減小包體積享幽,加快傳輸速度。如將項目中遇到的比較小的logo,圖標等合成雪碧圖拾弃,推薦合成雪碧圖的在線工具:css sprites generator
image (5).png
??????最后總結為一下值桩,本文由以下四個部分組成:基礎Web 技術層面的優(yōu)化;webpack 打包配置方面的優(yōu)化豪椿;Vue 代碼層面的優(yōu)化奔坟;其他優(yōu)化,來介紹如何優(yōu)化 Vue 項目的性能搭盾。希望大家閱讀完之后能有所啟發(fā)咳秉,若有其他補充,歡迎交流學習鸯隅!