实测 Vue SSR 的渲染性能:避开 20 倍耗时

导语:Vue SSR是Vue.js 2.0引入的直出渲染方案,本文将全面解析virtual-dom-based 及 string-based 的原理并对其进行对比。 1、前言

前端技术年年有新宠,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对象:

步骤2:生成with绑定上下文对象的render函数( 该函数被缓存):

步骤3:_h ----> _createElement    执行render函数返回 包含了业务数据 的vnode对象:

步骤4:遍历dom对象属性 拼接字符串 HTML渲染完成:

4、性能瓶颈分析

在上文中能够看到,步骤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将来版本中看到更好的渲染实现。

相关文章
相关标签/搜索