vue核心之vdom由浅入深的理解

 

一:为什么要用vdom(虚拟dom)?

    传统的jq是直接操作dom,但由于计算量较小,问题不大。现在随着前端的业务逻辑越来越复杂,vue的兴起,由原来的直接操作dom转变为数据驱动视图,所以dom的计算量是非常大的,而我们知道,操作dom是非常耗费性能的,比如我们创建一个简单的div,如图

   可以看到,真正的 DOM 元素是非常庞大的,因为浏览器的标准就把 DOM 设计的非常复杂。当我们频繁的去做 DOM 更新,会产生一定的性能问题

   因为浏览器js的执行速度是远远大于操作dom的,所以我们想到用js模拟dom,计算出最小的变更,来操作dom,那js怎么模拟dom呢?比如:

    <div id="main" class="container">
      <p>hello</p>
      <p style="color:#000;">
        <span>hi</span>
      </p>
    </div>

可以写成: 

{
                    tag: 'div',
                    props: {
                        className: 'container',
                        id: 'main'
                    },
                    children: [
                        {
                            tag: 'p',
                            text: 'hello'
                        },
                        {
                            tag: 'p',
                            props: { style: 'color:#000' },
                            children: [
                                {
                                    tag: 'span',
                                    text: 'hi'
                                }
                            ]
                        }
                    ]
                }

而要得出两次vdom的不同,需要比较新旧两次vdom,那怎么才能高效的比较呢?

而这个两棵vdom树比较的diff算法就是vdom的核心了

而传统的比较,需要分别遍历两个vdom,并排序,时间复杂度为O(n^3),这显然不行

经过大佬的研究,得出了一种时间复杂度为O(n)的算法

1.只比较同一层级,不跨级比较

     2.tag不相同,则直接删掉重建,不再继续深度比较

     3.tag和key,两者都相同,则认为是相同节点,不再深度比较

接下来我们看下源码,看下他是怎么实现的

1.首先是vnode的定义,打开vue/src/core/vdom/vnode

乍一看很多,很乱,其实别慌,我们看主要的就行了

咦,是不是很熟悉!  这不就是一开头我们自己用js模拟的dom的属性吗?还有一个key吗

另外的elm是当前虚拟节点对应的真实dom节点,context是当前节点的编译作用域,其他更多的是关于节点的一些判断,这些细节咱们暂且先不管了,后面用到了再细说,先走主流程

2.定义完VNode,该创建了,打开vue/src/core/vdom/create-element.js

里面返回了一个_createElement的方法

可以看到,_createElement 方法有 5 个参数,context 表示 VNode 的上下文环境,它是 Component 类型;tag 表示标签,它可以是一个字符串,也可以是一个 Componentdata 表示 VNode 的数据,它是一个 VNodeData 类型,可以在 flow/vnode.js 中找到它的定义,这里先不展开说;children 表示当前 VNode 的子节点,它是任意类型的,它接下来需要被规范为标准的 VNode 数组;normalizationType 表示子节点规范的类型,类型不同规范的方法也就不一样,它主要是参考 render 函数是编译生成的还是用户手写的。

_createElement函数先是判断tag和key,再将chidren规范化,通过simpleNormalizeChildren或者normalizeChildren方法将其变成一维数组,再通过tag的不同返回不同的vnode,

总结一下:说到这里,我们大概了解什么是vnode了,每个vnode都有children,children每个元素也是vnode,就形成了vnode树

好了,到这里,虚拟dom就创建好了,那怎么把虚拟dom渲染成真实dom呢?接下来我们就介绍update方法,该方法在首次渲染和数据更新时都会调用

3.渲染虚拟dom   /core/instance/lifecycle

通过看源码我们可以很容易看出,_update方法其实真正执行的是 vm.__patch__方法,而这个方法最终定义在src/core/vdom/patch.js的createPatchFunction中,在这方法中用到了函数柯里化的技巧,把因为不同平台产生的差异化参数提前固化,这里先不管这个,以后专门再写一篇讲讲这个。好了,继续看patch函数。

很容易看出首先是判断,看下isUndef和isDef是啥意思

那么就很容易看出patch是先判断oldVnode和vnode是否存在,若vnode不存在 oldVnode存在,所以就调用invokeDestroyHook进行销毁节点,结束函数执行

  • 若oldVnode不存在,则需要调用createElm创建节点

在createElm方法中,经过一系列判断,我们找到第一个关键函数createChildren,来看看这个函数干了些什么

其实就是递归调用createElm,然后生成子节点的真实 DOM

最后通过insert插入到父节点,因为在createChildren中是递归调用的,从最底部开始生成子dom的,所以vdom的树插入顺序是先子后父的

好了 这就是最简单dom树产生的过程。

  • 若vnode和oldVnode存在,则需要判断是否为同一节点,若是,则用patchVnode修改现有的节点

 

 

 

好了,现在我们先来看看sameVnode是怎么判断是否为同一节点

可以看出 当两个VNode的tag、key、isComment都相同,并且同时定义或未定义data的时候,且如果标签为input则type必须相同。这时候这两个VNode则算sameVnode,可以直接进行patchVnode操作

那么patchVnode又做了什么操作呢

1.如果新旧VNode都是静态的,同时它们的key相同(代表同一节点),并且新的VNode是clone或者是标记了once(标记v-once属性,只渲染一次),那么只需要替换elm以及componentInstance即可。

2.新老节点均有children子节点,则对子节点进行diff操作,调用updateChildren,这个updateChildren也是diff的核心。

3.如果老节点没有子节点而新节点存在子节点,先清空老节点DOM的文本内容,然后为当前DOM节点加入子节点。

4.当新节点没有子节点而老节点有子节点的时候,则移除该DOM节点的所有子节点。

5.当新老节点都无子节点的时候,只是文本的替换。

updateChildren又是怎么比对的呢

oldCh 和 newCh 各有两个头尾的变量 StartIdx 和 EndIdx ,它们的2个变量相互比较,一共有4种比较方式。如果 4 种比较都没匹配,如果设置了key,就会用key进行比较,如果存在key,并且满足sameVnode,会将该DOM节点进行复用,否则则会创建一个新的DOM节点。在比较的过程中,变量会往中间靠,一旦 StartIdx > EndIdx 表明 oldCh 和 newCh 至少有一个已经遍历完了,就会结束比较。

如果oldStartIdx和newStartIdx匹配上,真实dom的第一个节点会移到最前

如果oldStartIdx和newEndIdx匹配上,真实dom的第一个节点会移到最后,匹配上的两个指针继续向中间移动

当新老VNode节点的start或者end满足sameVnode时,直接将该VNode节点进行patchVnode即可。

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值