当前位置: 首页 > Web前端 > HTML5

virtual-dom的原理及其简单实现

时间:2023-04-04 23:10:34 HTML5

前言大家熟知的React和Vue都使用了virtual-dom。VirtualDOM凭借其高效的diff算法,让我们不再关心性能问题,可以随心所欲地修改数据状态。在实际开发中,我们不需要关心VirtualDOM是如何实现的,但是真正有必要了解VirtualDOM的实现原理。本文基于https://github.com/livoras/si...DOM。一、前端应用状态管理在越来越复杂的前端应用中,状态管理是一个经常被提及的话题。从早期的刀耕火种到jQuery,再到现在流行的MVVM时代,状态管理的形式发生了翻天覆地的变化,我们不再需要维护大量的事件回调和监听器来更新视图。相反,我们使用双向数据绑定。我们只需要维护相应的数据状态就可以自动更新视图,大大提高了开发效率。然而,双向数据绑定并不是唯一的方式。还有一个很粗暴有效的方法:一旦数据发生变化,就重新绘制整个视图,也就是重新设置innerHTML。这种做法确实简单、粗暴、有效,但是如果仅仅因为局部的一个小数据变化而更新整个视图,性价比就太低了。而且事件,获取焦点的输入框等等,都需要重新处理。所以,对于小型应用或者局部小视图,完全可以这样做,但是对于复杂的大型应用,则不可取。所以我们可以使用JavaScript来模拟DOM树,将新渲染的对象树与旧树进行比较,记录变化,然后应用到真正的DOM树上,这样我们只需要改变原始视图的不同地方,而不需要一次重新渲染。这就是virtual-DOM的优势2.视图渲染与DOM对象相比,原生JavaScript对象的处理速度更快、更简单。DOM树上的结构和属性信息可以用JavaScript表示,例如:varelement={tagName:'ul',//节点标签名props:{//dom属性键值对id:'list'},孩子们:[{tagName:'li',道具:{class:'item'},孩子们:[“Item1”]},{tagName:'li',道具:{class:'item'},孩子们:["Item2"]},{tagName:'li',props:{class:'item'},children:["Item3"]}]}那么html渲染的结果是:项目1项目2项目3因为如果DOM树的信息可以用JavaScript来表示,那么DOM树就可以用JavaScript来构造了。但是,仅仅构建DOM树是没有用的。我们需要将JavaScript构建的DOM树渲染成真实的DOM树。用JavaScript表达一个dom节点非常简单。我们只需要记录它的节点类型,属性键值对,子节点:表示ul标签varul=newElement('ul',{id:'list'},[{tagName:'li',props:{class:'item'},children:["Item1"]},{tagName:'li',props:{class:'item'},children:["Item2"]},{tagName:'li',props:{class:'item'},children:["项目3"]}])说了这么多,他只是用JavaScript表达的一个结构,如何渲染成真正的DOM结构:Element.prototype.render=function(){letel=document.createElement(this.tagName),//节点名称props=this.props//节点属性for(varpropNameinprops){propValue=props[propName]el.setAttribute(propName,propValue)}this.children.forEach((child)=>{varchildEl=(child.hasOwnProperty('tagName'))?newElement(child.tagName,child.props,child.children).render():document.createTextN颂歌(孩子);el.appendChild(childEl);})returnel}如果我们要把ul渲染成DOM结构,只需要ulRoot=ul.render()document.appendChild(ulRoot)这样ul就完成了DOM的渲染也有了真正的DOM结构项目1项目2项目33.比较虚拟DOM树的差异React的核心算法是diff算法(这里指的是优化后的算法)。下面看看diff算法是如何实现的:diff只会比较同一个颜色框内的DOM节点,即如果发现同一个父节点下的所有子节点都不存在,则将节点和子节点全部删除,并且不会进行进一步的比较。在实际代码中,会深入遍历新旧树,并对每个节点进行标记。然后,在新旧树的比较中,记录差异。//diff算法,比较两棵树functiondiff(oldTree,newTree){varindex=0//当前节点的标志varpatches={}//记录每个节点的差异dfsWalk(oldTree,newTree,index,patches)returnpatches}functiondfsWalk(oldNode,newNode,index,patches){//比较newNode和oldNode的不同,记录patches[index]=[...]diffChildren(oldNode.children,newNode.children,index,patches)}functiondiffChildren(oldChildren,newChildren,index,patches){letleftNode=nullvarcurrentNodeIndex=indexoldChildren.forEach((child,i)=>{varnewChild=newChildren[i]currentNodeIndex=(leftNode&&leftNode.count)//计算节点的标签?currentNodeIndex+leftNode.count+1:currentNodeIndex+1dfsWalk(child,newChild,currentNodeIndex,patches)//遍历子节点leftNode=child})}例如:如果有差异图中的div,mark如果为0,则:patches[0]=[{difference},{difference}]同理,p为patches[1],span为patches[3],以此类推。补丁是指差异。这些差异包括:1.不同类型的节点,2.节点点类型一样,只是属性值不同,文本内容不同,所以有几种类型:varREPLACE=0,//replacereplacesREORDER=1,//中子节点的重新排序操作theparentnodePROPS=2,//propsattributeChangeTEXT=3//如果text文本内容中节点类型不同,说明需要替换。比如把div换成section,记录差异:patches[0]=[{type:REPLACE,node:newNode//section},{type:PROPS,props:{id:'container'}}]4.将difference应用到DOM树中在第二题中,构建了真正的DOM树的信息,所以首先对DOM树进行深度优先遍历,遍历时与patches对象进行比较,找出different,然后应用它对DOM操作。functionpatch(node,patches){varwalker={index:0}//记录当前节点的flagdfsWalk(node,walker,patches)}functiondfsWalk(node,walker,patches){varcurrentPatches=patches[walker.index]//这是当前节点的差异varlen=node.childNodes?node.childNodes.length:0for(vari=0;i{switch(currentPatch.type){caseREPLACE:varnewNode=(typeofcurrentPatch.node==='string')?document.createTextNode(currentPatch.node):currentPatch.node.render()node.parentNode.replaceChild(newNode,node)中断;caseREORDER:reorderChldren(node,currentPatch.moves)breakcasePROPS:setProps(node,currentPatch.props)breakcaseTEXT:if(node.textContent){node.textContent=currentPatch.content}其他{node.nodeValue=currentPatch.content}breakdefault:thrownewError('Unknownpatchtype'+currentPatch.type)}})}这下基本实现了粗略的virtual-dom,具体情况比较复杂,但是对我们来说已经足够了了解虚拟世界。具体代码和分析已经上传到github5.参考https://www.cnblogs.com/justa...https://github.com/livoras/bl...https://github.com/y8n/blog/i...https://medium.com/@deathmood...http://www.infoq.com/cn/artic...