VDOM VS diff算法
1. 虚拟DOM( VDOM ) 和 diff算法
Vue 通过建立一个虚拟 DOM 来追踪自己要如何改变真实 DOM。
请仔细看这行代码:
return createElement('h1', this.blogTitle)createElement
到底会返回什么呢?
其实不是一个实际的 DOM 元素。它更准确的名字可能是 createNodeDescription,因为它所包含的信息会告诉 Vue 页面上需要渲染什么样的节点,包括及其子节点的描述信息。我们把这样的节点描述为**“虚拟节点 (virtual node)”,也常简写它为“VNode”。“虚拟 DOM”是我们对由 Vue 组件树建立起来的整个 VNode 树的称呼。**
1.为什么需要虚拟DOM
DOM是很慢的,其元素非常庞大,页面的性能问题鲜有由JS引起的,大部分都是由DOM操作引起的。如果对前端工作进行抽象的话,主要就是维护状态和更新视图;而更新视图和维护状态都需要DOM操作。其实近年来,前端的框架主要发展方向就是解放DOM操作的复杂性。在jQuery出现以前,我们直接操作DOM结构,这种方法复杂度高,兼容性也较差;有了jQuery强大的选择器以及高度封装的API,我们可以更方便的操作DOM,jQuery帮我们处理兼容性问题,同时也使DOM操作变得简单;但是聪明的程序员不可能满足于此,各种MVVM框架应运而生,有angularJS、avalon、vue.js等,MVVM使用数据双向绑定,使得我们完全不需要操作DOM了,更新了状态视图会自动更新,更新了视图数据状态也会自动更新,可以说MMVM使得前端的开发效率大幅提升,但是其大量的事件绑定使得其在复杂场景下的执行性能堪忧;有没有一种兼顾开发效率和执行效率的方案呢?ReactJS就是一种不错的方案,虽然其将JS代码和HTML代码混合在一起的设计有不少争议,但是其引入的Virtual DOM(虚拟DOM)却是得到大家的一致认同的。
2.理解虚拟DOM
虚拟的DOM的核心思想是:对复杂的文档DOM结构,提供一种方便的工具,进行最小化地DOM操作。这句话,也许过于抽象,却基本概况了虚拟DOM的设计思想
- (1) 提供一种方便的工具,使得开发效率得到保证
- (2) 保证最小化的DOM操作,使得执行效率得到保证
总结:
1. 虚拟DOM 是什么?
(虚拟DOM是利用 了js的对象的Object的对象模型来模拟真实DOM, 那么它的结构是一个树形结构.)
虚拟DOM是干什么的?
这就要从浏览器本身讲起如我们所知,在浏览器渲染网页的过程中,加载到HTML文档后,会将文档解析并构建DOM树,然后将其与解析CSS生成的CSSOM树一起结合产生爱的结晶——RenderObject树,然后将RenderObject树渲染成页面(当然中间可能会有一些优化,比如RenderLayer树)。这些过程都存在与渲染引擎之中,渲染引擎在浏览器中是于JavaScript引擎(JavaScriptCore也好V8也好 分离开的,但为了方便JS操作DOM结构,渲染引擎会暴露一些接口供JavaScript调用。由于这两块相互分离,通信是需要付出代价的,因此JavaScript调用DOM提供的接口性能不咋地。各种性能优化的最佳实践也都在尽可能的减少DOM操作次数。
虚拟DOM干了什么?
它直接用JavaScript实现了DOM树(大致上)。组件的HTML结构并不会直接生成DOM,而是映射生成虚拟的JavaScript DOM结构,React又通过在这个虚拟DOM上实现了一个 diff 算法找出最小变更,再把这些变更写入实际的DOM中。这个虚拟DOM以JS结构的形式存在,计算性能会比较好,而且由于减少了实际DOM操作次数,性能会有较大提升.
来一波关于虚拟DOM的代码示例
<body>
<div id="app">
<header class="header"> 头部 </header>
<section class="content"> 内容 </section>
<footer> 底部 </footer>
<section> 内容 </section>
<footer> 底部 </footer>
</div>
</body>
<script>
var vdom = {
vnode: {
tag: 'div',
attr: {
idName: '#app'
},
content: [
{
tag: 'header',
content: [
'头部'
]
},
{
tag: 'section',
content: [
'内容'
]
},
{
tag: 'footer',
content: [
'底部'
]
}
]
}
}
function render(parentNode,vnode,id,className){
var app = document.createElement('DIV')
app.id = 'app'
parentNode.appendChild(app)
}
// render----------------
vdom.vnode.content[0].className = 'header'
vdom = {
vnode: {
tag: 'div',
attr: {
idName: '#app'
},
content: [
{
tag: 'header',
attr: {
className: 'header'
},
content: [
'头部'
]
},
{
tag: 'section',
content: [
'内容'
]
},
{
tag: 'footer',
content: [
'底部'
]
}
]
}
}
vdom.vnode.content[1].className = 'content'
vdom = {
vnode: {
tag: 'div',
attr: {
idName: '#app'
},
content: [
{
tag: 'header',
attr: {
className: 'header'
},
content: [
'头部'
]
},
{
tag: 'section',
content: [
'内容'
]
},
{
tag: 'footer',
content: [
'底部'
]
}
]
}
}
// 。。。 经过多次的VDOM操作之后
// 通过render函数进行渲染
// 通过diff算法, 将所有的vdom对比一边, 找出不同的地方, 然后进行render函数渲染
var obj = {
className: 'yyb'
}
var obj1 = {
className: 'zhangsan'
}
for ( var i in obj ){
if( obj1[i] ){
//如果有
if( obj[i] === obj1[i]){
// 值是一样的
}else{
// 值不一样
}
}else{
//没有属性
}
}
// 有的
</script>
</html>
2. diff算法(diff算法作为 Virtual DOM的加速器,其算法的改进优化是React整个界面渲染的基础和性能的保障)
diff流程图
当数据发生改变时,set方法会让调用Dep.notify通知所有订阅者Watcher,订阅者就会调用patch给真实的DOM打补丁,更新相应的视图。
总结
- diff算法是用来比较两个或是多个文件, 返回值是文件的不同点
- 在采取diff算法比较新旧节点的时候,比较只会在同层级进行, 不会跨层级比较。
- diff算法是同级比较的
- diff思维也是来自后端
- diff算法的比较思维
patchVnode (oldVnode, vnode) {
const el = vnode.el = oldVnode.el
let i, oldCh = oldVnode.children, ch = vnode.children
if (oldVnode === vnode) return
if (oldVnode.text !== null && vnode.text !== null && oldVnode.text !== vnode.text) {
api.setTextContent(el, vnode.text)
}else {
updateEle(el, vnode, oldVnode)
if (oldCh && ch && oldCh !== ch) {
updateChildren(el, oldCh, ch)
}else if (ch){
createEle(vnode) //create el's children dom
}else if (oldCh){
api.removeChildren(el)
}
}
}
比较后会出现四种情况:
1、此节点是否被移除 -> 添加新的节点
2、属性是否被改变 -> 旧属性改为新属性
3、文本内容被改变-> 旧内容改为新内容
4、节点要被整个替换 -> 结构完全不相同 移除整个替换
(我在想这算是一个缺点吗?相同子节点不能重复利用了…)
- 整个VDOM的使用流程(Vue)
-
创建VDOM树
-
利用render函数渲染页面
(1. 用JavaScript模拟DOM树并渲染这个DOM树) -
数据改变,生成新的vDOM
** (2. 比较新老DOM树得到比较的差异对象)** -
通过diff算法比较 新 旧 两个VDOM , 将不同的地方进行修改, 相同的地方就地复用 , 最后在通过render函数渲染页面
(3. 把差异对象应用到渲染的DOM树。)