关于vue.js:手写-Vue2-系列-之-patch-diff

9次阅读

共计 3606 个字符,预计需要花费 10 分钟才能阅读完成。

当学习成为了习惯,常识也就变成了常识。 感激各位的 关注 点赞 珍藏 评论

新视频和文章会第一工夫在微信公众号发送,欢送关注:李永宁 lyn

文章已收录到 github 仓库 liyongning/blog,欢送 Watch 和 Star。

前言

上一篇文章 手写 Vue2 系列 之 初始渲染 中实现了原始标签、自定义组件、插槽的的初始渲染,当然其中也波及到 v-bind、v-model、v-on 指令的原理。实现首次渲染之后,接下来就该进行后续的更新了:

响应式数据产生更新 -> setter 拦挡到更新操作 -> dep 告诉 watcher 执行 update 办法 -> 进而执行 updateComponent 办法更新组件 -> 执行 render 生成新的 vnode -> 将 vnode 传递给 vm._update 办法 -> 调用 patch 办法 -> 执行 patchVnode 进行 DOM diff 操作 -> 实现更新

指标

所以,本篇的指标就是实现 DOM diff,实现后续更新。波及知识点只有一个:DOM diff。

实现

接下来就开始实现 DOM diff,实现响应式数据的后续更新。

patch

/src/compiler/patch.js

/**
 * 负责组件的首次渲染和后续更新
 * @param {VNode} oldVnode 老的 VNode
 * @param {VNode} vnode 新的 VNode
 */
export default function patch(oldVnode, vnode) {if (oldVnode && !vnode) {
    // 老节点存在,新节点不存在,则销毁组件
    return
  }

  if (!oldVnode) {// oldVnode 不存在,阐明是子组件首次渲染} else {if (oldVnode.nodeType) {// 实在节点,则示意首次渲染根组件} else {
      // 后续的更新
      patchVnode(oldVnode, vnode)
    }
  }
}

patchVnode

/src/compiler/patch.js

/**
 * 比照新老节点,找出其中的不同,而后更新老节点
 * @param {*} oldVnode 老节点的 vnode
 * @param {*} vnode 新节点的 vnode
 */
function patchVnode(oldVnode, vnode) {
  // 如果新老节点雷同,则间接完结
  if (oldVnode === vnode) return

  // 将老 vnode 上的实在节点同步到新的 vnode 上,否则,后续更新的时候会呈现 vnode.elm 为空的景象
  vnode.elm = oldVnode.elm

  // 走到这里阐明新老节点不一样,则获取它们的孩子节点,比拟孩子节点
  const ch = vnode.children
  const oldCh = oldVnode.children

  if (!vnode.text) { // 新节点不存在文本节点
    if (ch && oldCh) { // 阐明新老节点都有孩子
      // diff
      updateChildren(ch, oldCh)
    } else if (ch) { // 老节点没孩子,新节点有孩子
      // 减少孩子节点
    } else { // 新节点没孩子,老节点有孩子
      // 删除这些孩子节点
    }
  } else { // 新节点存在文本节点
    if (vnode.text.expression) { // 阐明存在表达式
      // 获取表达式的新值
      const value = JSON.stringify(vnode.context[vnode.text.expression])
      // 旧值
      try {
        const oldValue = oldVnode.elm.textContent
        if (value !== oldValue) { // 新老值不一样,则更新
          oldVnode.elm.textContent = value
        }
      } catch {
        // 避免更新时遇到插槽,导致报错
        // 目前不解决插槽数据的响应式更新
      }
    }
  }
}

updateChildren

/src/compiler/patch.js

/**
 * diff,比对孩子节点,找出不同点,而后将不同点更新到老节点上
 * @param {*} ch 新 vnode 的所有孩子节点
 * @param {*} oldCh 老 vnode 的所有孩子节点
 */
