这一节肯定能完!
经过DOM字符串的AST转化,再通过render变成vnode,最后就剩下patch到页面上了。
render函数跑完应该是在这里:
function mountComponent(vm, el, hydrating) { vm.$el = el; if (!vm.$options.render) { vm.$options.render = createEmptyVNode; { // warning } } // beforeMount var updateComponent; /* istanbul ignore if */ if ("development" !== 'production' && config.performance && mark) { updateComponent = function() { // dev render }; } else { updateComponent = function() { vm._update(vm._render(), hydrating); }; } // code... // mounted return vm }
vm._render()会生成一个vnode看,接下来调用_update渲染页面,如下:
Vue.prototype._update = function(vnode, hydrating) { var vm = this; // beforeUpdate // code... if (!prevVnode) { // initial render vm.$el = vm.__patch__( vm.$el, vnode, hydrating, false /* removeOnly */ , vm.$options._parentElm, vm.$options._refElm ); } else { // updates vm.$el = vm.__patch__(prevVnode, vnode); } // code... }; Vue$3.prototype.__patch__ = inBrowser ? patch : noop; var patch = createPatchFunction({ nodeOps: nodeOps, modules: modules }); function createPatchFunction(backend) { var i, j; var cbs = {}; var modules = backend.modules; var nodeOps = backend.nodeOps; for (i = 0; i < hooks.length; ++i) { // hook... } // fn... return function patch(oldVnode, vnode, hydrating, removeOnly, parentElm, refElm) { // code... if (isUndef(oldVnode)) { // component... } else { var isRealElement = isDef(oldVnode.nodeType); if (!isRealElement && sameVnode(oldVnode, vnode)) { // patch existing root node } else { // SSR or hydrating var oldElm = oldVnode.elm; var parentElm$1 = nodeOps.parentNode(oldElm); createElm( vnode, insertedVnodeQueue, oldElm._leaveCb ? null : parentElm$1, nodeOps.nextSibling(oldElm) ); //code... } } invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch); return vnode.elm } }
由于是初始化页面,所有在update的过程中,oldVNode被设置为空的div虚拟DOM,然后与生成的虚拟DOM进行替换。
核心细节在上述代码中的createElm函数:
// vnode => 生成的vnode // insertedVnodeQueue => [] // parentElm => body // refElm => #text // nested => undefined function createElm(vnode, insertedVnodeQueue, parentElm, refElm, nested) { vnode.isRootInsert = !nested; // for transition enter check if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) { return } var data = vnode.data; var children = vnode.children; var tag = vnode.tag; if (isDef(tag)) { // pre... vnode.elm = vnode.ns ? nodeOps.createElementNS(vnode.ns, tag) : // 调用这个生成一个tag标签 nodeOps.createElement(tag, vnode); setScope(vnode); { // 处理子节点 // 子节点是5个vnode组成的数据 因此会循环调用本函数 createChildren(vnode, children, insertedVnodeQueue); if (isDef(data)) { // 生成DOM节点的属性 invokeCreateHooks(vnode, insertedVnodeQueue); } // 将子节点插入到父节点中 // 处理到最外层节点 页面会渲染 insert(parentElm, vnode.elm, refElm); } if ("development" !== 'production' && data && data.pre) { inPre--; } } else if (isTrue(vnode.isComment)) { vnode.elm = nodeOps.createComment(vnode.text); insert(parentElm, vnode.elm, refElm); } else { vnode.elm = nodeOps.createTextNode(vnode.text); insert(parentElm, vnode.elm, refElm); } }
其实,这个普通的patch没有区别,只是由于是多个标签,所以会有兄弟元素,在插入节点会调用insertBefore进行插入,最后5个a标签依次插入生成的div,然后div插入body标签完成页面渲染。
虽然循环生成a标签以及其属性比较麻烦,但是由于整个标签是一次性插入body中,所以对于性能也没有什么影响。