源码解析虚拟DOM & diff(patchVnode源码)

138 阅读1分钟

DOM和虚拟DOM

DOM是由HTML解析器 由上到下,从左到右读取标签,把他们分解成节点,构建DOM树;

虚拟DOM:当页面展示的内容更新的时候,如果我们通过操作真实的DOM,DOM操作的执行速度和js的速度有差异,js远比DOM操作的执行快,并且DOM操作也会引起浏览器的回流重绘

虚拟DOM就是为了解决浏览器性能问题而出现,用js对象模拟DOM节点,把一些DOM操作保存到一个js对象,在改变DOM之前,会先比较虚拟DOM的数据,不一致,才会将改变应用到真实DOM上。

简单来说:我们只需要操作虚拟DOM,然后再用虚拟DOM跟真实DOM比较,不一样的地方才去更新它(真实DOM);减少了真实DOM的操作次数

回流:当页面中的元素的大小或是位置等发生改变,浏览器会根据改变对页面的结构重新计算

重绘:当页面中元素的背景,颜色改变引发浏览器对元素重新描绘。

康康源码

function patchVnode (oldVnode, vnode, insertedVnodeQueue, removeOnly) {
    /*新旧节点相同则直接返回*/
    if (oldVnode === vnode) {
      return
    }
    if (isTrue(vnode.isStatic) &&
        isTrue(oldVnode.isStatic) &&
        vnode.key === oldVnode.key &&
        (isTrue(vnode.isCloned) || isTrue(vnode.isOnce))) {
      vnode.elm = oldVnode.elm
      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 elm = vnode.elm = oldVnode.elm
    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)) {
        /*新老节点均有children子节点,则对子节点进行diff操作,调用updateChildren*/
        if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly)
      } else if (isDef(ch)) {
        /*如果老节点没有子节点而新节点存在子节点,先清空elm的文本内容,然后为当前节点加入子节点*/
        if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, '')
        addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)
      } else if (isDef(oldCh)) {
        /*新节点没有子节点而老节点有子节点的时候,则移除所有ele的子节点*/
        removeVnodes(elm, oldCh, 0, oldCh.length - 1)
      } else if (isDef(oldVnode.text)) {
       /*新老节点都无子节点的时候,只是文本的替换,因为这个逻辑中新节点text没有,所以直接去除ele的文本*/
        nodeOps.setTextContent(elm, '')
      }
    } else if (oldVnode.text !== vnode.text) {
      /*当新老节点text不一样时,直接替换这段文本*/
      nodeOps.setTextContent(elm, vnode.text)
    }
    if (isDef(data)) {
      if (isDef(i = data.hook) && isDef(i = i.postpatch)) i(oldVnode, vnode)
    }
  }

通过源码可以看到是先进行有无子节点判断,然后再进行同级比较;就是我们讲的先深度优先,再广度优先 END

上个小测试

<transition name="fade">
    <p v-if="show">我第一</p>
    <p v-else>我第二</p>
</transition>

transition是给切换添加过渡效果的,当变换变量show时,这个切换会不会出现淡入淡出过渡效果呢?为什么?