1、什么是虚拟 DOM
- 虚拟 DOM(Virtual DOM)是使用 JavaScript 对象描述真实 DOM
- Vue.js 中的虚拟 DOM 借鉴 Snabbdom,并添加了 Vue.js 的特性。
例如: 指令和组件机制
2、 为什么要使用虚拟 DOM
- 避免直接操作 DOM,提高开发效率
- 作为一个中间层可以跨平台
- 虚拟 DOM 不一定可以提高性能
- 首次渲染的时候会增加开销
- 复杂视图情况下提升渲染性能
3、Vue 虚拟 dom 完整的渲染过程
- new Vue()
- this._init()
- vm.$mount()
- src/platforms/web/entry-runtime-with-compiler.js
- 如果没有传递 render,把模板编译成 render 函数
- compileToFunction() 生成 render() 渲染函数
- option.render = render
- vm.$mount
- src/platforms/web/runtime/index.js
- mountComponent()
- mountComponent(this, el)
- src/core/instance/lifecycle.js
- 判断是否含有 render 选项,如果没有但是传入了模板,并且当前是开发环境,会发送警告
- 触发 beforeMount
- 定义 updateComponent
- vm._update(vm._render(), ...)
- vm._render() 渲染,渲染虚拟DOM
- vm._update() 更新,将虚拟DOM转移成真实 DOM
- 创建 Watcher 实例
- updateComponent 传递
- 调用 get() 方法
- 触发 mounted
- return vm
- watcher.get()
- 创建完 watcher 回调用一次 get
- 调用 updateComponent()
- 调用 vm._render() 创建 Vnode
- 调用 render.call(vm._renderProxy, vm.$createElement)
- 实例化时 Vue 传入的 render()
- 或者编译 template 生成的 render()
- 返回 VNode
- 调用 vm._update(vnode, ...)
- 调用 vm.patch(vm.$el, vnode) 挂载真实 DOM
- 记录 vm.$el
- updateComponent()
- vm._render()
- vnode = render.call(vm._renderProxy, vm.$createElement)
- vm.$createElement()
- h函数,用户设置的 render 函数中调用
- createElement(vm, a, b, c, s, true)
- vm._c()
- h 函数,模板编译的 render 函数中调用
- createElement(vm, a, b, c, d, true)
- _createElement()
- vnode = new VNode(config.parePlatformTagname(tag), data, children, undefined, undefined, context)
- vm._render() 结束,返回 VNode
- vm._update()
- 负责把虚拟 DOM,渲染成真实 DOM
- 首次执行 vm.patch(vm.$el, vnode, hydrating, false)
- 数据更新 vm.patch(prevVnode, vnode)
- vm.patch()
- runtime/index.js 中挂载 Vue.prototype.patch
- runtime/patch.js 的 patch 函数
- 设置 modules 和 nodeOps
- 调用 createPacthFunction() 函数 返回 patch 函数
- patch()
- vdom/patch.js 中的 createPatchFunction 返回 patch 函数
- 挂载 cbs 节点的属性/事件/样式操作的钩子函数
- 判断第一个参数是真实 DOM 还是虚拟 DOM。首次加载,第一个参数就是真实 DOM,转换成 VNode,调用 createElm
- 如果是数据更新的时候,新旧节点是 sameVnode 执行 patchVnode,就是Diff
- 删除旧节点
- createElement(vnode, insrtedVnodeQueue)
- 把虚拟节点,转换成真实 DOM,并插入到 DOM 树
- 把虚拟节点的 children,转换为真实 DOM,并插入到 DOM 树
- patchVnode
- 对比新旧Vnode,以及新旧VNode的子节点更新差异
- 如果新旧VNode都有子节点并且子节点不同的话,会调用updateChildren 对比子节点的差异
- updateChildren
- 从头和尾开始依次找到相同的子节点进行比较 patchVnode,总共有四种比较方式
- 在老节点的子节点中查找 newStartVnode,并进行处理
- 如果新节点比老节点多,把新增的子节点插入到 DOM 中
- 如果老节点比新节点多,把多余的老节点删除