掘金同人账号:summer holiday,不定时发送【对标大厂的必会面试题】、【必会常用算法】、【必会高并发解决】、【优化】等专题栏目供大家学习观看
面试官:“请你简述Vue中的diff算法”。
你:
diff算法在什么时候运行呢?当我们当前组件所依赖的数值更新和组件创建时运行update函数,update函数会调用组件的render函数,render生成新的虚拟dom树,update的到新虚拟dom树的根节点,然后进入update函数内部,将_vnode也就是旧虚拟dom树替换成新的虚拟dom树,然后用一个变量将旧虚拟dom树保存起来,接下来调用patch函数进行diff比对,vue在进行比对时遵循以下原则:
然后vue的diff会根据新旧domTree进行深度优先同层比对,如果父节点比对发现不同则父节点的所有子节点无需进行比对跟着父节点一起死去,如果父节点比对发现相同则继续往下比对父节点的子节点然后再进行下一个父节点的子节点进行比较,以此递归比较下去,比较时首先会比较标签名是否一致,然后会比较元素的key值是否相同,如果是input元素则还会比较type类型,如果都相同表示相同,如果有一个不相同表示不相同,vue的diff算法会在新旧dom树的头尾处记录头尾指针位置,相互聚拢,逐渐比较,来保证真实dom的高复用,当新虚拟dom树的头指针大于新虚拟dom树的尾指针则表示新虚拟dom树比对完成,diff结束,然后vue会根据新虚拟dom树的比对结果的真实dom树加入根节点中完成页面真实dom渲染完毕
patch
函数的对比流程
术语解释:
「相同」:是指两个虚拟节点的标签类型、key
值均相同,但input
元素还要看type
属性
任何元素都是可以有key值得
当匹配发现相同,并不是说vue就不处理了,他还是会对它进行处理,只不过这个处理就不动DOM了,他只是修改DOM值,而如果DOM类型的匹配不相同的话,则会去动这个DOM对它进行删除等其他操作了,该判断在vue源码中函数名叫:seeNode
「新建元素」:是指根据一个虚拟节点提供的信息,创建一个真实dom元素,同时挂载到虚拟节点的elm
属性上
「销毁元素」:是指:vnode.elm.remove()
「更新」:是指对两个虚拟节点进行对比更新,它仅发生在两个虚拟节点「相同」的情况下。具体过程稍后描述。
「对比子节点」:是指对两个虚拟节点的子节点进行对比,具体过程稍后描述
详细流程:
如果两个节点:
「相同」,进入**「更新」流程**
将旧节点的真实dom赋值到新节点:newVnode.elm = oldVnode.elm
对比新节点和旧节点的属性,有变化的更新到真实dom中
当前两个节点处理完毕,开始**「对比子节点」**
不「相同」
图解新旧虚拟dom树比对过程
「圆圈」:虚拟dom树
「方块」:真实dom树
「红色箭头」:头指针
「绿色箭头」:尾指针
「不同颜色的圆」:表示不同标签名的虚拟dom
「不同颜色的圆」:圆内的数字表示虚拟dom的key
没有旧虚拟dom树得情况:
运行render函数得到一个虚拟dom树,因为diff是深度优先,同层比较的,所以我们就拿同层的新旧虚拟dom树进行比较演示
这里我们可以看到由于第一次加载并没有所谓的旧虚拟dom树,所以vue就会拿到这个组件的根节点 e l ,然后直接遍历虚拟 d o m 树,生成相对应的真实 d o m ,并挂在到 el,然后直接遍历虚拟dom树,生成相对应的真实dom,并挂在到 el,然后直接遍历虚拟dom树,生成相对应的真实dom,并挂在到el真实dom根节点的子节点中
有旧虚拟dom树的情况:
首先我们两颗虚拟dom树在进行diff比对时遵循一个比对顺序:
为什么要遵循这个查找规则呢?
因为操作真实dom的性能消耗会很大,所以vue为了尽量的减少真实dom的操作,特别是新增和删除,所以它会这样:
在「对比子节点」时,vue一切的出发点,都是为了:
根据以上原则于是我们接下来进行图示解析Vue中的diff算法:
首先我们拥有两颗新旧dom树,上面的时旧树,每一个虚拟dom节点绑定着一个相对应的真实dom,下面的是真实dom节点现在正在准备与旧dom树进行diff比对,然后根据比对结果进行真实dom操作
此时我们先比对【头头比对】:
发现元素标签相同还有key相同,于是将下面的新虚拟dom树的该节点与这个真实dom绑定起来
然后新旧头指针向后移动:
此时又去比对【头头】:
同样的匹配规则,于是新旧头指针再次向后移:
此时我们发现现在的头头不相等了,于是开始【头尾比较】:
我们发现头尾的3和6标签名一不一样key值也不一样,所以头尾不相等,于是【尾头比较】:
我们发现5和8虽然标签一致,但是key值不一致,所以不相等:于是进行以新头尾基准查找旧节点:
我们发现还是找不到任何一个与8相等的旧虚拟dom,于是vue没办法了只能新建一个真实dom8了:
然后我们需要将真实dom根据新虚拟dom树的位置进行调整:
此时新头向后移:
进行【头头】比较:
3和4的标签和key都不匹配所以不相等:
进行【头尾】比较:
3和6的标签和key都不匹配所以不相等:
进行【尾头】比较:
5和4的key不匹配所以不相等:
进行【尾尾】比较:
不匹配
进行【以新头为基准查找旧虚拟dom】比较:
此时发现在旧虚拟dom树中发现了4:
此时需要根据新虚拟dom树的位置对真实dom的位置进行调整:
此时新头向后移动:
进行【头头】比较:
发现3和5的key值不相同
进行【头尾】比较:
3和6标签和key都不相同
进行【尾头】比较:
5和5的标签不相同:
进行【尾尾】比较:
不匹配
进行【以新头为基准查找旧树】比较:
发现没找到任何与之匹配的旧树
于是vue没办法生成新的真实dom:
根据新虚拟dom树调整真实dom树:
移动新头:
进行【头头】比较:
不匹配
进行【头尾】比较:
不匹配
进行【尾头】比较:
不匹配
进行【尾尾】比较:
不匹配
vue没办法生成新的真实dom并调整位置:
头大于尾表示新虚拟dom树已经比较完成,diff结束,不管旧虚拟dom了,记住它是不会去抹掉旧虚拟dom对真实dom的线的,垃圾回收器自己会去将旧虚拟dom树删掉的所以不需要管,于是我们得到新的dom结构(哪些未匹配上的旧真实dom,vue没办法只能remove掉了):
添加到根元素的子元素中:
完成渲染
「对比子节点」
在「对比子节点」时,vue一切的出发点,都是为了: