前端技術年年有新寵,Vue.js 2.0以其輕量級、漸進式、簡潔的語法在MVVM框架中脫穎而出,一經推出便很受業界青睞。前端
QQ空間Hybrid業務也在積極推進MVVM框架重構H5頁面。爲了提升首屏渲染速度,wns緩存+直出 是必不可少的。在Vue 1× 時代,沒有 server-side-render 方案,直出須要專門給寫一份首屏非Vue語法的模板。Vue2.0 server-side-render(簡稱Vue SSR)的推出,成功地讓先後端渲染模板代碼同構。vue
不過對於海量PV級的業務,直出模板的渲染效率直接影響服務端的壓力,在對業務代碼重構的同時,直出模板的性能是須要衡量的關鍵指標。node
當前經常使用的模板渲染方案能夠歸結成兩類:git
a類:string-based (基於字符串拼接)github
b類:virtual-dom-based(基於虛擬dom對象)正則表達式
Vue SSR的模板是virtual-dom-based,因此QQ空間Hybrid業務作Vue 2.0的改造的同時,模板類型也從以前的a類轉換成b類。後端
本文是在實際業務場景中對Vue SSR的渲染性能作測試,並解析渲染步驟,給出嘗試優化的方案和最終結論。緩存
2、經常使用string-based模板渲染原理介紹仍是先介紹下經常使用的string-based模板的寫法和編譯後的樣子, 幫助不太熟悉HTML模板原理的同窗能夠更容易理解文章後面優化的過程。微信
首先看一下H5頁面經常使用的HTML模板寫法:markdown
上面的模板應該是前端開發者很熟悉的一種模板語法,經過<%%>
來包裹邏輯,<%=%>
來賦值。
下面來看看模板代碼被編譯後的JS是什麼樣的:
JS中執行 tmpl.test(data) 便可拿到渲染出來的HTML片斷。tmpl.test 就至關因而一個test模板的render函數。
那麼這個render函數是如何經過上面的script模板轉換過來的呢,此時須要一個模板的編譯工具來實現,編譯工具的核心是一個正則表達式:
let jscReg = new RegExp(/(?:(?:\r\n|\r|\n)\s*?)?<%([=-]?)([\w\W\r\n]*?)%>(?:\r\n|\r|\n)?/gmi);
用這個正則表達式,能夠將模板代碼中的 JS邏輯與JS屬性值 分割出來,再經過'引號來將靜態dom串包裹,拼接,最後加上函數的頭尾,就能獲得一個完整的render函數。編譯過程能夠經過預編譯在線下實現,也能夠在線上實時動態完成。
看到如今應該都理解了string-based HTML模板在JS中渲染的方式了吧,這種類型的模板渲染效率實際上是最高的,由於它的render函數是不存在任何冗餘邏輯,completely字符串拼接。
3、Vue SSR性能如何首先在QQ空間禮物商城列表首屏來作個實驗,使用手機QQ掃描下方二維碼便可前往查看(建議手機QQ掃一掃,微信中打開只是靜態展現):
Vue SSR渲染調用代碼以下:
傳統模板渲染調用
實測循環 1w 次渲染耗時對比:
傳統模板:143ms
Vue SSR:2685ms
Vue SSR的耗時是傳統模板的近20倍!
4、20倍差距是如何產生的?20倍差距是如何產生的?咱們來分析一下Vue SSR完整的渲染過程。
Vue模板片斷:
Vue模板語法大部分都是指令試的僞代碼,既不是HTML語法,也不是JS語法,這也是virtual-dom類模板渲染較複雜的緣由之一。
步驟1:模板字符串經過正則解析成virtual-dom對象:在上文中能夠看到,步驟1(模板字符串經過正則解析成virtual-dom對象)與步驟2(生成with綁定上下文對象的render函數)都已經被緩存,在本次對比中直接忽略其耗時,問題只能出在步驟三、4:
步驟3:執行render函數生成vnode-tree對象;
步驟4:遞歸遍歷vnode-tree將其轉化拼接成HTML;
相比傳統string-based模板以最直接的方式拼接HTML,邏輯包含了 with、call、對象建立、遞歸拼接 是制約性能的關鍵,因爲vnode對象是包含了業務數據,不能經過緩存vnode來解決,即使是緩存了vnode,步驟4的拼接耗時也是瓶頸所在。
5、緩存沒有用,直接上大招!既然咱們已經知道什麼樣的render函數是最快的,那麼就作個工具直接把Vue模板編譯成string-based類的render函數便可,目標:
引入jscHelper github.com/jialunguo/v…
步驟1:將vue-server-render在 第1步 生成的 virtual-dom對象 拼成 string-based語法的模板字符串,genEl是先將Vue模板語法轉換成jsc語法:
1)值的轉換
2)for循環轉換
3)if判斷轉換
4)處理屬性
5)Vue指令轉換
6)樣式class和內聯代碼轉換
7)遞歸處理子dom
8)標籤閉合
這樣Vue模板就被jscHelper轉換成了傳統的string-based類模板。
步驟2:再經過文章開頭介紹的正則表達式:
let jscReg = new RegExp(/(?:(?:\r\n|\r|\n)\s*?)?<%([=-]?)([\w\W\r\n]*?)%>(?:\r\n|\r|\n)?/gmi);
將模板編譯成render函數,加以緩存。詳細過程請看GitHub上的jscHelper源碼。
6、最終優化效果最終優化效果很是明顯:
傳統模板: 160 ms
Vue SSR : 2963 ms
Vue SSR + jscHelper: 210 ms
注:因爲jscHelper須要對Vue的語法作轉換,複雜的語法會增長耗時,因此耗時仍是略高於傳統模板的。
7、寫在最後經過jscHelper對vue-server-render的性能作提高,須要持續地維護對Vue語法的兼容,並且目前並不支持相似<transition>
, <keep-alive>
, <router-view>
等高級語法,對組件的渲染方式也須要兼容。做爲使用方,咱們更但願Vue做者自己能多提供一種簡單的string-based渲染方式來做爲高性能的直出渲染方案。
我已經在GitHub上面提了相關的issue github.com/vuejs/vue/i… ,但願能在Vue.js將來版本中看到更好的渲染實現。