Vue 框架經過數據雙向綁定和虛擬 DOM 技術,幫咱們處理了前端開發中最髒最累的 DOM 操做部分, 咱們再也不須要去考慮如何操做 DOM 以及如何最高效地操做 DOM;但 Vue 項目中仍然存在項目首屏優化、Webpack 編譯配置優化等問題,因此咱們仍然須要去關注 Vue 項目性能方面的優化,使項目具備更高效的性能、更好的用戶體驗。本文是做者經過實際項目的優化實踐進行總結而來,但願讀者讀完本文,有必定的啓發思考,從而對本身的項目進行優化起到幫助。本文內容分爲如下三部分組成:html
v-if是 真正 的條件渲染,由於它會確保在切換過程當中條件塊內的事件監聽器和子組件適當地被銷燬和重建;也是惰性的:若是在初始渲染時條件爲假,則什麼也不作——直到條件第一次變爲真時,纔會開始渲染條件塊。前端
v-show 就簡單得多, 無論初始條件是什麼,元素老是會被渲染,而且只是簡單地基於 CSS 的 display 屬性進行切換。vue
因此,v-if 適用於在運行時不多改變條件,不須要頻繁切換條件的場景;v-show 則適用於須要很是頻繁切換條件的場景。node
computed:是計算屬性,依賴其它屬性,而且 computed 的值有緩存,只有它依賴的屬性值發生改變,下一次獲取 computed 的值時纔會從新計算 computed 的值;webpack
watch:更多的是 觀察 的做用,相似於某些數據的監聽回調,每當監聽的數據變化時都會執行回調進行後續操做:ios
在列表數據進行遍歷渲染時,須要爲每一項 item 設置惟一 key 值,方便 Vue.js 內部機制精準找到該條列表數據。當 state 更新時,新的狀態值和舊的狀態值對比,較快地定位到 diff。web
v-for 比 v-if 優先級高,若是每一次都須要遍歷整個數組,將會影響速度,尤爲是當之須要渲染很小一部分的時候,必要狀況下應該替換成 computed 屬性。vue-cli
<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>
複製代碼
Vue 會經過 Object.defineProperty 對數據進行劫持,來實現視圖響應數據的變化,然而有些時候咱們的組件就是純粹的數據展現,不會有任何改變,咱們就不須要 Vue來劫持咱們的數據,在大量數據展現的狀況下,這可以很明顯的減小組件初始化的時間,那如何禁止 Vue 劫持咱們的數據呢?能夠經過 Object.freeze 方法來凍結一個對象,一旦被凍結的對象就不再能被修改了。express
export default { data: () => ({ users: {} }), async created() { const users = await axios.get("/api/users"); this.users = Object.freeze(users); } }; 複製代碼
Vue 組件銷燬時,會自動清理它與其它實例的鏈接,解綁它的所有指令及事件監聽器,可是僅限於組件自己的事件。 若是在 js 內使用 addEventListene 等方式是不會自動銷燬的,咱們須要在組件銷燬時手動移除這些事件的監聽,以避免形成內存泄露,如:npm
created() { addEventListener('click', this.click, false) }, beforeDestroy() { removeEventListener('click', this.click, false) } 複製代碼
對於圖片過多的頁面,爲了加速頁面加載速度,因此不少時候咱們須要將頁面內未出如今可視區域內的圖片先不作加載, 等到滾動到可視區域後再去加載。這樣對於頁面加載性能上會有很大的提高,也提升了用戶體驗。咱們在項目中使用 Vue 的 vue-lazyload 插件:
(1) 安裝插件:
npm install vue-lazyload --save-dev
複製代碼
(2)再入口文件 main.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 是單頁面應用,可能會有不少的路由引入 ,這樣使用 webpcak 打包後的文件很大,當進入首頁時,加載的資源過多,頁面會出現白屏的狀況,不利於用戶體驗。若是咱們能把不一樣路由對應的組件分割成不一樣的代碼塊,而後當路由被訪問的時候才加載對應的組件,這樣就更加高效了。這樣會大大提升首屏顯示的速度,可是可能其餘的頁面的速度就會降下來。
路由懶加載:
const Foo = () => import('./Foo.vue') const router = new VueRouter({ routes: [ { path: '/foo', component: Foo } ] }) 複製代碼
咱們在項目中常常會須要引入第三方插件,若是咱們直接引入整個插件,會致使項目的體積太大,咱們能夠藉助 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) 複製代碼
服務端渲染是指 Vue 在客戶端將標籤渲染成的整個 html 片斷的工做在服務端完成,服務端造成的 html 片斷直接返回給客戶端這個過程就叫作服務端渲染。
更好的 SEO: 由於 SPA 頁面的內容是經過 Ajax 獲取,而搜索引擎爬取工具並不會等待 Ajax 異步完成後再抓取頁面內容,因此在 SPA 中是抓取不到頁面經過 Ajax 獲取到的內容;而 SSR 是直接由服務端返回已經渲染好的頁面(數據已經包含在頁面中),因此搜索引擎爬取工具能夠抓取渲染好的頁面;
更快的內容到達時間(首屏加載更快): SPA 會等待全部 Vue 編譯後的 js 文件都下載完成後,纔開始進行頁面的渲染,文件下載等須要必定的時間等,因此首屏渲染須要必定的時間;SSR 直接由服務端渲染好頁面直接返回顯示,無需等待下載 js 文件及再去渲染等,因此 SSR 有更快的內容到達時間;
更多的開發條件限制: 例如服務端渲染只支持 beforCreate 和 created 兩個鉤子函數,這會致使一些外部擴展庫須要特殊處理,才能在服務端渲染應用程序中運行;而且與能夠部署在任何靜態文件服務器上的徹底靜態單頁面應用程序 SPA 不一樣,服務端渲染應用程序,須要處於 Node.js server 運行環境;
更多的服務器負載:在 Node.js 中渲染完整的應用程序,顯然會比僅僅提供靜態文件的 server 更加大量佔用CPU 資源,所以若是你預料在高流量環境下使用,請準備相應的服務器負載,並明智地採用緩存策略。
在 vue 項目中除了能夠在 webpack.base.conf.js 中 url-loader 中設置 limit 大小來對圖片處理,對小於 limit 的圖片轉化爲 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, } } ] } 複製代碼
Babel 插件會在將 ES6 代碼轉換成 ES5 代碼時會注入一些輔助函數,例以下面的 ES6 代碼:
class HelloWebpack extends Component{...} 複製代碼
這段代碼再被轉換成能正常運行的 ES5 代碼時須要如下兩個輔助函數:
babel-runtime/helpers/createClass // 用於實現 class 語法 babel-runtime/helpers/inherits // 用於實現 extends 語法 複製代碼
在默認狀況下, Babel 會在每一個輸出文件中內嵌這些依賴的輔助函數代碼,若是多個源代碼文件都依賴這些輔助函數,那麼這些輔助函數的代碼將會出現不少次,形成代碼冗餘。爲了避免讓這些輔助函數的代碼重複出現,能夠在依賴它們時經過 require('babel-runtime/helpers/createClass') 的方式導入,這樣就能作到只讓它們出現一次。babel-plugin-transform-runtime 插件就是用來實現這個做用的,將相關輔助函數進行替換成導入語句,從而減少 babel 編譯出來的代碼的文件大小。
(1)首先,安裝 babel-plugin-transform-runtime:
npm install babel-plugin-transform-runtime --save-dev
複製代碼
(2)而後,修改 .babelrc 配置文件爲:
"plugins": [ "transform-runtime" ] 複製代碼
若是項目中沒有去將每一個頁面的第三方庫和公共模塊提取出來,則項目會存在如下問題:
因此咱們須要將多個頁面的公共代碼抽離成單獨的文件,來優化以上問題 。Webpack 內置了專門用於提取多個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 ); } }), // 抽取出代碼模塊的映射關係 new webpack.optimize.CommonsChunkPlugin({ name: 'manifest', chunks: ['vendor'] }) 複製代碼
當使用 DOM 內模板或 JavaScript 內的字符串模板時,模板會在運行時被編譯爲渲染函數。一般狀況下這個過程已經足夠快了,但對性能敏感的應用仍是最好避免這種用法。
預編譯模板最簡單的方式就是使用單文件組件——相關的構建設置會自動把預編譯處理好,因此構建好的代碼已經包含了編譯出來的渲染函數而不是原始的模板字符串。
若是你使用 webpack,而且喜歡分離 JavaScript 和模板文件,你可使用 vue-template-loader,它也能夠在構建過程當中把模板文件轉換成爲 JavaScript 渲染函數。
當使用單文件組件時,組件內的 CSS 會以 style 標籤的方式經過 JavaScript 動態注入。這有一些小小的運行時開銷,若是你使用服務端渲染,這會致使一段 「無樣式內容閃爍 (fouc) 」 。將全部組件的 CSS 提取到同一個文件能夠避免這個問題,也會讓 CSS 更好地進行壓縮和緩存。
查閱這個構建工具各自的文檔來了解更多:
咱們在項目進行打包後,會將開發中的多個文件代碼打包到一個文件中,而且通過壓縮、去掉多餘的空格、babel編譯化後,最終將編譯獲得的代碼會用於線上環境,那麼這樣處理後的代碼和源代碼會有很大的差異,當有 bug的時候,咱們只能定位到壓縮處理後的代碼位置,沒法定位到開發環境中的代碼,對於開發來講很差調式定位問題,所以 sourceMap 出現了,它就是爲了解決很差調式代碼問題的。
SourceMap 的可選值以下(+ 號越多,表明速度越快,- 號越多,表明速度越慢, o 表明中等速度 )
緣由以下:
cheap: 源代碼中的列信息是沒有任何做用,所以咱們打包後的文件不但願包含列相關信息,只有行信息能創建打包先後的依賴關係。所以無論是開發環境或生產環境,咱們都但願添加 cheap 的基本類型來忽略打包先後的列信息;
module: 無論是開發環境仍是正式環境,咱們把都但願能定位到bug的源代碼具體的位置,好比說某個Vue文件報錯了,咱們但願能定位到具體的Vue文件,所以咱們也須要module配置;
soure-map: source-map 會爲每個打包後的模塊生成獨立的 soucemap 文件,所以咱們須要增長source-map 屬性;
eval-source-map: eval 打包代碼的速度很是快,由於它不生成 map 文件,可是能夠對 eval 組合使用 eval-source-map 使用會將 map 文件以 DataURL 的形式存在打包後的 js 文件中。在正式環境中不要使用 eval-source-map, 由於它會增長文件的大小,可是在開發環境中,能夠試用下,由於他們打包的速度很快。
Webpack 輸出的代碼可讀性很是差並且文件很是大,讓咱們很是頭疼。爲了更簡單、直觀地分析輸出結果,社區中出現了許多可視化分析工具。這些工具以圖形的方式將結果更直觀地展現出來,讓咱們快速瞭解問題所在。接下來說解咱們在 Vue 項目中用到的分析工具:webpack-bundle-analyzer 。
咱們在項目中 webpack.prod.conf.js 進行配置:
if (config.build.bundleAnalyzerReport) { var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; webpackConfig.plugins.push(new BundleAnalyzerPlugin()); } 複製代碼
執行 $ npm run build --report 後生成分析報告以下:
gzip 是 GNUzip 的縮寫,最先用於 UNIX 系統的文件壓縮。HTTP 協議上的 gzip 編碼是一種用來改進 web 應用程序性能的技術,web 服務器和客戶端(瀏覽器)必須共同支持 gzip。目前主流的瀏覽器,Chrome,firefox,IE等都支持該協議。常見的服務器如 Apache,Nginx,IIS 一樣支持,gzip 壓縮效率很是高,一般能夠達到 70% 的壓縮率,也就是說,若是你的網頁有 30K,壓縮以後就變成了 9K 左右;
如下咱們以服務端使用咱們熟悉的 express 爲例,開啓 gzip 很是簡單,相關步驟以下:
npm install compression --save
複製代碼
var compression = require('compression'); var app = express(); app.use(compression()) 複製代碼
Chrome 的 Performance 面板能夠錄製一段時間內的 js 執行細節及時間。使用 Chrome 開發者工具分析頁面性能的步驟以下。
打開 Chrome 開發者工具,切換到 Performance 面板
點擊 Record 開始錄製
刷新頁面或展開某個節點
點擊 Stop 中止錄製
本文經過如下三部分組成:Vue 代碼層面的優化、webpack 配置層面的優化、基礎的 Web 技術層面的優化;來介紹怎麼去優化 Vue 項目的性能。 但願對讀完本文的你有幫助、有啓發,若是有不足之處,歡迎批評指正交流!