Vue 虚拟 DOM 和 Diff算法

(一)虚拟DOM

虚拟DOM又称为Virtual DOM,简称vDom,vDOM是实现 Vue 和 React 的基石

diff 算法是 vDom 中最核心、最重要的部分

DOM操作是非常耗费性能的

Vue 和 React 是数据驱动视图,是如何简化DOM操作的?

(二)解决方案

将DOM操作计算转化为JS计算,使用js来模拟DOM 架构

js计算速度比DOM计算速度快太多了

给出一个 html 片段 要会转化为 js 对象

(三)时间复杂度

js 对比 两段 DOM转化后的代码 与 对比树类似,先看看树的对比

降低复杂度的方法:3点

tag 表示 标签 a、div、span等

所以vue循环的时候 传一个 :key 有助于vue dom 的比较 性能优化方面渲染更快捷

(四)diff 算法

diff 对比是一个宽泛的概念,两个对象也可以做diff 对比,两棵树也可以做 diff 如vDom diff,其他语言也有diff 算法相关应用

(五)深入diff 算法源码

(1)snabbdom

要了解snabbdom的话有必要先去github上先了解下snabbdom: https://github.com/snabbdom/snabbdom

diff 算法的一个框架体现

 以上是主要的几段代码 主要是 h 函数 和 patch 函数 还有 patchVNode、updateChildren函数,这四个函数要搞懂 都是干什么的

虚拟DOM的目的是实现最小更新。

(五)函数解析

npm i snabbdom

(1)首先看 h 函数

主要作用:根据传进来的参数,返回 vNode 对象,vNode 也就是虚拟节点

看下 h 函数的传参

export function h (sel: string, data: VNodeData | null, children: VNodeChildren): VNode

第一个参数:传入的dom元素

第二个参数:dom 元素的特性,比如绑定的事件,style 样式等等

第三个参数:他的子元素,子元素大于1个的话,使用数组包起来

<template>
  <div id="app">
    <div class="header">
      <span>我是文字部分</span>
      <div class="intro">这里是介绍</div>
    </div>
  </div>
</template>

<script>
import { h } from 'snabbdom'
export default {
  name: 'App',
  mounted () {
    let vNode = h('div.header', {}, [h('span', {}, '我是文字部分'), h('div.intro', {}, '这里是介绍')])
    console.log(JSON.stringify(vNode, null, 2))
  }
}
</script>

<style>
.header {
  color: red
}
.intro {
  font-size: 13px;
}
</style>

vNode结果:

{
  "sel": "div.header",
  "data": {},
  "children": [
    {
      "sel": "span",
      "data": {},
      "text": "我是文字部分"
    },
    {
      "sel": "div.intro",
      "data": {},
      "text": "这里是介绍"
    }
  ]
}

可以看出 h 函数将 dom 结构转化成 js 对象,来形容dom

说个题外话:前两天看见了组内大哥的骚操作,JSON.stringify 可以传三个参数的,第三个参数是缩进空格,看的方便

(2)patch 函数

patch 函数是 snabbdom 的核心,它的作用是对比两个 vNode(也就是 js 对象),把vNode之间的差异渲染到真实DOM,并返回新的vNode

先来看一下 patch 函数是什么

<template>
</template>

<script>
import { init } from 'snabbdom'
export default {
  name: 'App',
  mounted () {
    // patch 方法是由 init 函数返回的 一个函数中返回另一个函数 也就是高阶函数
    let patch = init([])
    console.log(patch)
  }
}
</script>

<style>
</style>

打印出来:

传入新旧两个vNode,返回值是一个 vNode,在 patch 中又调用了一个新的函数 patchNode,这个函数稍后再讲~

现在让我们看一下 patch 函数的效果

代码:

<template>
  <div id="app">
    <div class="old">
      <span>我是old</span>
    </div>
    <div class="header">
      <span>我是文字部分</span>
      <div class="intro">这里是介绍</div>
    </div>
  </div>
</template>

<script>
import { h, init } from 'snabbdom'
export default {
  name: 'App',
  mounted () {
    let vNode = h('div.header', {}, [h('span', {}, '我是文字部分'), h('div.intro', {}, '这里是介绍')])
    console.log(JSON.stringify(vNode, null, 2))
    console.log('----------------')
    // patch 方法是由 init 函数返回的 一个函数中返回另一个函数 也就是高阶函数
    let patch = init([])
    console.log(patch)
    console.log('------------')
    setTimeout(() => {
      var appNode = document.querySelector('.old')
      var newNode = patch(appNode, vNode)
      console.log(JSON.stringify(newNode, null, 2))
    }, 3000)
  }
}
</script>

