4.4 虚拟Vnode映射成真实DOM
回到 updateComponent
的最后一个过程,虚拟的DOM
树在生成virtual dom
后,会调用Vue
原型上_update
方法,将虚拟DOM
映射成为真实的DOM
。从源码上可以知道,_update
的调用时机有两个,一个是发生在初次渲染阶段,另一个发生数据更新阶段。
updateComponent = function () {
// render生成虚拟DOM,update渲染真实DOM
vm._update(vm._render(), hydrating);
};
vm._update
方法的定义在lifecycleMixin
中。
lifecycleMixin()
function lifecycleMixin() {
Vue.prototype._update = function (vnode, hydrating) {
var vm = this;
var prevEl = vm.$el;
var prevVnode = vm._vnode; // prevVnode为旧vnode节点
// 通过是否有旧节点判断是初次渲染还是数据更新
if (!prevVnode) {
// 初次渲染
vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false)
} else {
// 数据更新
vm.$el = vm.__patch__(prevVnode, vnode);
}
}
_update
的核心是__patch__
方法,如果是服务端渲染,由于没有DOM
,_patch
方法是一个空函数,在有DOM
对象的浏览器环境下,__patch__
是patch
函数的引用。
// 浏览器端才有DOM,服务端没有dom,所以patch为一个空函数
Vue.prototype.__patch__ = inBrowser ? patch : noop;
而patch
方法又是createPatchFunction
方法的返回值,createPatchFunction
方法传递一个对象作为参数,对象拥有两个属性,nodeOps
和modules
,nodeOps
封装了一系列操作原生DOM
对象的方法。而modules
定义了模块的钩子函数。
var patch = createPatchFunction({ nodeOps: nodeOps, modules: modules });
// 将操作dom对象的方法合集做冻结操作
var nodeOps = /*#__PURE__*/Object.freeze({
createElement: createElement$1,
createElementNS: createElementNS,
createTextNode: createTextNode,
createComment: createComment,
insertBefore: insertBefore,
removeChild: removeChild,
appendChild: appendChild,
parentNode: parentNode,
nextSibling: nextSibling,
tagName: tagName,
setTextContent: setTextContent,
setStyleScope: setStyleScope
});
// 定义了模块的钩子函数
var platformModules = [
attrs,
klass,
events,
domProps,
style,
transition
];
var modules = platformModules.concat(baseModules);
真正的createPatchFunction
函数有一千多行代码,这里就不方便列举出来了,它的内部首先定义了一系列辅助的方法,而核心是通过调用createElm
方法进行dom
操作,创建节点,插入子节点,递归创建一个完整的DOM
树并插入到Body
中。并且在产生真实阶段阶段,会有diff
算法来判断前后Vnode
的差异,以求最小化改变真实阶段。后面会有一个章节的内容去讲解diff
算法。createPatchFunction
的过程只需要先记住一些结论,函数内部会调用封装好的DOM api
,根据Virtual DOM
的结果去生成真实的节点。其中如果遇到组件Vnode
时,会递归调用子组件的挂载过程,这个过程我们也会放到后面章节去分析。