VirtualDOM普通JS对象描述DOM对象DOM对象:成员多,成本高VirtualDOM{sel:'div',//nodeselectordata:{},//存储节点属性的对象,对应节点的sel[prop]属性,如onclick,styletext:'Hello',//如果是文本节点,则对应文本节点的textContent,否则为nullchildren:undefined,//存储子节点数组,每个子节点也是vnode结构体elm:undefined,//引用真实节点key:'msg',}为什么要用virtualDOM来维护view和state的关系,virtualDOM可以维护程序状态,跟踪最后状态并在复杂视图的情况下提高渲染性能,通过比较前后两次差异更新真实DOM跨平台浏览器平台渲染DOM服务器渲染SSR(Nuxt.js/Next.js)原生application(Weex/ReactNative)applet(mpvue/uni-app)virtualDO内部使用的VirtualDOMM库SnabbdomVue2.x是修改后的Snabbdom。可以使用TypeScriptvirtual-domSnabbdom通过模块扩展源代码//1.导入模块import{init,h,styleModule,eventListenersModule}from'snabbdom';//2.注册模块constpatch=init([styleModule,eventListenersModule]);//3.使用h()函数的第二个参数传入模块中使用的数据(对象)letvnode=h('div',[h('h1',{style:{backgroundColor:'red'}},'HelloWorld'),h('p',{on:{click:eventHandler}},'这是屁股on')])functioneventHandler(){alert('hello')}letapp=document.querySelector('#app')patch(app,vnode)init挂载钩子函数,接收模块列表数组,并返回补丁函数import{classModule,styleModule}from"snabbdom";constpatch=init([classModule,styleModule]);patchinit函数返回并接受两个参数,第一个表示当前视图的DOM祖先或Vnode(如果是DOM则转为Vnode),第二个表示如果传入一个新的Vnode带有DOM元素有了父元素,传入的元素将被转换为DOM的newVnode替换。如果传入一个oldVnode,snabbdom会匹配较新的Vnode。补丁(旧节点,新节点);h接收一个字符串类型的标签/选择器、一个可选的数据对象和一个可选的字符串或子数组。从“snabbdom”导入{h};constvnode=h("div",{style:{color:"#000"}},[h("h1","Headline"),h("p","Aparagraph"),]);HooksSnabbdom提供了丰富的钩子选择。挂钩既被模块用于扩展Snabbdom,也被用于普通代码以在虚拟节点生命周期中的所需点执行任意代码。Snabbdom支持几个钩子:pre,create,update,destroy,remove,posth("div.row",{key:movie.rank,hook:{insert:(vnode)=>{movie.elmHeight=vnode.elm.offsetHeight;},},});Diff算法Snbbdom根据DOM的特点对传统的diff算法进行了优化,只比较同级节点的执行过程。OldCh和newCh各有两个变量oldStartIdx、oldEndIdx、newStartIdx、newEndIdx,它们的2个变量相互比较,一共有4种比较方式。如果四个比较都不匹配,如果设置了key,则使用key进行比较。在比较过程中,变量会向中间倾斜。一旦newStartIdx>newEndIdx或oldStartIdx>oldEndIdx表示至少遍历了oldCh和newCh之一,则比较结束。循环比较oldStartVnode|newStartVnode如果新旧起始节点相同Vnode(key和sel相同),则调用patchVnode()比较更新节点并将新旧起始索引后移oldStartIdx++/newStartIdx++如果新旧节点不相同Vnode,继续下一步比较oldEndVnode|newEndVnode如果新旧起始节点相同Vnode(key和sel相同),则调用patchVnode()比较更新节点,将旧起始和新起始索引前移oldEndIdx--/newEndIdx--如果旧和新节点不是sameVnode,proceed下一步是比较oldStartVnode|newEndVnode如果新旧起始节点是sameVnode(key和sel相同),调用patchVnode()比较更新节点。将oldStartVnode对应的DOM元素向右移动,更新索引。如果新旧节点不是sameVnode,则进入下一步比较oldEndVnode|newEndVnode如果新旧起始节点是sameVnode(key和sel相同),调用patchVnode()比较更新节点。将oldEndVnode对应的DOM元素向左移动,更新索引。如果新旧节点不是sameVnode,则进行下一次比较遍历没有上述四种情况的新节点,使用newStartNode的key在旧节点数组中寻找相同的节点。如果没有找到,说明newStartNode是一个新节点。创建与新节点对应的DOM元素并将其插入到DOM树中。如果找到,则判断新节点该节点的sel选择器是否与找到的旧节点相同。如果不是,则表示该节点已被修改。重新创建相应的DOM元素并将其插入到DOM树中。如果相同,将elmToMove对应的DOM元素向左移动。当先遍历完旧节点的所有子节点(oldStartIdx>oldEndIdx)时循环结束,循环结束——说明还有剩余的新节点,将右侧新节点的所有子节点批量插入到对(newStartIdx>newEndIdx)。有剩余节点,batch剩余节点删除key的作用是通过key值准确判断两个节点是否相同,避免频繁更新不同元素,减少不必要的DOM操作,提高性能。标签名相同的元素的过渡切换也需要添加key属性。让他们区分开来,否则标签名相同的虚拟DOM只会更新内容
