Web前端最新vue虚拟dom核心方法patch解析_vue patch(1),前端并发原理解析

总结

阿里十分注重你对源码的理解,对你所学,所用东西的理解,对项目的理解。

最新阿里蚂蚁金服四面(已拿offer)Java技术面经总结

最新阿里蚂蚁金服四面(已拿offer)Java技术面经总结

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

最新阿里蚂蚁金服四面(已拿offer)Java技术面经总结

可以说理解了snabbdom 中的具体方法即理解了vue中虚拟dom的核心思想:
当状态更新时,将新的JavaScript对象和旧的JavaScript对象进行比较(即diff算法),运算出两者的差异,将差异应用到真正的DOM,以此减少了DOM的操作。

diff 策略

在这里插入图片描述

  • 只比较同一级,不跨级比较
  • 标签名不同,直接删除,不继续深度比较
  • 标签名相同,key相同就认为是相同节点,不继续比较
patch

在这里插入图片描述

patch方法是虚拟dom核心中的核心。在VNode(虚拟节点)改变后和初始化前都会调用。

主要逻辑如下:
  • 调用 module 中的 pre hook(生命周期相关)
  • 如果传入的是 Element 转成空的 vnode(这里在首次创建时,会传入一个dom元素)
  • 新旧节点为 sameVnode 的话,则调用 patchVnode 更新 vnode , 否则创建新节点
  • 创建新节点 ,插入新节点 ,移除旧节点
  • 调用元素上的 insert hook(生命周期相关)
  • 调用元素上的 post hook(生命周期相关)
源码解析:
function patch(oldVnode: VNode | Element, vnode: VNode): VNode {
  let i: number, elm: Node, parent: Node;
  const insertedVnodeQueue: VNodeQueue = [];
  // 调用 module 中的 pre hook
  for (i = 0; i < cbs.pre.length; ++i) cbs.pre[i]();

  // 如果传入的是 Element 转成空的 vnode
  if (!isVnode(oldVnode)) {
  //创建一个空的vnode 并关联DOM元素 emptyNodeAt
    oldVnode = emptyNodeAt(oldVnode);
  }

  // sameVnode 时 (sel 和 key相同) 调用 patchVnode
  // 判断两个参数是否是相同的vnode
  // 比较标签名 key
  if (sameVnode(oldVnode, vnode)) {
    patchVnode(oldVnode, vnode, insertedVnodeQueue);
  } else {
    elm = oldVnode.elm as Node;
    parent = api.parentNode(elm);

    // 创建新的 dom 节点 vnode.elm
    createElm(vnode, insertedVnodeQueue);

    if (parent !== null) {
      // 插入 dom
      api.insertBefore(parent, vnode.elm as Node, api.nextSibling(elm));
      // 移除旧 dom
      removeVnodes(parent, [oldVnode], 0, 0);
    }
  }

  // 调用元素上的 insert hook,注意 insert hook 在 module 上不支持
  for (i = 0; i < insertedVnodeQueue.length; ++i) {
    (((insertedVnodeQueue[i].data as VNodeData).hook as Hooks).insert as any)(insertedVnodeQueue[i]);
  }

  // 调用 module post hook
  for (i = 0; i < cbs.post.length; ++i) cbs.post[i]();
  return vnode;
}

function emptyNodeAt(elm: Element) {
  const id = elm.id ? '#' + elm.id : '';
  const c = elm.className ? '.' + elm.className.split(' ').join('.') : '';
  return vnode(api.tagName(elm).toLowerCase() + id + c, {}, [], undefined, elm);
}

// key 和 selector 相同
function sameVnode(vnode1: VNode, vnode2: VNode): boolean {
  return vnode1.key === vnode2.key && vnode1.sel === vnode2.sel;
}

patchVnode
主要逻辑如下:
  • 调用 module 中的 prepatch hook(生命周期相关)
  • 按照新旧Vnode 的text 和children 的差异做出差异化处理
  • 值得注意的是:新旧节点均存在 children,且不一样时,对 children 进行 diff 调用 updateChildren
function patchVnode(oldVnode: VNode, vnode: VNode, insertedVnodeQueue: VNodeQueue) {
  let i: any, hook: any;
  // 调用 prepatch hook 生命周期钩子
  if (isDef((i = vnode.data)) && isDef((hook = i.hook)) && isDef((i = hook.prepatch))) {
    i(oldVnode, vnode);
  }
  //设置新的vnode的dom 关联
  const elm = (vnode.elm = oldVnode.elm as Node);
  let oldCh = oldVnode.children;
  let ch = vnode.children;
  if (oldVnode === vnode) return;
  if (vnode.data !== undefined) {
    // 调用 module 上的 update hook 生命周期钩子
    for (i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode);
    i = vnode.data.hook;
    // 调用 vnode 上的 update hook 生命周期钩子
    if (isDef(i) && isDef((i = i.update))) i(oldVnode, vnode);
  }
  if (isUndef(vnode.text)) {
    if (isDef(oldCh) && isDef(ch)) {
      // 新旧节点均存在 children,且不一样时,对 children 进行 diff
      if (oldCh !== ch) updateChildren(elm, oldCh as Array<VNode>, ch as Array<VNode>, insertedVnodeQueue);
    } else if (isDef(ch)) {
      // 旧节点不存在 children 新节点有 children
      // 旧节点存在 text 置空
      if (isDef(oldVnode.text)) api.setTextContent(elm, '');
      // 加入新的 vnode
      addVnodes(elm, null, ch as Array<VNode>, 0, (ch as Array<VNode>).length - 1, insertedVnodeQueue);
    } else if (isDef(oldCh)) {
      // 新节点不存在 children 旧节点存在 children 移除旧节点的 children
      removeVnodes(elm, oldCh as Array<VNode>, 0, (oldCh as Array<VNode>).length - 1);
    } else if (isDef(oldVnode.text)) {
      // 旧节点存在 text 置空
      api.setTextContent(elm, '');
    }
  } else if (oldVnode.text !== vnode.text) {
    // 更新 text
    api.setTextContent(elm, vnode.text as string);
  }
  // 调用 postpatch hook 生命周期钩子
  if (isDef(hook) && isDef((i = hook.postpatch))) {
    i(oldVnode, vnode);
  }
}

核心方法updateChildren

在这里插入图片描述
新的Vnode 和 老的Vnode 都有children时,进入方法。

  • 对新旧node分别赋值 newStartIdx oldStartIdx newEndIdx oldEndIdx 进行指针循环遍历
  • 对不符合same 的节点进行劲增 其他的进行patchVnode 的替换
  • 循环结束处理剩余节点 进行批量删除或批量新增
  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, elmToMove, refElm
 
    // removeOnly is a special flag used only by <transition-group>
    // to ensure removed elements stay in correct relative positions


### 最后

**[开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】](https://bbs.csdn.net/topics/618166371)**

>![](https://img-blog.csdnimg.cn/img_convert/797c30400abf31ce6ce57729f9cbcdab.webp?x-oss-process=image/format,png)

 ensure removed elements stay in correct relative positions


### 最后

**[开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】](https://bbs.csdn.net/topics/618166371)**

>[外链图片转存中...(img-zYGSRNqc-1715202150135)]

  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值