1. 命令式和声明式
首先我们应该知道什么是命令式,jQuery就是典型的命令式框架,命令式框架的一个最大的特点是关注过程。例如,我们把下面这段话翻译成对应的代码:
- 获取id为app的div标签
- 它的文本内容为hello world
- 为它绑定点击事件
- 点击时弹出提示:ok
对应的代码为:
$('#app').text('hello world').on('click', () => {
alert('ok');
});
用原生的JavaScript更加能够理解
const div = document.querySelector('#app');// 获取div
div.innerHTML = 'hello world';// 设置文本内容
div.addEventListener('click', () => { // 绑定点击事件
alert('ok');
});
可以看到,自然语言可以一一对应代码,代码做的就是完成描述的过程,这就是命令式框架的特点。
声明式框架更加关注结果,命令式实现上面的自然语言描述的功能,如下:
<div @click="()=>alert('ok')">hello world</div>
Vue.js帮我们封装了命令式的过程代码,然后暴露给我们的是这种声明式的 。
2. 性能与可维护性的权衡
结论:声明式代码的性能不优于命令式代码的性能。
命令式代码的更新性能消耗=更新新内容的性能消耗;
声明式代码的更新性能消耗=更新新内容的性能消耗+找出差异的性能消耗;
可以看出声明式代码的更新性能消耗比命令式的多了一个找出差异的性能消耗,由于已经有结论命令式代码的性能始终优于声明式代码的性能,所以只有找出差异的性能消耗足够的小无限接近于0,性能才能向命令式靠近。
Vue.js使用声明式框架的主要理由就是可维护性高于命令式框架,框架设计者只能在保持可维护性的同时尽量让性能损耗最小化。
3. 虚拟DOM的性能
原生JavaScript指的是像documen.createElement之类的DOM操作方法,并不包含innerHTML(模板),因为它比较特殊,需要单独讨论。
上面提到的减少找出差异的性能消耗,是提高声明式框架性能的主要方式,而所谓的虚拟DOM就是为了最小化找出差异性能损耗而出现的。
- JavaScript层面的操作要比DOM操作快得多,它们不是一个数量级的,所以可以得到一个描述innerHTML创建页面性能的公式:HTML字符串拼接的计算量+innerHTML的DOM计算量。
- 虚拟DOM创建新页面的过程是,第一步创建JavaScript对象,这个对象可以理解为是真实的DOM描述;第二步是递归地遍历虚拟DOM树并创建真实的DOM。所以DOM创建页面性能的公式:创建JavaScript对象的计算量+创建真实DOM的计算量。
上面的公式可以看出来,无论是JavaScript层面的计算,还是DOM层面的计算,其实两者的差距几乎没有。在创建新页面的时候,都需要创建新的DOM元素。就只从创建页面的性能对比来看的话,虚拟DOM根innerHTML没有什么优势。
但是从页面更新的性能对比来看,innerHTML是重新构建HTML字符串,再重新设置DOM元素的innerHTML属性,也就是哪怕只是内容只改了一个文字,也要重新设置innerHTML属性,也就意味着要销毁所有旧的DOM元素,再全部重新创建新的DOM元素。
再看虚拟DOM的更新页面的性能公式是:创建新的JavaScript对象+Diff,这时比创建页面过程时多了一个Diff的性能消耗,而这个消耗的产生也是JavaScript层面上的运算,不会产生数量级的差异。这个Diff就是判断页面更新时需要更新的必要元素,而不是全部元素更新一遍。
值得注意的是在更新页面时 ,innerHTML的性能消耗是跟更新的页面大小相关的,更新的页面越大,性能的消耗也会越大。而虚拟DOM只会更新变化的那一部分元素。
innerHTML、虚拟DOM和原生JavaScript在更新页面时的性能对比,如下:
------------------------------------------------------------------------------------------------->
性能弱 性能强
innerHTML(模板) | 虚拟DOM | 原生JavaScript |
---|---|---|
心智负担中等 | 心智负担小 | 心智负担大 |
可维护性强 | 可维护性查 | |
性能差 | 性能不错 | 性能高 |
以上内容就是Vue.js最终权衡选择使用虚拟DOM的原因。