您好,欢迎来到三六零分类信息网!老站,搜索引擎当天收录,欢迎发信息

diff算法使用详解(附代码)

2024/4/12 3:07:59发布33次查看
这次给大家带来diff算法使用详解(附代码),diff算法使用的注意事项有哪些,下面就是实战案例,一起来看一下。
虚拟dom
diff算法首先要明确一个概念就是diff的对象是虚拟dom,更新真实dom则是diff算法的结果
vnode基类
constructor (   。。。  ) {   this.tag = tag   this.data = data   this.children = children   this.text = text   this.elm = elm   this.ns = undefined   this.context = context   this.fncontext = undefined   this.fnoptions = undefined   this.fnscopeid = undefined   this.key = data && data.key   this.componentoptions = componentoptions   this.componentinstance = undefined   this.parent = undefined   this.raw = false   this.isstatic = false   this.isrootinsert = true   this.iscomment = false   this.iscloned = false   this.isonce = false   this.asyncfactory = asyncfactory   this.asyncmeta = undefined   this.isasyncplaceholder = false  }
这个部分的代码 主要是为了更好地知道在diff算法中具体diff的属性的含义,当然也可以更好地了解vnode实例
整体过程
核心函数是patch函数
isundef判断(是不是undefined或者null)
// empty mount (likely as component), create new root elementcreateelm(vnode, insertedvnodequeue) 这里可以发现创建节点不是一个一个插入,而是放入一个队列中统一批处理
核心函数samevnode
function samevnode (a, b) {  return (   a.key === b.key && (    (     a.tag === b.tag &&     a.iscomment === b.iscomment &&     isdef(a.data) === isdef(b.data) &&     sameinputtype(a, b)    ) || (     istrue(a.isasyncplaceholder) &&     a.asyncfactory === b.asyncfactory &&     isundef(b.asyncfactory.error)    )   )  ) }
这里是一个外层的比较函数,直接去比较了两个节点的key,tag(标签),data的比较(注意这里的data指的是vnodedata),input的话直接比较type。
export interface vnodedata {  key?: string | number;  slot?: string;  scopedslots?: { [key: string]: scopedslot };  ref?: string;  tag?: string;  staticclass?: string;  class?: any;  staticstyle?: { [key: string]: any };  style?: object[] | object;  props?: { [key: string]: any };  attrs?: { [key: string]: any };  domprops?: { [key: string]: any };  hook?: { [key: string]: function };  on?: { [key: string]: function | function[] };  nativeon?: { [key: string]: function | function[] };  transition?: object;  show?: boolean;  inlinetemplate?: {   render: function;   staticrenderfns: function[];  };  directives?: vnodedirective[];  keepalive?: boolean; }
这会确认两个节点是否有进一步比较的价值,不然直接替换
替换的过程主要是一个createelm函数 另外则是销毁oldvnode
// destroy old node     if (isdef(parentelm)) {      removevnodes(parentelm, [oldvnode], 0, 0)     } else if (isdef(oldvnode.tag)) {      invokedestroyhook(oldvnode)     }
插入过程简化来说就是判断node的type分别调用
createcomponent(会判断是否有children然后递归调用)
createcomment
createtextnode
创建后使用insert函数
之后需要用hydrate函数将虚拟dom和真是dom进行映射
function insert (parent, elm, ref) {   if (isdef(parent)) {    if (isdef(ref)) {     if (ref.parentnode === parent) {      nodeops.insertbefore(parent, elm, ref)     }    } else {     nodeops.appendchild(parent, elm)    }   }  }
核心函数
function patchvnode (oldvnode, vnode, insertedvnodequeue, removeonly) {   if (oldvnode === vnode) {    return   }   const elm = vnode.elm = oldvnode.elm   if (istrue(oldvnode.isasyncplaceholder)) {    if (isdef(vnode.asyncfactory.resolved)) {     hydrate(oldvnode.elm, vnode, insertedvnodequeue)    } else {     vnode.isasyncplaceholder = true    }    return   }   if (istrue(vnode.isstatic) &&    istrue(oldvnode.isstatic) &&    vnode.key === oldvnode.key &&    (istrue(vnode.iscloned) || istrue(vnode.isonce))   ) {    vnode.componentinstance = oldvnode.componentinstance    return   }   let i   const data = vnode.data   if (isdef(data) && isdef(i = data.hook) && isdef(i = i.prepatch)) {    i(oldvnode, vnode)   }   const oldch = oldvnode.children   const ch = vnode.children   if (isdef(data) && ispatchable(vnode)) {    for (i = 0; i < cbs.update.length; ++i) cbs.update[i](oldvnode, vnode) if (isdef(i = data.hook) && isdef(i = i.update)) i(oldvnode, vnode) } if (isundef(vnode.text)) { if (isdef(oldch) && isdef(ch)) { if (oldch !== ch) updatechildren(elm, oldch, ch, insertedvnodequeue, removeonly) } else if (isdef(ch)) { if (isdef(oldvnode.text)) nodeops.settextcontent(elm, '') addvnodes(elm, null, ch, 0, ch.length - 1, insertedvnodequeue) } else if (isdef(oldch)) { removevnodes(elm, oldch, 0, oldch.length - 1) } else if (isdef(oldvnode.text)) { nodeops.settextcontent(elm, '') } } else if (oldvnode.text !== vnode.text) { nodeops.settextcontent(elm, vnode.text) } if (isdef(data)) { if (isdef(i = data.hook) && isdef(i = i.postpatch)) i(oldvnode, vnode) } }
const el = vnode.el = oldvnode.el 这是很重要的一步,让vnode.el引用到现在的真实dom,当el修改时,vnode.el会同步变化。
比较二者引用是否一致
之后asyncfactory不知道是做什么的,所以这个比较看不懂
静态节点比较key,相同后也不做重新渲染,直接拷贝componentinstance(once命令在此生效)
如果vnode是文本节点或注释节点,但是vnode.text != oldvnode.text时,只需要更新vnode.elm的文本内容就可以
children的比较
如果只有oldvnode有子节点,那就把这些节点都删除
如果只有vnode有子节点,那就创建这些子节点,这里如果oldvnode是个文本节点就把vnode.elm的文本设置为空字符串
都有则updatechildren,这个之后详述
如果oldvnode和vnode都没有子节点,但是oldvnode是文本节点或注释节点,就把vnode.elm的文本设置为空字符串
updatechildren
这部分重点还是关注整个算法
首先四个指针,oldstart,oldend,newstart,newend,两个数组,oldvnode,vnode。
function updatechildren (parentelm, oldch, newch, insertedvnodequeue, removeonly) { let oldstartidx = 0 let newstartidx = 0 let oldendidx = oldch.length - 1 let oldstartvnode = oldch[0] let oldendvnode = oldch[oldendidx] let newendidx = newch.length - 1 let newstartvnode = newch[0] let newendvnode = newch[newendidx] let oldkeytoidx, idxinold, vnodetomove, refelm while (oldstartidx <= oldendidx && newstartidx <= newendidx) { if (isundef(oldstartvnode)) { oldstartvnode = oldch[++oldstartidx] // vnode has been moved left } else if (isundef(oldendvnode)) { oldendvnode = oldch[--oldendidx] } else if (samevnode(oldstartvnode, newstartvnode)) { patchvnode(oldstartvnode, newstartvnode, insertedvnodequeue) oldstartvnode = oldch[++oldstartidx] newstartvnode = newch[++newstartidx] } else if (samevnode(oldendvnode, newendvnode)) { patchvnode(oldendvnode, newendvnode, insertedvnodequeue) oldendvnode = oldch[--oldendidx] newendvnode = newch[--newendidx] } else if (samevnode(oldstartvnode, newendvnode)) { // vnode moved right patchvnode(oldstartvnode, newendvnode, insertedvnodequeue) canmove && nodeops.insertbefore(parentelm, oldstartvnode.elm, nodeops.nextsibling(oldendvnode.elm)) oldstartvnode = oldch[++oldstartidx] newendvnode = newch[--newendidx] } else if (samevnode(oldendvnode, newstartvnode)) { // vnode moved left patchvnode(oldendvnode, newstartvnode, insertedvnodequeue) canmove && nodeops.insertbefore(parentelm, oldendvnode.elm, oldstartvnode.elm) oldendvnode = oldch[--oldendidx] newstartvnode = newch[++newstartidx] } else { if (isundef(oldkeytoidx)) oldkeytoidx = createkeytooldidx(oldch, oldstartidx, oldendidx) idxinold = isdef(newstartvnode.key) ? oldkeytoidx[newstartvnode.key] : findidxinold(newstartvnode, oldch, oldstartidx, oldendidx) if (isundef(idxinold)) { // new element createelm(newstartvnode, insertedvnodequeue, parentelm, oldstartvnode.elm, false, newch, newstartidx) } else { vnodetomove = oldch[idxinold] if (samevnode(vnodetomove, newstartvnode)) { patchvnode(vnodetomove, newstartvnode, insertedvnodequeue) oldch[idxinold] = undefined canmove && nodeops.insertbefore(parentelm, vnodetomove.elm, oldstartvnode.elm) } else { // same key but different element. treat as new element createelm(newstartvnode, insertedvnodequeue, parentelm, oldstartvnode.elm, false, newch, newstartidx) } } newstartvnode = newch[++newstartidx] } } if (oldstartidx > oldendidx) {    refelm = isundef(newch[newendidx + 1]) ? null : newch[newendidx + 1].elm    addvnodes(parentelm, refelm, newch, newstartidx, newendidx, insertedvnodequeue)   } else if (newstartidx > newendidx) {    removevnodes(parentelm, oldch, oldstartidx, oldendidx)   }  }
一个循环比较的几种情况和处理(以下的++ --均指index的++ --)比较则是比较的node节点,简略写法 不严谨 比较用的是samevnode函数也不是真的全等
整体循环不结束的条件oldstartidx <= oldendidx && newstartidx <= newendidx
oldstart === newstart,oldstart++ newstart++
oldend === newend,oldend-- newend--
oldstart === newend, oldstart插到队伍末尾 oldstart++ newend--
oldend === newstart, oldend插到队伍开头 oldend-- newstart++
剩下的所有情况都走这个处理简单的说也就两种处理,处理后newstart++
newstart在old中发现一样的那么将这个移动到oldstart前
没有发现一样的那么创建一个放到oldstart之前
循环结束后并没有完成
还有一段判断才算完
if (oldstartidx > oldendidx) {    refelm = isundef(newch[newendidx + 1]) ? null : newch[newendidx + 1].elm    addvnodes(parentelm, refelm, newch, newstartidx, newendidx, insertedvnodequeue)   } else if (newstartidx > newendidx) {    removevnodes(parentelm, oldch, oldstartidx, oldendidx)   }
简单的说就是循环结束后,看四个指针中间的内容,old数组中和new数组中,多退少补而已
相信看了本文案例你已经掌握了方法,更多精彩请关注其它相关文章!
推荐阅读:
pushstate与replacestate使用步骤详解
css3二级导航菜单制作步骤详解
以上就是diff算法使用详解(附代码)的详细内容。
该用户其它信息

VIP推荐

免费发布信息,免费发布B2B信息网站平台 - 三六零分类信息网 沪ICP备09012988号-2
企业名录 Product