什么是虚拟 DOM?
- 虚拟
DOM(Virtual DOM)
是使用JavaScript
对象描述真实的DOM
Vue.js
中的虚拟DOM
借鉴Snabbdom
,并添加了Vue.js
的特性。- eg: 指令和组件机制
为什么要使用虚拟 DOM
- 避免直接操作
DOM
,提高开发效率 - 作为一个中间层可以跨平台
- 虚拟
DOM
不一定可以提高性能- 首次渲染的时候会增加开销
- 复杂视图情况下提升渲染性能
示例代码
const vm = new Vue({
el: '#app',
render (h) {
// h(tag, data, children)
// return h('h1', this.msg)
// return h('h1', { domProps: { innerHTML: this.msg } })
// return h('h1', { attrs: { id: 'title' } }, this.msg)
const vnode = h(
'h1',
{
attrs: { id: 'title' }
},
this.msg
)
console.log(vnode)
return null
},
data: {
msg: 'Hello Vue'
}
})
h 函数
vm.$createElement(tag, data, children, normalizeChildren)
tag
- 标签名称或者组件对象data
- 描述tag
,可以设置DOM
的属性或者标签的属性children
-tag
中的文本内容或者子节点
VNode
VNode
的核心属性tag
data
children
text
elm
key
VNode 的创建过程 - createElement
src/core/vdom/create-element.js
代码片段
const SIMPLE_NORMALIZE = 1
const ALWAYS_NORMALIZE = 2
// wrapper function for providing a more flexible interface
// without getting yelled at by flow
export function createElement (
context: Component,
tag: any,
data: any,
children: any,
normalizationType: any,
alwaysNormalize: boolean
): VNode | Array<VNode> {
if (Array.isArray(data) || isPrimitive(data)) {
normalizationType = children
children = data
data = undefined
}
if (isTrue(alwaysNormalize)) {
normalizationType = ALWAYS_NORMALIZE
}
return _createElement(context, tag, data, children, normalizationType)
}
update
src/core/instance/lifecycle.js
// _update 方法的作用是把 VNode 渲染成真实的 DOM
// 首次渲染会调用,数据更新会调用
Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
const vm: Component = this
const prevEl = vm.$el
const prevVnode = vm._vnode
const restoreActiveInstance = setActiveInstance(vm)
vm._vnode = vnode
// Vue.prototype.__patch__ is injected in entry points
// based on the rendering backend used.
if (!prevVnode) {
// initial render
vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
} else {
// updates
vm.$el = vm.__patch__(prevVnode, vnode)
}
restoreActiveInstance()
// update __vue__ reference
if (prevEl) {
prevEl.__vue__ = null
}
if (vm.$el) {
vm.$el.__vue__ = vm
}
// if parent is an HOC, update its $el as well
if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {
vm.$parent.$el = vm.$el
}
// updated hook is called by the scheduler to ensure that children are
// updated in a parent's updated hook.
}
patch 函数
src/platforms/web/runtime/index.js
import { patch } from './patch'
Vue.prototype.__patch__ == inBrowser ? patch : noop
src/platforms/web/runtime/patch.js
export const patch: Function = createPatchFunction({ nodeOps, modules });
执行过程
- createElm() - 把
vnode
转换成真实DOM
挂载到DOM
树上 - patchVnode()
// 创建 DOM 节点
createElm(
vnode,
insertedVnodeQueue,
// extremely rare edge case: do not insert if old element is in a
// leaving transition. Only happens when combining transition +
// keep-alive + HOCs. (#4590)
oldElm._leaveCb ? null : parentElm,
nodeOps.nextSibling(oldElm) // 如果这个不为空,则会把创建好的dom插入到它之前
)
// 更新操作,diff 算法
// patch existing root node
patchVnode(oldVnode, vnode, insertedVnodeQueue, null, null, removeOnly)
// updateChildren
// diff 算法
// 更新新旧节点的子节点
function updateChildren (parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly)
整个Diff过程分析 点击查看