本文使用的vue版本为3.0.4
- createApp() & mount()
- setup()
- render() h()
- Virtual Dom
- 生命周期hooks
- Proxy代理
- reactive() ref()
- computed()
- watch()
- provide() inject()
- directives()
- components()
什么是Virtual Dom?
英文全称Virtual Document Object Model,直译:虚拟的文档对象模型。
简而言之,就是使用js 对象去描述一个dom节点,有很多相关的文章描述,这里不再赘述。
我们可以像这样理解一个Virtual Dom。
<div id="app">my vdom</div>
const vdom = {
tag:'div',
text:'my vdom',
attributes:{
id:'app'
},
children:[]
}
而在vue中,有一种自己独特的Virtual Dom——VNode,在Virtual Dom的思想基础上,添加了一些vue运行编译所需要的属性,一个初始VNode模板在源码中是这样定义的:
// packages/runtime-core/src/vnode.ts
const vnode: VNode = {
__v_isVNode: true, // 区分VNode与普通js object的标志
[ReactiveFlags.SKIP]: true,
type,
props,
key: props && normalizeKey(props),
ref: props && normalizeRef(props),
scopeId: currentScopeId,
children: null,
component: null,
suspense: null,
ssContent: null,
ssFallback: null,
dirs: null,
transition: null,
el: null,
anchor: null,
target: null,
targetAnchor: null,
staticCount: 0,
shapeFlag,
patchFlag,
dynamicProps,
dynamicChildren: null,
appContext: null
}
为什么要使用Virtual Dom?
一个面试的常见问题:请谈谈你对虚拟dom的理解
初级回答:
操作虚拟dom比操作真实dom元素性能好。
请勿模仿!这样回答基本上等同于你告诉面试官:我不知道什么是虚拟dom。
进阶版:
将dom元素抽象为js对象,结合dom diff算法在更新视图时起到性能优化的效果。
有点内味了,但是感觉还差点什么。
高级版:
1.将页面元素抽象为对象,不再局限于浏览器的Dom,奠定了跨平台渲染的基础,打开了函数式UI编程的大门。
以通用的方式描述页面中的元素,根据不同的终端转化为不同的render function进行渲染
2.浏览器不会对Dom操作进行缓存 => 批量处理。意味着你操作了Dom多少次,浏览器就会渲染多少次。而引入了Virtual Dom的概念后,我们可以将多次Dom更新都先体现在Virtual Dom上,再统一映射到真实Dom节点,避免掉了一部分重复渲染工作。
Vue是怎么做的?
Vue中采用异步的方式更新
Dome,当观察到数据变化,会开启一个异步队列(优先采用Promise或MutationObserver的方式开启微任务队列,如果浏览器不支持,则会选择setTimeout(function(){},0)的方式开启宏任务队列),待本次event loop结束后,将所有变化同步到VNode中,再执行VNode -> render function -> dom的流程。
*注:由于兼容性原因,MutationObserver的方式已被废弃
3.不仅如此,在将VNode转化为Dom元素时还采用了优化后的diff算法:不再递归遍历整颗树,而是采用深度优先的方式逐层比较同级的树节点;还可以为Dom元素设置key值,加强节点的可复用性。
diff算法中的一些细节:
1.在对比同级树节点时,会先判断两个树节点是否对应同一个
Dom节点: a.如果不对应,则会用新的树节点将老的树节点整个替换掉。
b.如果对应,则会根据节点的类型以及是否含有子节点等情况进行下一步处理。
2.对于循环出来的
Dom元素(例如列表项li),为其设置key属性,生成key => index对,在其相对顺序发生变化时,可以最大限度的复用之前的元素。否则在发生例如节点内容未变化而只是顺序改变的情况时,只能对位处理,只要该位置的元素不同,就会进行patch(对Dom进行增、删、改)。3.补充上一条,除非你确定你的列表项顺序不会发生改变,也不会发生除了向列表尾部追加列表项以外的操作,否则不要使用索引
index作为key值。如果你的key依赖于列表顺序,那么在顺序发生变化以后,key值也会相应改变,key => idnex对就不再有意义。
由此可见,Virtula Dom的理想使用场景是:
Dom结构繁杂- 需要频繁的操作
Dom - 多端渲染
反过来,这些情况下则不适用Virtual Dom:
Dom结构简单- 几乎不需要对
Dom进行修改
在页面的首次渲染中,相较于Dom.innerHTML,Virtual Dom多了一步转化VNode的过程,速度有可能会慢于Dom.innerHTML;而在后续的Dom更新过程中,每次都要执行diff计算,而且内存中要始终维护一份树结构的备份。
所以要仔细斟酌,使用Virtual Dom所带来的提升是否能cover住所牺牲的部分。