什么是命令式和声明式呢?
我们来看一下jQuery,他是典型的命令式框架,命令是框架的一大特点就是注重过程
$('#app').text('hello word').on('click',()=>{alert('ok')})
我们可以看到上面这一段代码,做的事情是:获取到id为app的元素,把他的文本内容更改为hello word,并添加了一个点击事件,点击后弹出ok提示
我们再看看声明式的写法(以vue来举例)
<div @click='()=>{alert("ok")}'>hello word</div>
看以上代码,往往声明式的框架关注的是结果。关于这个结果如何实现的,我们并不需要关系,vue.js会帮我们实现。换句话说:vue.js帮我们封装了过程。
因此,我们不难猜出,vue.js内部一定是命令式的,而暴露出来给用户的却更加声明式
性能与可维护性的权衡
在这里我们先抛出一个结论:声明式代码的性能不优于命令式代码的性能
为什么会得出这样的结论呢?很简单我们假设一下,我们要把上面的div的内容要改成 ‘hello vue3’有没有比一下代码性能更好的呢?
div.textContent = 'hello vue3'
答案是没有
为什么呢?
我们现在来思考一下,如果是声明式框架会怎么样,如以下代码
<!-- 之前: -->
<div @click='()=>{alert("ok")}'>hello word</div>
<!-- 之后 -->
<div @click='()=>{alert("ok")}'>hello vue3</div>
这个是他的描述结果,要从之前的word改成vue3,对于框架而言,如果需要实现最优的性能,那一定是找到前后的差异并只更新变化的地方,但最优的更新代码仍然是:
div.textContent = 'hello vue3'
我们把A定义为:直接修改的性能消耗,B定义为:找出差异的性能消耗
那么:
命令式代码的更新性能消耗:
A
声明式代码的更新性能消耗:B+A
我们可以看到,声明式代码会比命令式代码多出找出差异部分的性能消耗,最理想的情况就是 B为0
(找出差异的性能消耗为0)是,命令式代码和声明式代码的性能消耗相同,但是无法做到超越。毕竟框架本身就是封装了命令式代码才实现了面向用户的声明式。
这里就符合我们最开始得到的解困:声明式代码的性能不优于命令式代码的性能
那么,问题又来了,为什么vue.js不选择性能更好的命令式,而选择声明式呢?
问题就在于:声明式代码的可维护性更好,我们通过以上的代码,我们可以明确的感受到:命令式代码,我们需要去维护整个过程
,而声明式代码,我们只需要展示结果
就可以,看上去更加的直观,至于做事的过程,我们并不需要关系,因为vue.js已经帮我们封装好了。而框架设计者要做的就是把B(找出差异的能量消耗)降低到最小,也就是在保持可维护性的同时让性能损失最小
虚拟DOM的性能到底如何
我们在上面提到了 声明式代码的性能消耗 为 A+B
(直接修改的性能 + 找出差异的性能)
如果我们最小化B,那我们声明式代码就无限趋近于命令式代码,这里我们就需要说到vue.js的虚拟DOM
了
虚拟DOM就是为了最小化找出找出差异最小化出现的
具体的虚拟dom的diff算法 我们在后面再说。其实我们都知道 涉及dom层面的计算远比JavaScript层面的计算差,我们这边就来对比一下虚拟dom和innerHTML的在创建页面时候的性能
虚拟dom | innerHTML | |
---|---|---|
纯JavaScript层面的计算 | 创建JavaScript对象(VNode) | 渲染HTML字符串 |
DOM层面的计算 | 新建所有的DOM元素 | 新建所有的DOM元素 |
可以看到在创建页面的时候无论是纯JavaScript层面的计算和dom层面的计算,两者的差距不大。
我们再看看在更新页面的时候
虚拟dom | innerHTML | |
---|---|---|
纯JavaScript层面的计算 | 创建新的JavaScript对象 + Diff | 渲染HTML字符串 |
DOM层面的计算 | 做必要的dom更新 | 销毁所有旧的dom元素,新建所有新的dom元素 |
我们可以看到,在更新页面的时候,在纯JavaScript层面的计算要比创建页面的时候多了一个diff的性能消耗。在dom层面的计算,我们可以看到虚拟dom只会做必要的dom更新,但是innerHTML来说,需要全量的更新,这时候虚拟dom的优势就体现出来。
我们在更新页面的时候 加上性能因素
虚拟dom | innerHTML | |
---|---|---|
纯JavaScript层面的计算 | 创建新的JavaScript对象 + Diff | 渲染HTML字符串 |
DOM层面的计算 | 做必要的dom更新 | 销毁所有旧的dom元素,新建所有新的dom元素 |
性能因素 | 与数据的变化量相关 | 于模板的大小相关 |
这张表格的的意思是:虚拟dom的性能因素是diff,在innerHTML的性能问题主要是销毁所有的旧元素,在新建所有新的dom元素,如果模板越大,在dom层面的性能消耗也越大
我们粗略的终结一下 innerHTML、虚拟dom 以及原生 JavaScript(指createElement等方法)在更新页面时的性能
性能差 ——> 性能高
innerHTML(模板) | 虚拟dom | 原生JavaScript |
---|---|---|
心智负担中等 | 心智负担低 | 心智负担高 |
性能差 | 可维护性高,性能不错 | 可维护性差,性能高 |
总结
- 我们先讨论了命令式和声明式的差异,其中命令式更加关注过程,而声明式更加关注结果。命令式在理论上时可以做到性能极致的优化,但是用户需要承受极大的心智负担;而声明式能够有效的减少用户的心智负担,但是性能上有一定的牺牲,框架设计者要想办法尽量的使其性能的损耗最小化
- 接着我们讨论了虚拟dom的性能,并给出了一个公式,声明式的更新性能消耗 = 找出差异的性能消耗 + 直接修改的性能消耗,虚拟dom的意义就在于找到差异的性能最小化。