虚拟DOM
虚拟DOM其实就是在真实DOM之前加了一层JS对象生成的DOM
- 用JS对象模拟DOM
- 把这个虚拟DOM对象转为真实的DOM插入到页面中
- 如果有事件修改了虚拟DOM,比较两个虚拟DOM树的差异,得到差异对象(补丁)
- 把差异对象应用到正则的DOM树上
diff算法
- Diff 比较两个虚拟DOM的区别(比较两个对象的区别)
- 根据两个虚拟对象的区别,创建出补丁(patch),描述改变的内容,将这个补丁用来更新页面
- 差异计算: 先序深度优先遍历
diff算法的三种优化策略
- 比较同级的节点(同一父节点的子节点)
- 当发现节点已经不存在,则该节点及其子节点会被完全删除掉,不会用于进一步的比较。这样只需要对树进行一次遍历,便能完成整个DOM树的比较。对于不同层的节点,只有简单的创建和删除。 - 相同类型的节点比较:对属性进行重设实现节点的转换
renderA: <div style={{color: 'red'}} />
renderB: <div style={{fontWeight: 'bold'}} />
=> [removeStyle color], [addStyle font-weight 'bold']
3. 列表节点的比较(key存在的意义)
- 添加、删除、排序
- 顺序的调整类似插入/删除
模拟一个虚拟DOM
// 节点类
class Element{
constructor(type,props,children){
this.type= type;
this.props= props;
this.children= children;
}
}
// 生成节点类实例
function createElement(type,props,children){
return new Element(type,props,children)
}
// 创建一个虚拟DOM对象
let virtualDOM= createElement('ul',{class: 'list'},[
createElement('li',{class: 'item'},['a']),
createElement('li',{class: 'item'},['b']),
createElement('li',{class: 'item'},['c'])
])
console.log(virtualDOM)
// 设置属性的方法
function setAttr(node,key,value){
switch(key){
case 'value': // node是input或者textarea
if(node.tagName.toUpperCase()=== 'INPUT' || node.tagName.toUpperCase()=== 'TEXTAREA'){
node.value= value
}else{
node.setAttribute(key,value)
}
break;
case 'style':
node.style.cssText= value;
break;
default:
node.setAttribute(key,value)
break;
}
}
// render方法将vnode 转化成真实的dom
function render(eleObj){
let el= document.createElement(eleObj.type); //生成父节点
// 遍历父节点的props添加属性
for(let key in eleObj.props){
// 设置属性的方法
setAttr(el,key,eleObj.props[key])
}
// 遍历父节点的children
eleObj.children.forEach(item => {
item= (item instanceof Element)? render(item) : document.createTextNode(item) //创建文本节点
el.appendChild(item)
});
return el
}
let el= render(virtualDOM)
console.log(el)
// 将元素插入到页面
function renderDOM(el,target){
target.appendChild(el)
}
renderDOM(el,document.body)
模拟Diff算法
未完待续。。。