Vue2关于虚拟dom详解

54 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第23天,点击查看活动详情

众所周知,操作一次dom需要重绘整个视图,需要经历创建 DOM 树 —> 创建 Style Rules -> 构建 Render 树 —> 布局 Layout -—> 绘制 Painting这一过程,是相当耗费性能。而Vue2 内部使用的虚拟DOM就是改造的 Snabbdom,以此来作为一种提升性能的解决方案。

虚拟dom基础

因此Vue.js将DOM抽象成一个以js对象为节点的虚拟Dom,简单而言就是由普通的JS对象来描述DOM对象。

那么使用虚拟dom的好处和作用是什么呢?

1.实际上通过虚拟dom改变真正的dom并不比直接操作dom效率更高。而是页面的更新可以先全部反映在这个 JS对象 上,通过对比新旧vnode,找到真正需要更新的地方,再去进行真实的dom操作,相比整个替换算有dom,从而提升性能。

2.虚拟DOM是对真实DOM的一层抽象,不依赖某个平台,它可以是浏览器平台,也可以是weex,甚至是node平台也可以对这样一棵抽象DOM树进行创建删除修改等操作,这也为前后端同构提供了可能。

由以上介绍可知,虚拟DOM就是通过JS来生成一个AST节点树,接下来可以看一下一个虚拟dom是什么样子的?

真实的模板:

<div class="test">
    <span class="name">hello,vivi</span>
</div>

用来描述上述真实dom的虚拟dom

{
    tag: 'div'
    data: {
        class: 'test'
    },
    children: [
        {
            tag: 'span',
            data: {
                class: 'name'
            }
            text: 'hello,vivi'
        }
    ]
}

源码分析

vue2源码地址

VNode基类

/src/core/vdom/vnode.ts

VNode的属性虽然很多,但是我们主要了解 tagdata, 属性, children 属性, text 属性,elm 属性,key 属性即可。

image.png

创建一个空的VNode

/src/core/vdom/vnode.ts

image.png

创建一个文本节点

image.png

创建一个组件节点

/src/core/vdom/create-component.ts

image.png

克隆一个VNode节点

/src/core/vdom/vnode.ts

image.png

创建一个虚拟节点

createElement()方法主要是用来创建一个虚拟节点。

通过源码可以看到当data上已经绑定__ob__的时候,代表该对象已经被响应式处理过了,所以创建一个空节点。

当tag不存在的时候同样创建一个空节点。

当tag不是一个String类型的时候代表tag是一个组件的构造类,则直接用new VNode创建。

当tag是String类型的时候,如果是保留标签,则用new VNode创建一个VNode实例,如果在vm的option的components找得到该tag,代表这是一个组件,否则统一用new VNode创建。

image.png

image.png

image.png

image.png

创建 VNode 过程

首先初始化vue,即new Vue()过程,调用this._init(options)方法-->在该初始化方法中通过$mount 实例方法去挂载 dom 的-->在$mount 方法会去调用 mountComponent 方法,在这个方法中先实例化一个渲染Watcher,在它的回调函数中会调用 updateComponent 方法,在此方法中调用 vm._render 方法先生成虚拟 Node,最终调用 vm._update 更新 DOM

src/core/instance/lifecycle.js

image.png

src/core/instance/render.js

可以看到,vue利用 _createElement 方法创建 VNode。可以看回上文的_createElement方法的详解。

image.png