前言 曾经面试时碰到过一个问题:为什么现有的Vue框架开发可以淘汰之前的JQuery? 我回答:Vue框架无需自己操作DOM,可以避免自己频繁的操作DOM。 面试官接着反问我:Vue框架无需自己操作DOM,有什么优势吗,不用操作DOM就一定是好的吗? 我懵了,在我的认知里Vue框架无需自己操作DOM性能是一定优于自己来操作DOM元素的,其实并不是的..... 声明式框架与命令式框架首先我们得了解声明式框架和命令式框架的区别。 命令式框架关注过程 JQuery就是典型的命令式框架。 例如我们来看如下一段代码: $( "button.continue" ).html( "Next Step..." ).on('click', () => { alert('next') })
这段代码的含义就是先获取一个类名为continue的button元素,它的内容为 Next Step...,并为它绑定一个点击事件。可以看到自然语言描述与代码是一一对应的,这更符合我们做事的逻辑。 声明式框架更关注结果 现有的Vue,React都是典型的声明式框架。 接着来看一段Vue的代码: <button class="continue" @click="() => alert('next')">Next Step...</button>
这是一段类HTML模板,它更像是直接提供一个结果。至于怎么实现这个结果,就交给Vue内部来实现,开发者不用关心。 性能比较首先告诉大家结论:声明式代码性能不优于命令式代码性能。 即:声明式代码性能 <= 命令式代码性能 为什么会这样呢? 还是拿上面的代码举例 假设我们要将button的内容改为 pre Step,那么命令式的实现就是: button.textContent = "pre Step"
很简单,就是直接修改 声明式的实现就是: <!--之前 -->
<button class="continue" @click="() => alert('next')">Next Step...</button>
<!--现在 -->
<button class="continue" @click="() => alert('next')">pre Step</button>
对于声明式框架来说,它需要找到更改前后的差异并只更新变化的地方。但是最终更新的代码仍然是: button.textContent = "pre Step"
假设直接修改的性能消耗为 A, 找出差异的性能消耗为 B, 那么就有: - 命令式代码的更新性能消耗 = A
- 声明式代码的更新性能消耗 = A + B
可以看到声明式代码永远要比命令式代码要多出找差异的性能消耗。 那既然声明式代码的性能无法超越命令式代码的性能,为什么我们还要选择声明式代码呢?这就要考虑到代码可维护性的问题了。当项目庞大之后,手动完成dom的创建,更新与删除明显需要更多的时间和精力。而声明式代码框架虽然牺牲了一点性能,但是大大提高了项目的可维护性,降低了开发人员的心智负担 那么,有没有办法能同时兼顾性能和可维护性呢? 有!那就是使用虚拟dom 虚拟DOM首先声明一个点,命令式代码只是理论上会比声明式代码性能高。因为在实际开发过程中,尤其是项目庞大之后,开发人员很难写出绝对优化的命令式代码。而Vue框架内部使用虚拟Dom + 内部封装Dom元素操作的方式,能让我们不用付出太多精力的同时,还能保证程序的性能下限,甚至逼近命令式代码的性能。 在讨论虚拟Dom的性能之前,我们首先要说明一个点:JavaScript层面的计算所需时间要远低于Dom层面的计算所需时间 看过浏览器渲染与解析机制的同学应该很明白为什么会这样。 我们在使用原生JavaScript编写页面时,很喜欢使用innerHTML,这个方法非常特殊,下面我们来比较一下使用虚拟Dom和使用innerHTML的性能差异。 创建页面时 我们在使用innerHTML创建页面时,通常是这样的: const data = "hello"
const htmlString = `<div>${data}</div>`
domcument.querySelect('.target').innerHTML = htmlString
这个过程需要先通过JavaScript层的字符串运算,然后是Dom层的innerHTML的Dom运算 (将字符串赋值给Dom元素的innerHTML属性时会将字符串解析为Dom树) 而使用虚拟Dom的方式通常是编译用户编写的类html模板得到虚拟Dom(JavaScript对象),然后遍历虚拟Dom树创建真实Dom对象 两者比较: innerHTML 虚拟Dom
JavaScript层面运算计算拼接HTML字符串创建JavaScript对象(虚拟Dom)
Dom层面运算新建所有Dom元素新建所有Dom元素可以看到两者在创建页面阶段的性能差异不大。尽管在JavaScript层面,创建虚拟Dom对象貌似更耗时间,但是总体来说,Dom层面的运算是一致的,两者属于同一数量级,宏观来看可认为没有差异。 更新页面时 使用innerHTML更新页面,通常是这样: //更新
const newData = "hello world"
const newHtmlString = `<div>${newData}</div>`
domcument.querySelect('.target').innerHTML = newHtmlString
这个过程同样是先通过JavaScript层的字符串运算,然后是Dom层的innerHTML的Dom运算。但是它在Dom层的运算是销毁所有旧的DOM元素,再全量创建新的DOM元素。 而使用虚拟Dom的方式通常是重新创建新的虚拟Dom(JavaScript对象),然后比较新旧虚拟Dom,找到需要更改的地方并更新Dom元素。 两者比较: innerHTML 虚拟Dom
JavaScript层面运算计算拼接HTML字符串创建JavaScript对象(虚拟Dom)+ Diff算法
Dom层面运算销毁所有旧的Dom元素,新建所有新的DOM元素必要的DOM更新可以看到虚拟DOM在JavaScript层面虽然多出一个Diff算法的性能消耗,但这毕竟是JavaScript层面的运算,不会产生数量级的差异。而在DOM层,虚拟DOM可以只更新差异部分,对比innerHTML的全量卸载与全量更新性能消耗要小得多。所以模板越大,元素越多,虚拟DOM在更新页面的性能上就越有优势。 总结现在我们可以回答这位面试官的问题了:JQuery属于命令式框架,Vue属于声明式框架。在理论上,声明式代码性能是不优于命令式代码性能的,甚至差于命令式代码的性能。但是声明式框架无需用户手动操作DOM,用户只需关注数据的变化。声明式框架在牺牲了一点性能的情况下,大大降低了开发难度,提高了项目的可维护性,且声明式框架通常使用虚拟DOM的方式,使其在更新页面时的性能大大提升。综合来说,声明式框架仍旧是更好的选择。
|