<style>
.header {
  color: red
}
.intro {
  font-size: 13px;
}
</style>

初始:

3s后:

可以看出 patch 函数将页面上 旧 node 更新成了 新的 Node, 并返回 新的 vNode(新的js对象)

(3)patchVNode

作用:patchVnode(oldVnode, newVnode) 函数的作用是对比新旧两个节点,更新它们的差异。

如果sameNode 判断是true 的话,就会走到patchNode 函数中

sameNode函数:判断 oldNode 中的key 和 sel 是否 和 newNode 中的 key 和 sel 是否相同,如果相同返回true;说明他们是相同的Vnode. 否则的话,反之。

patchNode 主要是 进行数据更新、位置移动

(4)updateChildren 函数

在 patchVNode 函数中引出了 updateChildren 函数,updateChildren 函数也是重要的一个函数

updateChildren() 是整个 Virtual DOM 的核心,内部使用 diff 算法,对比新旧节点的 children,更新DOM 

function updateChildren(parentElm, oldCh, newCh, insertedVnodeQueue) {
        var oldStartIdx = 0, newStartIdx = 0;
        var oldEndIdx = oldCh.length - 1;
        var oldStartVnode = oldCh[0];
        var oldEndVnode = oldCh[oldEndIdx];
        var newEndIdx = newCh.length - 1;
        var newStartVnode = newCh[0];
        var newEndVnode = newCh[newEndIdx];
        var oldKeyToIdx;
        var idxInOld;
        var elmToMove;
        var before;
        while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
            if (oldStartVnode == null) {
                oldStartVnode = oldCh[++oldStartIdx]; // Vnode might have been moved left
            }
            else if (oldEndVnode == null) {
                oldEndVnode = oldCh[--oldEndIdx];
            }
            else if (newStartVnode == null) {
                newStartVnode = newCh[++newStartIdx];
            }
            else if (newEndVnode == null) {
                newEndVnode = newCh[--newEndIdx];
            }
            else if (sameVnode(oldStartVnode, newStartVnode)) {
                patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue);
                oldStartVnode = oldCh[++oldStartIdx];
                newStartVnode = newCh[++newStartIdx];
            }
            else if (sameVnode(oldEndVnode, newEndVnode)) {
                patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue);
                oldEndVnode = oldCh[--oldEndIdx];
                newEndVnode = newCh[--newEndIdx];
            }
            else if (sameVnode(oldStartVnode, newEndVnode)) {
                patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue);
                api.insertBefore(parentElm, oldStartVnode.elm, api.nextSibling(oldEndVnode.elm));
                oldStartVnode = oldCh[++oldStartIdx];
                newEndVnode = newCh[--newEndIdx];
            }
            else if (sameVnode(oldEndVnode, newStartVnode)) {
                patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue);
                api.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm);
                oldEndVnode = oldCh[--oldEndIdx];
                newStartVnode = newCh[++newStartIdx];
            }
            else {
                if (oldKeyToIdx === undefined) {
                    oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx);
                }
                idxInOld = oldKeyToIdx[newStartVnode.key];
                if (isUndef(idxInOld)) {
                    api.insertBefore(parentElm, createElm(newStartVnode, insertedVnodeQueue), oldStartVnode.elm);
                    newStartVnode = newCh[++newStartIdx];
                }
                else {
                    elmToMove = oldCh[idxInOld];
                    if (elmToMove.sel !== newStartVnode.sel) {
                        api.insertBefore(parentElm, createElm(newStartVnode, insertedVnodeQueue), oldStartVnode.elm);
                    }
                    else {
                        patchVnode(elmToMove, newStartVnode, insertedVnodeQueue);
                        oldCh[idxInOld] = undefined;
                        api.insertBefore(parentElm, elmToMove.elm, oldStartVnode.elm);
                    }
                    newStartVnode = newCh[++newStartIdx];
                }
            }
        }
        if (oldStartIdx <= oldEndIdx || newStartIdx <= newEndIdx) {
            if (oldStartIdx > oldEndIdx) {
                before = newCh[newEndIdx + 1] == null ? null : newCh[newEndIdx + 1].elm;
                addVnodes(parentElm, before, newCh, newStartIdx, newEndIdx, insertedVnodeQueue);
            }
            else {
                removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx);
            }
        }
    }

循环遍历 具体对比过程看下面文章吧~

更多学习查看文章:https://blog.csdn.net/u012961419/article/details/107596868

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值