- 4.4 虚拟Vnode映射成真实DOM
4.4 虚拟Vnode映射成真实DOM
回到 updateComponent的最后一个过程,虚拟的DOM树在生成virtual dom后,会调用Vue原型上_update方法,将虚拟DOM映射成为真实的DOM。从源码上可以知道,_update的调用时机有两个,一个是发生在初次渲染阶段,另一个发生数据更新阶段。
updateComponent = function () {// render生成虚拟DOM,update渲染真实DOMvm._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时,会递归调用子组件的挂载过程,这个过程我们也会放到后面章节去分析。
