原文: https://github.com/Coffcer/Bl...javascript
通常來講,你不須要太關心vue的運行時性能,它在運行時很是快,但付出的代價是初始化時相對較慢。在最近開發的一個Hybrid APP裏,Android Webview初始化一個較重的vue頁面居然用了1200ms ~ 1400ms,這讓我開始重視vue的初始化性能,並最終優化到200 ~ 300ms,這篇文章分享個人優化思路。html
先看一下常見的vue寫法:在html裏放一個app組件,app組件裏又引用了其餘的子組件,造成一棵以app爲根節點的組件樹。vue
<body> <app></app> </body>
而正是這種作法引起了性能問題,要初始化一個父組件,必然須要先初始化它的子組件,而子組件又有它本身的子組件。那麼要初始化根標籤<app>
,就須要從底層開始冒泡,將頁面全部組件都初始化完。因此咱們的頁面會在全部組件都初始化完纔開始顯示。java
這個結果顯然不是咱們要的,更好的結果是頁面能夠從上到下按順序流式渲染,這樣可能整體時間增加了,但首屏時間縮減,在用戶看來,頁面打開速度就更快了。git
要實現這種渲染模式,我總結了下有3種方式實現。第3種方式是我認爲最合適的,也是我在項目中實際使用的優化方法。github
這種方式很是簡單,例如:數組
<body> <A></A> <B></B> <C></C> </body>
拋棄了根組件<app>
,從而使A、B、C每個組件初始化完都馬上展現。但根組件在SPA裏是很是必要的,因此這種方式只適用小型頁面。瀏覽器
異步組件在官方文檔已有說明,使用很是簡單:app
<app> <A></A> <B></B> </app>
new Vue({ components: { A: { /*component-config*/ }, B (resolve) { setTimeout(() => { resolve({ /*component-config*/ }) }, 0); } } })
這裏<B>
組件是一個異步組件,會等到手動調用resolve函數時纔開始初始化,而父組件<app>
也沒必要等待<B>
先初始化完。dom
咱們利用setTimeout(fn, 0)將<B>
的初始化放在隊列最後,結果就是頁面會在<A>
初始化完後馬上顯示,而後再顯示<B>
。若是你的頁面有幾十個組件,那麼把非首屏的組件全設成異步組件,頁面顯示速度會有明顯的提高。
你能夠封裝一個簡單的函數來簡化這個過程:
function deferLoad (component, time = 0) { return (resolve) => { window.setTimeout(() => resolve(component), time) }; } new Vue({ components: { B: deferLoad( /*component-config*/ ), // 100ms後渲染 C: deferLoad( /*component-config*/, 100 ) } })
看起來很美好,但這種方式也有問題,考慮下這樣的結構:
<app> <title></title> <A></A> <title></title> <B></B> <title></title> <C></C> </app>
仍是按照上面的異步組件作法,這時候就須要考慮把哪些組件設成異步的了。若是把A、B、C都設成異步的,那結果就是3個<title>
會首先渲染出來,頁面渲染的過程在用戶看來很是奇怪,並非預期中的從上到下順序渲染。
這是我推薦的一種作法,簡單有效。仍是那個結構,咱們給要延遲渲染的組件加上v-if:
<app> <A></A> <B v-if="showB"></B> <C v-if="showC"></C> </app>
new Vue({ data: { showB: false, showC: false }, created () { // 顯示B setTimeout(() => { this.showB = true; }, 0); // 顯示C setTimeout(() => { this.showC = true; }, 0); } });
這個示例寫起來略顯囉嗦,但它已經實現了咱們想要的順序渲染的效果。頁面會在A組件初始化完後顯示,而後再按順序渲染其他的組件,整個頁面渲染方式看起來是流式的。
有些人可能會擔憂v-if
存在一個編譯/卸載過程,會有性能影響。但這裏並不須要擔憂,由於v-if
是惰性的,只有當第一次值爲true時纔會開始初始化。
這種寫法看起來很麻煩,若是咱們能實現一個相似v-if
的組件,而後直接指定多少秒後渲染,那就更好了,例如:
<app> <A></A> <B v-lazy="0"></B> <C v-lazy="100"></C> </app>
一個簡單的指令便可,不須要js端任何配合,而且能夠用在普通dom上面,Nice!
在vue裏,相似v-if
和v-for
這種是terminal指令,會在指令內部編譯組件。若是你想要本身實現一個terminal指令,須要加上terminal: true
,例如:
Vue.directive('lazy', { terminal: true, bind () {}, update () {}, unbind () {} });
這是vue在1.0.19+新增的功能,因爲比較冷門,文檔也沒有特別詳細的敘述,最好的方式是參照着v-if
和v-for
的源碼來寫。
我已經爲此封裝了一個terminal指令,你能夠直接使用:
https://github.com/Coffcer/vu...
除了組件上的優化,咱們還能夠對vue的依賴改造入手。初始化時,vue會對data作getter、setter改造,在現代瀏覽器裏,這個過程實際上挺快的,但仍然有優化空間。
Object.freeze()
是ES5新增的API,用來凍結一個對象,禁止對象被修改。vue 1.0.18+之後,不會對已凍結的data作getter、setter轉換。
若是你確保某個data不須要跟蹤依賴,可使用Object.freeze將其凍結。但請注意,被凍結的是對象的值,你仍然能夠將引用整個替換調。看下面例子:
<p v-for="item in list">{{ item.value }}</p>
new Vue({ data: { // vue不會對list裏的object作getter、setter綁定 list: Object.freeze([ { value: 1 }, { value: 2 } ]) }, created () { // 界面不會有響應 this.list[0].value = 100; // 下面兩種作法,界面都會響應 this.list = [ { value: 100 }, { value: 200 } ]; this.list = Object.freeze([ { value: 100 }, { value: 200 } ]); } })
vue 1.0+ 的組件其實不算輕量,初始化一個組件包括依賴收集、轉換等過程,但其實有些是能夠放在編譯時提早完成的。vue 2.0+ 已經在這方面作了很多的改進:分離了編譯時和運行時、提供函數組件等,能夠預見,vue 2.0的性能將有很大的提高。
v-lazy-component: https://github.com/Coffcer/vu...