[Vue源码分析] Virtual DOM
最近小组有个关于vue virtual dom的分享会,提前准备一下…
读前须知:
本文章涉及源码版本为Vue 2.5.2,文中涉及到源码部分,解释直接写在源码中(中文部分为本人添加),截图尽量放完整代码,但由于截图的大小限制,部分只能放关键截图,建议结合源码阅读此文章。
附上尤雨溪大佬的github vue仓库地址:https://github.com/vuejs/vue
为什么使用virtual dom
做一件事一般都先问问为什么,那么为什么使用virtual dom?真正的 DOM 元素是非常庞大的,因为浏览器的标准把 DOM 设计的很复杂。如果频繁地操作 DOM ,会产生一定的性能问题。
举个例子:创建一个header标签,并打印dom的描述信息:
可以看到,输出的信息虽然挺多看不懂,但可以看得出来这是很庞大的一段内容。
相比之下:Virtual DOM 用一个原生的 JS 对象去描述一个 DOM 节点,所以它比创建一个 DOM 的代价要小很多。
virtual dom 的数据结构
virtual dom的定义在src/core/vdom/vnode.js
中,是一个VNode 类,定义如图:
virtual dom映射到真实的 DOM 实际上要经历 VNode 的 create、diff、patch 等过程。
virtual dom 的创建
VNode 的 create 是通过createElement
方法创建的,源码位于src/core/vdom/create-elemenet.js中
:
这个方式实际调用的是_createElement()
方法,此方法同样位于src/core/vdom/create-elemenet.js
,为了方便阅读,关键代码解释已经写在源码中,如图
VNode children的VNode化
VNode化的意思是什么?Virtual DOM 实际上是一个树状结构,每一个 VNode 可能会有若干个子节点,这些子节点应该也是 VNode 的类型,由于上边传入的children是any类型的,因此需要将children转为VNode类型。
这个过程用到两个方法:normalizeChildren(children)
、simpleNormalizeChildren(children)
这两个方法的定义在src/core/vdom/helpers/normalzie-children.js
中:
当一个 childrean 包含组件的时候,由于 functional component 函数式组件返回的是一个数组而不是一个根节点,所以需要通过Array.prototype.concat
方法把整个 children 转化为深度只有一层。
如果children是数组类型,normalizeChildren
方法实际上调用的是normalizeArrayChildren
方法,该方法在同文件下,源码中已经添加关键代码解释,如下:
至此,我们知道了Virtual DOM是通过createElement
方法创建的,但是createElement
方法是什么时候调用的呢?接下来我们先分析一下new Vue()
发生了哪些事。
new Vue()发生了那些事
我们都知道,vue项目中的入口是src下的main.js
,如图:
new Vue()发生了什么?接下来我们看一下Vue源码,源码位于src/core/instance/index.js
中
可以看到,Vue实际上是一个类,源码中判断了this instanceof Vue
,用于限制此类只能通过new关键字初始化,这个类看起来很简单,只是调用了一个this._init()
方法,这个方法做了什么?this_init()
方法在src/core/instance/init.js
中定义,主要是做了一堆初始化操作,关键源码已添加中文注释:
可以看到this_init()
方法在做了一堆初始化操作后调用的是vm.$mount()
方法,这个方法时怎么挂载实例的?接下来看一下这个方法的源码,此方法的定义位于src/platform/web/entry-runtime-with-compiler.js
中,关键代码已经添加中文注释:
逗了一圈,发现这个方法只是对options做了一些规范化的操作,最后调用的还是最初缓存下来的Vue原型上的$mount方法,那么这个方法是什么时候定义的?查找一下,发现此方法位于:src/platform/web/runtime/index.js
:
以下为$mount
中使用到的query方法的源码,源码位于src/platform/web/util/index.js
中:
最后$mount
方法调用的是mountComponent
方法,此方法在src/core/instance/lifecycle.js
中定义,关键代码注释已经添加到源码中:
从上边源码可以看出,mountComponent
最核心的方法有三个:vm._render()
、vm._update()
、new Watcher
,Watcher
暂时不作介绍。
接下来分析一下vm._render()
,此方法在src/core/instance/render.js
中定义,如下:
可以看到,这个方法返回的是虚拟dom,而虚拟dom的创建是调用vm.$createElement
,vm.$createElement
是什么?
可以看到,它正是文章开头介绍到的createElement
方法,createElement
方法就是这时候调用的,兜了一大圈,终于解决了之前提到的createElement
方法什么时候调用的问题。
vm._update()
又是什么?
上边介绍mountComponent
时提到过,vm.update()
的作用是把创建好的虚拟dom渲染成真实的dom,vm._update()源码位于:src/core/instance/lifecycle.js
,如图:
从源码中可以看到,_update
方法在首次渲染和更新时都调用了vm.__patch__
方法,只是传入参数不一样,可以想象得到vm.__patch__
会有两套逻辑,在这里先分析首次渲染时vm.__patch__
做了什么。
vm.__patch__
源码位于src/platforms/web/runtime/index.js
,如下:
patch
应该就是要将虚拟dom转换为真实dom的函数,但是noop是什么?从上边的导入路径可以得知此方法地址位于src/shared/util.js
,如下:
可见,noop
是一个空函数,也就是当前如果不是浏览器环境的话,vm.__patch__
将会是一个空函数。可以得知,在非浏览器渲染(服务端渲染)中,不需要把虚拟dom转换为真实dom。
接下来,重点看一下patch
方法,源码位于src/platforms/web/runtime/patch.js
中
这个函数的定义很简单,就一句代码,接受的是一个Function类型的返回值,该值由createPatchFunction
返回。
createPatchFunction
源码位于src/core/vdom/patch.js
中,这个方法很复杂,但总的来说就是定义了一系列的辅助方法,而最终返回一个 patch 方法>
深夜了,先睡,待更新~~~~~~
- 上一篇:非标准析(NSA)之发端
- 下一篇:设计模式 | 备忘录模式及典型应用