Vue进阶 Diff算法详解

写作不易,未经作者允许禁止以任何形式转载!
如果觉得文章不错,欢迎关注、点赞和分享!原文链接:
Vue进阶 Diff算法详解

一、虚拟DOM

什么是虚拟DOM?

虚拟DOM就是把真实DOM树的结构和信息抽象出来,以对象的形式模拟树形结构,如下:

真实DOM:

<div>
    <p>Hello World</p>
</div>

对应的虚拟DOM就是:

let vnode = {
   
    tag: 'div',
    children:[ {
   tag:'p', text:'Hello World'}]
}

为什么需要虚拟DOM?

渲染真实DOM会有一定的开销,如果每次修改数据都进行真实DOM渲染,都会引起DOM树的重绘和重排,性能开销很大。那么有没有可能只修改一小部分数据而不渲染整个DOM呢?虚拟DOM和Diff算法可以实现。

怎么实现?

  1. 先根据真实DOM生成一颗虚拟DOM树
  2. 当某个DOM节点数据发生改变时,生成一个新的Vnode
  3. 新的Vnode和旧的oldVnode进行对比
  4. 通过patch函数一边比对一边给真实DOM打补丁或者创建Vnode、移除oldVnode等

有什么不一样?

  1. 真实DOM操作为一个属性一个属性去修改,开销较大。
  2. 虚拟DOM直接修改整个DOM节点再替换真实DOM

还有什么好处?

Vue的虚拟DOM数据更新机制是异步更新队列,并不是数据变更马上更新DOM,而是被推进一个数据更新异步队列统一更新。想要马上拿到DOM更新后DOM信息?有个API叫 Vue.nextTick

二、 Diff算法

传统Diff算法

遍历两棵树中的每一个节点,每两个节点之间都要做一次比较。

比如 a->e 、a->d 、a->b、a->c、a->a

  • 遍历完成的时间复杂度达到了O(n^2)
  • 对比完差异后还要计算最小转换方式,实现后复杂度来到了O(n^3)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-W9Ydnr4l-1617040179713)(https://bloginfo.lebronchao.com/doc-image/(null)]-20210326001324690.png)

Vue优化的Diff算法

Vue的diff算法只会比较同层级的元素,不进行跨层级比较

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JxgOiniZ-1617040179718)(https://bloginfo.lebronchao.com/doc-image/(null)]-20210324234529318-20210326001327579.(null))

三、 Vue中的Diff算法实现

Vnode分类

  • EmptyVNode: 没有内容的注释节点
  • TextVNode: 文本节点
  • ElementVNode: 普通元素节点
  • ComponentVNode: 组件节点
  • CloneVNode: 克隆节点,可以是以上任意类型的节点,唯一的区别在于isCloned属性为true

Patch函数

patch函数接收以下参数:

  1. oldVnode:旧的虚拟节点
  2. Vnode:新的虚拟节点
  3. hydrating:是否要和真实DOM混合
  4. removeOnly:特殊的flag,用于 transition-group

处理流程大致分为以下步骤:

  1. vnode不存在,oldVnode存在时,移除oldVnode
  2. vnode存在,oldVnode不存在时,创建vnode
  3. vnode和oldVnode都存在时
    1. 如果vnode和oldVnode是同一个节点(通过sameVnode函数对比 后续详解),通过patchVnode进行后续比对工作
    2. 如果vnode和oldVnode不是同一个节点,那么根据vnode创建新的元素并挂载至oldVnode父元素下。如果组件根节点被替换,遍历更新父节点element。然后移除旧节点。如果oldVnode是服务端渲染元素节点,需要用hydrate函数将虚拟dom和真是dom进行映射

源码如下,已写好注释便于阅读

return function patch(oldVnode, vnode, hydrating, removeOnly) {
   
    // 如果vnode不存在,但是oldVnode存在,移除oldVnode
    if (isUndef(vnode)) {
   
      if (isDef(oldVnode)) invokeDestroyHook(oldVnode)
      return
    }

    let isInitialPatch = false
    const insertedVnodeQueue = []

    // 如果oldVnode不存在,但是vnode存在时,创建vnode
    if (isUndef(oldVnode)) {
   
      isInitialPatch = true
      createElm(vnode, insertedVnodeQueue)
    } else {
   
      // 剩余情况为vnode和oldVnode都存在

      // 判断是否为真实DOM元素
      const isRealElement = isDef(oldVnode.nodeType)
      if (!isRealElement && sameVnode(oldVnode, vnode)) {
   
        // 如果vnode和oldVnode是同一个(通过sameVnode函数进行比对  后续详解)
        // 受用patchVnode函数进行后续比对工作 (函数后续详解)
        patchVnode(oldVnode, vnode, insertedVnodeQueue, removeOnly)
      } else {
   
        // vnode和oldVnode不是同一个的情况
        if (isRealElement) {
   
          // 如果存在真实的节点,存在data-server-render属性
          if (oldVnode.nodeType === 1 && oldVnode.hasAttribute(SSR_ATTR)) {
   
            // 当旧的Vnode是服务端渲染元素,hydrating记为true
            oldVnode.removeAttribute(SSR_ATTR)
            hydrating = true
          }
          // 需要用hydrate函数将虚拟DOM和真实DOM进行映射
          if (isTrue(hydrating)) {
   
            // 需要合并到真实DOM上
            if (hydrate(oldVnode, vnode, insertedVnodeQueue)) {
   
              // 调用insert钩子
              invokeInsertHook(vnode, insertedVnodeQueue, true)
              return oldVnode
            } else if (process.env.NODE_ENV !== 'production') {
   
              warn(
                'The client-side rendered virtual DOM tree is not matching ' +
                'server-rendered content. This is likely caused by incorrect ' +
                'HTML markup, for example nesting block-level elements inside ' +
                '<p>, or missing <
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值