function updateChildren(ch, oldCh) {
  // 四个游标
  // 新孩子节点的开始索引,叫 新开始
  let newStartIdx = 0
  // 新完结
  let newEndIdx = ch.length - 1
  // 老开始
  let oldStartIdx = 0
  // 老完结
  let oldEndIdx = oldCh.length - 1
  // 循环遍历新老节点,找出节点中不一样的中央,而后更新
  while (newStartIdx <= newEndIdx && oldStartIdx <= oldEndIdx) { // 根为 web 中的 DOM 操作特点,做了四种假如,升高工夫复杂度
    // 新开始节点
    const newStartNode = ch[newStartIdx]
    // 新完结节点
    const newEndNode = ch[newEndIdx]
    // 老开始节点
    const oldStartNode = oldCh[oldStartIdx]
    // 老完结节点
    const oldEndNode = oldCh[oldEndIdx]
    if (sameVNode(newStartNode, oldStartNode)) { // 假如新开始和老开始是同一个节点
      // 比照这两个节点,找出不同而后更新
      patchVnode(oldStartNode, newStartNode)
      // 挪动游标
      oldStartIdx++
      newStartIdx++
    } else if (sameVNode(newStartNode, oldEndNode)) { // 假如新开始和老完结是同一个节点
      patchVnode(oldEndNode, newStartNode)
      // 将老完结挪动到新开始的地位
      oldEndNode.elm.parentNode.insertBefore(oldEndNode.elm, oldCh[newStartIdx].elm)
      // 挪动游标
      newStartIdx++
      oldEndIdx--
    } else if (sameVNode(newEndNode, oldStartNode)) { // 假如新完结和老开始是同一个节点
      patchVnode(oldStartNode, newEndNode)
      // 将老开始挪动到新完结的地位
      oldStartNode.elm.parentNode.insertBefore(oldStartNode.elm, oldCh[newEndIdx].elm.nextSibling)
      // 挪动游标
      newEndIdx--
      oldStartIdx++
    } else if (sameVNode(newEndNode, oldEndNode)) { // 假如新完结和老完结是同一个节点
      patchVnode(oldEndNode, newEndNode)
      // 挪动游标
      newEndIdx--
      oldEndIdx--
    } else {// 下面几种假如都没命中,则老诚实的遍历,找到那个雷同元素}
  }
  // 跳出循环,阐明有一个节点首先遍历完结了
  if (newStartIdx < newEndIdx) {// 阐明老节点先遍历完结,则将残余的新节点增加到 DOM 中}
  if (oldStartIdx < oldEndIdx) {// 阐明新节点先遍历完结,则将残余的这些老节点从 DOM 中删掉}
}

sameVNode

/src/compiler/patch.js

/**
 * 判断两个节点是否雷同
 * 这里的判读比较简单,只做了 key 和 标签的比拟
 */
function sameVNode(n1, n2) {return n1.key == n2.key && n1.tag === n2.tag}

后果

好了,到这里,虚构 DOM 的 diff 过程就实现了,如果你能看到如下效果图,则阐明一切正常。

动图地址:https://gitee.com/liyongning/…

能够看到,页面曾经齐全做到响应式数据的初始渲染和后续更新。其中对于 Computed 计算属性的内容依然没有正确的显示进去,这很失常,因为还没实现这个性能,所以接下来就会去实现 conputed 计算属性,也就是下一篇内容 手写 Vue2 系列 之 computed

链接

  • 配套视频,微信公众号回复:” 精通 Vue 技术栈源码原理视频版 ” 获取
  • 精通 Vue 技术栈源码原理 专栏
  • github 仓库 liyongning/Vue 欢送 Star
  • github 仓库 liyongning/Lyn-Vue-DOM 欢送 Star
  • github 仓库 liyongning/Lyn-Vue-Template 欢送 Star

感激各位的:关注 点赞 珍藏 评论,咱们下期见。


当学习成为了习惯,常识也就变成了常识。 感激各位的 关注 点赞 珍藏 评论

新视频和文章会第一工夫在微信公众号发送,欢送关注:李永宁 lyn

文章已收录到 github 仓库 liyongning/blog,欢送 Watch 和 Star。

正文完
 0