资源列表:
慕课网:Vue.js 源码全方位深入解析
Vue.js 技术揭秘
知乎:染陌同学 VirtualDOM与diff(Vue实现)
Vue源码解析-数据驱动
Introduction
现代前端框架抛弃里如使用 JQuery 等前端库直接修改 DOM,而是由数据驱动视图。
本章节主要研究 Vue 中模板和数据如果渲染成最红的 DOM。
1 | <div id="app"> |
new Vue 发生了什么
new 关键字在 JavaScript 中实例化一个对象,而 Vue 本质是一个 Function 模拟的类,在其上扩展静态方法和原型方法。
源码,在
src/core/instance/index.js
1 | function Vue (options) { |
最后 new Vue() 调用的是 this._init 方法。
源码:
src/core/instance/init.js。
Vue 初始化主要做了几件事
- 合并配置
- 初始化生命周期
- 初始化事件中心
- 初始化渲染
- 初始化
data、props、computed、watcher等
最后判断是否有 el 属性,如果有则调用 vm.$mount 方法挂载 vm,挂载的目标就是把模板渲染成最终的 DOM
1 | // 68 |
el、template
el

template

1 | <body> |
Vue 实例挂载的实现
$mount 方法的实现是和平台、构建方式相关,不同平台如 weex 有不同的 $mount 实现,底层使用的 API 也不尽相同,这里研究 compiler 的实现。
源码: src/platform/web/entry-runtime-with-compiler.js
- 对
el做了限制,不能挂载到body、html根节点上 如果没有render方法,则会把el或者template字符串转成render方法,在Vue 2.0版本中,所有Vue的组件的渲染最终都需要render方法
$mount 方法实际上会去调用 mountComponent 方法,
源码:src/core/instance/lifecycle.js
- 先调用
vm._render方法生成虚拟Node - 再实例化一个渲染
Watcher,在它的回调函数中调用了updateComponent - 最后调用
vm._update更新DOM - 最后的判断,
vm._isMounted为true, 表示已经挂载,同时执行mounted钩子函数。
⚠️ 这里注意
vm.$vnode表示Vue实例的父虚拟Node,所以它为Null则表示当前是根Vue的实例。
render
它用来把实例渲染成一个虚拟 Node,
源码: src/core/instance/render.js
Vue 文档中的 render 方法,第一个参数 createElement,用来创建 Vnode,模板中的写法:
1 | <div id="app"> |
等同于 render 函数的写法
1 | render: function (createElement) { |
📌 Vue 2.0 相比 Vue 1.0 最大的升级就是利用了 Virtual DOM
Virtual DOM
为什么要使用虚拟 DOM ?
在文档中,一个真实的 DOM 元素是很庞大的,直接修改该元素成本很高,使用 虚拟 DOM,只需要维护一个真实 DOM 映射的对象,这要比维护真实 DOM 成本小得多。
Virtual DOM 是用 VNode 这么一个 Class 去描述
定义在 src/core/vdom/vnode.js
VNode 是对真实 DOM 的一种抽象描述,核心无非就是几个关键属性,标签名、数据、子节点、键值等。
映射到真实的 DOM 实际上要经历 VNode 的 create、diff、patch 等过程。
createElement
Vue.js 利用 createElement 方法创建 VNode,
它定义在 src/core/vdom/create-elemenet.js 中:
每个 VNode 都有 children,每个 children 也是一个 VNode,这样就形成了 VNode tree。
最后通过 vm._update, 将 VNode 渲染成真实的 DOM 并渲染出来。
update
该方法被调用的时机有两次,一次是首次渲染,一次是在数据更新的时候。
源码: src/core/instance/lifecycle.js
核心是调用了 vm.__patch__,该方法根据平台的不同有区别。
在该方法中实现了 diff 算法,具体参考: Vue.js 技术揭秘
diff算法
diff 算法是通过同层的树节点进行比较而非对树进行逐层搜索遍历,所以时间复杂度只有 O(n),是一种高效的算法。

判断为同一个 VNode的依据
1 | /* |
如何进行对比?
1.如果新旧
VNode都是静态的,同时它们的key相同(代表同一节点),并且新的VNode是clone或者是标记了once(标记v-once属性,只渲染一次),那么只需要替换elm以及componentInstance即可。2.新老节点均有
children子节点,则对子节点进行diff操作,调用updateChildren,这个updateChildren也是diff的核心。3.如果老节点没有子节点而新节点存在子节点,先清空老节点
DOM的文本内容,然后为当前DOM节点加入子节点。4.当新节点没有子节点而老节点有子节点的时候,则移除该
DOM节点的所有子节点。5.当新老节点都无子节点的时候,只是文本的替换。
其他对比细节参考博客文章。
DOM操作
我们只是将虚拟 DOM 映射成了真实的 DOM。那如何给这些 DOM 加入 attr、class、style 等 DOM 属性呢?
需要在 create 以及 update 钩子被调用时更新 DOM 的 attr 属性即可
总结
模板和数据如何渲染成最终的 DOM 的过程分析完毕了,我们可以通过下图更直观地看到从初始化 Vue 到最终渲染的整个过程。
