关于DOMDIFF原理,请点击进入任意门
虚拟DOM
virtual dom,也就是虚拟节点。它是通过JS的Object对象模拟DOM中的节点,然后再通过特定的render方法将其渲染成真实的DOM节点。
创建虚拟DOM
- 本文基于react来写,但是其实跟react也没多大关系
通过createElement方法来创建虚拟DOM,这个方法有三个属性type、props、children
- 首先,虚拟DOM本身是一个对象,例如:
- 那么,我们需要构建类似上图结构的一个对象,建一个index.js文件,代码如下:
import {createElement} from './element';
let vertualDom = createElement('ul',{class:'list'},[
createElement('li',{class:'item'},[1]),
createElement('li',{class:'item'},[2]),
createElement('li',{class:'item'},[3])
])
console.log(vertualDom);
- 现在的关键就在于,如何创建一个createElement函数,使得该函数的返回值就是图片上的值,既然上述代码引用了element.js文件,那么在该文件中写如下代码:
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);
}
export {createElement};
这时,打印出来的vertualDom就是图片中所示的结构。
-
结构有了,如何将这个对象结构,转化成真正的DOM才是关键。
-
根据结构所示,我们需要将最外层Element转化成DOM结构,然后再将children中的每个Element转化成DOM结构,这样一层层转化,就需要用到递归。
-
当然,我们在最开始的时候已经说了,转化成虚拟DOM转化成真实的DOM,需要用到render方法,所以,我们现在来构建这个render方法。
-
在index.js文件中:
import {createElement,render} from './element';
let vertualDom = createElement('ul',{class:'list'},[
createElement('li',{class:'item'},[1]),
createElement('li',{class:'item'},[2]),
createElement('li',{class:'item'},[3])
])
//通过render方法,返回一个真实的DOM
let el = render(vertualDom);//这里的el就是我们的真实DOM
console.log(vertualDom);
console.log(el);
- 在element.js中,写下render的逻辑:
class Element{
constructor(type,props,children){
this.type = type;
this.props = props;
this.children = children;
}
}
//设置属性
function setAttr(node,key,value){//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;
default:
node.setAttribute(key,value);
break;
}
//其他还有很多情况,加对应的case就可以了
}
function createElement(type,props,children){
return new Element(type,props,children);
}
//render方法可以将虚拟DOM转化成真实的DOM
function render(eleObj){
//这个eleObj就是虚拟DOM,也即是一开始打印的vertualDom结构
let el = document.createElement(eleObj.type);
for(let key in eleObj.props){
//设置属性的方法
setAttr(el,key,eleObj.props[key])
}
eleObj.children.forEach(child =>{
//判断child是不是一个element
child = (child instanceof Element) ? render(child) : document.createTextNode(child);
el.appendChild(child);
})
return el;
}
export {createElement,render,Element};
- render方法已经写好了,真实DOM已经打印出来了,现在就只差一步了,将真实的DOM渲染到页面上去。渲染这步极其简单,将真实的DOM插入到目标节点即可。所以我们再element.js封装一个renderDom方法,并且在index.js导入即可。
- element.js
...与前面的一样
//将DOM渲染到页面上
function renderDom(el,target){
target.appendChild(el);
}
export {createElement,render,Element,renderDom};
- index.js
import {createElement,render,renderDom} from './element';
let vertualDom = createElement('ul',{class:'list'},[
createElement('li',{class:'item'},[1]),
createElement('li',{class:'item'},[2]),
createElement('li',{class:'item'},[3])
])
//通过render方法,返回一个真实的DOM
let el = render(vertualDom);//这里的el就是我们的真实DOM
renderDom(el,window.root);//渲染到页面上
console.log(vertualDom);
console.log(el);
- 此时,在页面上就渲染出来了.
为什么通过虚拟DOM来更新节点性能好?
比如有一个ul-li列表,那么如果直接操作DOM,是整个ul都更新掉,而通过虚拟DOM来更新的话,会在虚拟DOM里算好哪些节点是要更新的,然后直接去操作这些需要更新的DOM,对于不需要更新的是不会变的。所以通过虚拟DOM来更新节点性能好。