VirtualDOM
# Virtual DOM
# 对虚拟DOM
的理解重要
从本质来说,虚拟dom
就是一个js
对象,通过对象的形式来表示dom
结构,将页面的装填抽象成js
对象,配合不同渲染工具可以更容易的跨平台,通过事物处理机制,将多次修改dom
一次性更新到页面中,防止页面渲染多次,减少修改dom
的重绘和重排,提高性能
设计虚拟dom
就是为了更好的跨平台,比如nodejs
就没有dom
,想要达成ssr
,就得借助虚拟dom
,在代码渲染到页面之前,vue
会将代码转化为虚拟dom
,以对象的形式描述真实dom
,每次变化之前,虚拟dom
会缓存一份,和变化后的进行diff
比较,重新渲染改变了的dom
节点
# 虚拟dom
的解析过程
对要插入的文档的
dom
树进行分析,使用js
对象表示出来(包含tagName
,props
,children
等),然后保存起来,最后将dom
片段插入文档当页面状态发生改变,需要对
dom
结构进行调整,根据变更的状态,重新构建一颗对象树,将新旧树进行对比,记录差异最后将有差异的地方应用到真实
dom
树上,就更新视图了
# 为什么要使用虚拟dom
- 手动操作
DOM
比较麻烦,还需要考虑浏览器兼容性问题,虽然有jQuery
等库简化DOM
操作,但是随着项目的复杂DOM
操作复杂提升 - 为了简化
DOM
的复杂操作于是出现了各种MVVM
框架,MVVM
框架解决了视图和状态的同步问题 - 为了简化视图的操作我们可以使用模板引擎,但是模板引擎没有解决跟踪状态变化的问题,于是
Virtual DOM
出现了 - 保证性能的下限,提供过的去的性能,页面的渲染流程包括:解析
HTML
-> 生成dom
-> 生成cssdom
->layout
->paint
->compile
重排重绘性能消耗
- 真实
DOM
:生成html
字符串 -> 重建所有DOM
元素 - 虚拟
DOM
:生成vNode
->DOMdiff
-> 必要的DOM
更新
- 真实
# vue
为什么采用 vdom
重要
引入
Virtual DOM
在性能方面的考量仅仅是一方面。
- 性能受场景的影响是非常大的,不同的场景可能造成不同实现方案之间成倍的性能差距,所以依赖细粒度绑定及
Virtual DOM
哪个的性能更好还真不是一个容易下定论的问题。 Vue
之所以引入了Virtual DOM
,更重要的原因是为了解耦HTML
依赖,这带来两个非常重要的好处是:
- 不再依赖
HTML
解析器进行模版解析,可以进行更多的AOT
工作提高运行时效率:通过模版AOT
编译,Vue
的运行时体积可以进一步压缩,运行时效率可以进一步提升;- 可以渲染到
DOM
以外的平台,实现SSR
、同构渲染这些高级特性,Weex
等框架应用的就是这一特性。
综上,
Virtual DOM
在性能上的收益并不是最主要的,更重要的是它使得Vue
具备了现代框架应有的高级特性。
AOT是什么
AOT
是Ahead-of-time compiler
模板编译的目的仅仅是将template
转化为render function
,这个过程,正好可以在项目构建的过程中完成,这样可以让实际组件在 runtime
时直接跳过模板渲染,进而提升性能,这个在项目构建的编译template
的过程,就是预编译
- 可以有更快的渲染速度:模版引擎的代码会预先被编译,优先给用户呈现
- 更早检查出
template
错误并报告给程序员 - 需下载的资源变少:预编译的影响只会在组件实例化的时候进行编译,生存
render
函数后不会进行二次编译,可以让实际组件在runtime
时候跳过模版渲染
# 虚拟DOM
会比真实DOM
性能好吗
在首次渲染大量dom
的时候由于多了一层虚拟dom
的计算,会比innerHTML
慢一些
但是他保证了性能的下限,在真实dom
操作的时候进行针对性的优化(diff
比较),还是更快的
# diff
算法的原理重要
在新老节点的dom
对比时:
首先对比节点本身,判断是不是同一个节点
- 如果不是就删除该节点重新创建节点来替换
- 如果是的话,进行
patchVnode
,判断如何对该节点的子节点进行处理,首先判断一方有子节点一方没有的情况 - 如果新的
children
没有子节点,就把旧的子节点去掉 - 如果都有子节点,进行
updateChildren
,判断如何对这些新老子节点进行操作
匹配时找到相同子节点,递归比较子节点
在diff
中,只对同层的子节点进行比较,放弃跨级的比较,时间复杂度从O(n^3)
-> O(n)
,也就是说当新旧children
都为多个子节点才要核心的diff
算法进行同层级比较
# Vue
中key
的作用
在
v-if
中使用key
,由于Vue
会高效渲染元素,通常会复原已有元素而不是从头渲染,如果有相同类型的元素,那么元素就会复用,如果是相同的input
元素也会复用,这样可能会出现input
输入一开始就有值的情况,我们通过key
来表示独立的元素v-for
使用key
v-for
更新已渲染过的元素列表,默认使用就地复用的策略,如果数据项顺序发生改变,vue
不会移动dom
来匹配顺序,而是简单复用,提供key
来跟踪元素的身份高效更新虚拟DOM
我们不使用index
作为key
是因为,无论数组怎么颠倒顺序,index
都是0,1,2,3,4
这样排列,导致Vue
会复用错误的旧节点