本文全部代码小的已经上传github?
虚拟DOM
直观来说,虚拟DOM其实就是用数据结构表示真实的DOM结构。使用它的原因是,频繁的操作DOM会使得网站的性能下降,为了保证性能,我们需要使得DOM的操作尽量精简,我们可以通过操作虚拟DOM的方法,去比较新旧节点的差异然后精确的获取最小的,最为必要的DOM集合,最终挂载到真实的DOM上。因为操作数据结构,远比我们直接修改DOM节点来的快,我们真实的DOM操作在最好的情况下,其实只需要在最后来那么一下,不是吗
如何表示DOM结构
这是一段列表的DOM结构,我们分析一下,其中需要包含的信息有
1. 标签类型 ul,li...
2. 标签属性 class,style...
3. 孩子节点ul->li li->text ...
无论再复杂的结构,也都是类似的,那么我们在找到DOM结构的共性之后,我们应该怎么表示呢
通过这张图我们可以发现,我们可以用对象JS对象轻易地就将它表示出来,几个属性也是非常好理解
- tagName对应真实的标签类型
- attrs表示节点上的所有属性
- child表示该节点的孩子节点
那这样我们是不是可以给这个虚拟DOM设定一个类 like this
function newElement(tag,attr,child){ //创建对象函数
return new Element(tag,attr,child)
}
测试一下
ok没问题是不是,那现在虚拟DOM其实就已经被创建出来了,那么有了虚拟DOM之后怎么挂载到真实DOM上呢
生成真实DOM节点
首先我们会需要一个根据对象属性来设置标签属性的方法
然后我们在类的内部添加创建节点的render方法
到这里我们就可以通过使用render方法创建真实的DOM节点了,在方法内部,我们通过调用SetVdToDom方法对属性进行设置,然后对子节点进行类型判断,递归到最后剩下的文本节点。
最后我们通过一个renderDom方法将dom渲染到浏览器看看
//vdmock.js 部分
const VdObj1 = newElement('ul',{id: 'list'},[
newElement('li',{class: 'list-1',style:'color:red' }, ['lavie']),
newElement('li',{class: 'list-2' }, ['virtual dom']),
newElement('li',{class: 'list-3' }, ['React']),
newElement('li',{class: 'list-4' }, ['Vue'])
])
const RealDom = VdObj1.render()
const renderDom = function(element,target){
target.appendChild(element)
}
export default function start(){
renderDom(RealDom,document.body)
}
// index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script type="module" src="./vdmock.js" ></script>
<title>Document</title>
</head>
<body >
<script type="module" >
import start from './vdmock.js'
start()
</script>
</body>
</html>
结果如下:
虚拟DOM diff
通过上面方法,我们可以很简单的生成虚拟DOM并且将它渲染到浏览器上面,那么我们在用户进行操作之后,如何计算出前后虚拟DOM之间的差异呢?下面就来介绍一下diff算法
我们通过给diff传入新旧的两个节点通过内部的getDiff递归对比节点并存储变化然后返回,下面我们来实现一下getDiff
获取最小差异数组
const REMOVE = 'remove'
const MODIFY_TEXT = 'modify_text'
const CHANGE_ATTRS = 'change_attrs'
const TAKEPLACE = 'replace'
let initIndex = 0
const getDiff = (oldNode,newNode,index,difference)=>{
let diffResult = []
//新节点不存在的话说明节点已经被删除
if(!newNode){
diffResult.push({
index,
type: REMOVE
}) //如果是文本节点直接替换就行
}else if(!oldNode){ //旧节点说明new是新增节点直接添加就行
diffResult.push({
type: TAKEPLACE,
newNode
})
}else if(typeof newNode === 'string' && typeof oldNode === 'string'){
if(oldNode !== newNode){
diffResult.push({
index,
value: newNode,
type: MODIFY_TEXT
})
} //如果节点类型相同则则继续比较属性是否相同
}else if(oldNode.tagName === newNode.tagName){
let storeAttrs = {}
for(let key in oldNode){
if(oldNode[key] !== newNode[key]){
storeAttrs[key] = newNode[key]
}
}
for (let key in newNode){
if(!oldNode.hasOwnProperty(key)){
storeAttrs[key] = newNode[key]
}
}
if(Object.keys(storeAttrs).length>0){
diffResult.push({
index,
value: storeAttrs,
type: CHANGE_ATTRS
})
} //遍历子节点
// oldNode.child.forEach((child,index)=>{
// console.log(child,111)
// getDiff(child,newNode.child[index],++initIndex,difference)
// }) //如果类型不相同,那么无需对比直接替换掉就行
newNode.child.forEach((child,index)=>{
getDiff(oldNode.child[index],child,++initIndex,difference)
})
}else if(oldNode.tagName !== newNode.tagName){
diffResult.push({
type: TAKEPLACE,
newNode
})
} //最后将结果返回
if(diffResult.length){
difference[index] = diffResult
}
}
测试结果如下:
更新dom
现在我们已经生成了两个虚拟DOM,并且将两个DOM之间的差异用对象的方式保存了下来,接下来,我们就要通过这些来将差异更新到真实的DOM上面去!!!
pace函数会自身进行递归,对当前节点的差异用dofix进行更新
const doFix = (node,difference) =>{
difference.forEach(item=>{
switch (item.type){
case 'change_attrs':
const attrs = item.value.attrs
for( let key in attrs ){
if(node.nodeType !== 1) //nodeType为1表示节点
return
const value = attrs[key]
if(value){
SetVdToDom(node,key,value)
}else{
node.removeAttribute(key)
}
}
break
case 'modify_text':
node.textContent = item.value
break
case 'replace':
let newNode = (item.newNode instanceof Element) ? item.newNode.render(item.newNode) :
document.createTextNode(item.newNode)
node.parentNode.replaceChild(newNode,node)
break
case 'remove' :
node.parentNode.removeChild(node)
break
default:
break
}
})
}
万事具备,那我们来测试一下!
const VdObj1 = newElement('ul',{id: 'list'},[
newElement('li',{class: 'list-1',style:'color:red' }, ['lavie']),
newElement('li',{class: 'list-2' }, ['virtual dom']),
newElement('li',{class: 'list-3' }, ['React']),
newElement('li',{class: 'list-4' }, ['Vue']) ,
])
const VdObj = newElement('ol',{id: 'list'},[
newElement('h2',{class: 'list-1',style:'color:green' }, ['lavieee']),
newElement('li',{class: 'list-2' }, ['virtual dom']),
newElement('li',{class: 'list-3' }, ['React']),
newElement('li',{class: 'list-4' }, ['Vue']) ,
newElement('li',{class: 'list-5' }, ['Dva']) ,
newElement('li',{class: 'list-5' }, ['Dva'])
])
const RealDom = VdObj1.render()
const renderDom = function(element,target){
target.appendChild(element)
}
export default function start(){
renderDom(RealDom,document.body)
const diffs = diff(VdObj1,VdObj)
fixPlace(RealDom,diffs)
}
before
diff after
嘻嘻完美
通过这几个例子下来,其实虚拟dom的思想就已经可以实现了,我们在使用框架的过程中如果可以梳理清楚其中的核心概念,一定会走的更加踏实。
最后祝大家新年快乐,牛年offer拿到手软,加薪不加班?!!!!
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!