什么是虚拟DOM
Virtual dom, 即虚拟DOM节点。它通过JS的Object对象模拟DOM中的节点,然后再通过特定的render方法将其渲染成真实的DOM节点。
虚拟DOM就是一个普通的JavaScript对象,包含了tag、props、children等属性。
虚拟dom的实现过程
- 通过js建立节点描述对象
- diff算法比较分析新旧两个虚拟DOM差异
- 将差异patch到真实dom上实现更新
如何生成一个dom这里我们拿react来举例
createElement
createElement 接受type, props,children三个参数创建一个虚拟标签元素DOM的方法。
function createElement(type, props, children) {
return new Element(type, props, children);
}
`
这是一个react组件
const App=()=>{
const yy=(e)=>{console.log(e)}
return (
<div style={{color:"red"}} className='xx' onClick={(e)=>{yy(e)}}>
<span>hello</span>
</div>
)
}
这段代码会被babel-loader转化成下面的代码
"use strict";
const App = () => {
const yy = e => {
console.log(e);
};
return /*#__PURE__*/React.createElement("div", {
style: {
color: "red"
},
className: "xx",
onClick: e => {
yy(e);
}
}, /*#__PURE__*/React.createElement("span", null, "hello"));
};
render方法
render方法接受一个虚拟节点对象参数,其作用是:将虚拟DOM转换成真实DOM。
什么是DOM Diff
Dom diff
Dom diff 则是通过JS层面的计算,返回一个patch对象,即补丁对象,在通过特定的操作解析patch对象,完成页面的重新渲染。
Diff 算法
规则:同层比较
Diff算法中有很多种情况,接下来我们以常见的几种情况做下讨论:
- 当节点类型相同时,去看一下属性是否相同 产生一个属性的补丁包 {type:‘ATTRS’, attrs: {class: ‘list-group’}}
- 新的dom节点不存在 {type: ‘REMOVE’, index: xxx}
- 节点类型不相同 直接采用替换模式 {type: ‘REPLACE’, newNode: newNode}
- 文本的变化:{type: ‘TEXT’, text: 1}
比较两颗虚拟DOM树的核心diff方法接受oldTree旧DOM树、newTree新DOM树两个参数,根据两个虚拟对象创建出补丁,描述改变的内容,将这个补丁用来更新DOM。该方法的核心在于walk递归树,该方法将比较后的差异节点放到补丁包中,具体递归树核心逻辑请看下方walk递归树小节。
function diff(oldTree, newTree) {
let patches = {};
let index = 0; // 默认先比较树的第一层
// 递归树 比较后的节点放到补丁包中
walk(oldTree, newTree, index, patches);
return patches;
}
这里我们经常会遇到这样的问题
当我们在react 或者vue中对数组遍历并返回多个节点是通常都要设置不同的key,为什么一定要这样做呢
举个删除dom的例子就明白了
-
把所有的虚拟dom想像成在一个数组中,当我们删除一个节点,那么它后面的节点就会往前靠
-
DOM Diff这是后会对之前的dom和现在的dom做比较(从前往后比较,当对比到被删除的节点处时,会发现后面的dom节点与之前的不一样了(出现这种情况是删除一个节点后后面的节点都往前靠了,所以对比的之前的结果都不一样)那么后面的都会被一一对比后更替)
这就是为什么我们在遍历时一定要设置key而且key不能为index因为他们默认就是用的index,所以设置成index会出现问题。因此我们的key必须是唯一的
优缺点
优点
- 小修改无需频繁更新DOM,框架的diff算法会自动比较,分析出需要
- 更新的节点,按需更新更新数据不会造成频繁的回流与重绘
- 表达力更强,数据更新更加方便保存的是js对象,具备跨平台能力
缺点
虚拟DOM同样也有缺点,首次渲染大量DOM时,由于多了一层虚拟DOM的计算,会比innerHTML插入慢。