// 定义并导出initRender函数,接受vm exportfunctioninitRender (vm: Component) { // 初始化实例的根虚拟节点 vm._vnode = null// the root of the child tree // 定义实例的静态树节点 vm._staticTrees = null// v-once cached trees // 获取配置对象 const options = vm.$options // 设置父占位符节点 const parentVnode = vm.$vnode = options._parentVnode // the placeholder node in parent tree // renderContext存储父节点有无声明上下文 const renderContext = parentVnode && parentVnode.context // 将子虚拟节点转换成格式化的对象结构存储在实例的$slots属性 vm.$slots = resolveSlots(options._renderChildren, renderContext) // 初始化$scopedSlots属性为空对象 vm.$scopedSlots = emptyObject
// 为实例绑定渲染虚拟节点函数_c和$createElement // 内部实际调用createElement函数,并获得恰当的渲染上下文 // 参数按顺序分别是:标签、数据、子节点、标准化类型、是否标准化标识 // bind the createElement fn to this instance // so that we get proper render context inside it. // args order: tag, data, children, normalizationType, alwaysNormalize
// 内部版本_c被从模板编译的渲染函数使用 // internal version is used by render functions compiled from templates vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false) // 用户写的渲染函数会总是应用执行标准化的公共版本 // normalization is always applied for the public version, used in // user-written render functions. vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)
// 为了更容易创建高阶组件,暴露了$attrs 和 $listeners // 并且需要保持属性的响应性以便能够实现更新,以下是对属性的响应处理 // $attrs & $listeners are exposed for easier HOC creation. // they need to be reactive so that HOCs using them are always updated const parentData = parentVnode && parentVnode.data
// 非生产环境下重置插槽上的_rendered标志以进行重复插槽检查 // reset _rendered flag on slots for duplicate slot check if (process.env.NODE_ENV !== 'production') { for (const key in vm.$slots) { // $flow-disable-line vm.$slots[key]._rendered = false } }
// public mount method Vue.prototype.$mount = function ( el?: string | Element, hydrating?: boolean ): Component{ el = el && inBrowser ? query(el) : undefined return mountComponent(this, el, hydrating) }
// 定义并导出mountComponent函数 // 接受Vue实例vm,DOM元素el、布尔标识hydrating参数 // 后两参数可选,返回组件实例 exportfunctionmountComponent ( vm: Component, el: ?Element, hydrating?: boolean ): Component{ // 设置实例的$el属性 vm.$el = el // 检测实例属性$options对象的render方法,未定义则设置为创建空节点 if (!vm.$options.render) { vm.$options.render = createEmptyVNode // 非生产环境检测构建版本并警告 if (process.env.NODE_ENV !== 'production') { /* istanbul ignore if */ if ((vm.$options.template && vm.$options.template.charAt(0) !== '#') || vm.$options.el || el) { warn( 'You are using the runtime-only build of Vue where the template ' + 'compiler is not available. Either pre-compile the templates into ' + 'render functions, or use the compiler-included build.', vm ) } else { warn( 'Failed to mount component: template or render function not defined.', vm ) } } } // 调用生命周期钩子函数beforeMount,准备首次加载 callHook(vm, 'beforeMount')
// 定义updateComponent方法 let updateComponent // 非生产环境加入性能评估 /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && config.performance && mark) { updateComponent = () => { const name = vm._name const id = vm._uid const startTag = `vue-perf-start:${id}` const endTag = `vue-perf-end:${id}`
// 在Watcher类内部将此监听器设置到实例的_watcher上。 // 由于初次patch可能调用$forceUpdate方法(例如在子组件的mounted钩子), // 这依赖于已经定义好的vm._watcher // we set this to vm._watcher inside the watcher's constructor // since the watcher's initial patch may call $forceUpdate (e.g. inside child // component's mounted hook), which relies on vm._watcher being already defined // 建立对渲染的观察,最末参数声明为渲染监听器,并传入监视器的before方法, // 在初次渲染之后,实例的_isMounted为true,在每次渲染更新之前会调用update钩子 new Watcher(vm, updateComponent, noop, { before () { if (vm._isMounted) { callHook(vm, 'beforeUpdate') } } }, true/* isRenderWatcher */) // 设置hydrating标识为false hydrating = false
// 手动安装的实例,mounted调用挂载在自身 // 渲染创建的子组件在其插入的钩子中调用了mounted // manually mounted instance, call mounted on self // mounted is called for render-created child components in its inserted hook // vm.$vnode为空设置_isMounted属性为true,并调用mounted钩子 // vm.$vnode为空是因为实例是根组件,没有父级节点。 if (vm.$vnode == null) { vm._isMounted = true callHook(vm, 'mounted') } // 返回实例 return vm }
// 为组件作用域CSS设置范围id属性。 // 这是作为一种特殊情况实现的,以避免通过正常的属性修补过程的开销。 // set scope id attribute for scoped CSS. // this is implemented as a special case to avoid the overhead // of going through the normal attribute patching process. // 设置CSS作用域ID functionsetScope (vnode) {}
// 渲染混合 // 注意:这是一个仅限浏览器的函数,因此我们可以假设elms是DOM节点。 // Note: this is a browser-only function so we can assume elms are DOM nodes. functionhydrate (elm, vnode, insertedVnodeQueue, inVPre) {}
// 初始化isInitialPatch标识和insertedVnodeQueue队列 let isInitialPatch = false const insertedVnodeQueue = []
// 以下分两种情况构建节点: // 如果不存在旧虚拟节点 if (isUndef(oldVnode)) { // 空挂载(比如组件),会创建新的根元素 // empty mount (likely as component), create new root element // 这种情况说明时首次渲染,设置isInitialPatch为true isInitialPatch = true // 根据虚拟节点创建新DOM节点 createElm(vnode, insertedVnodeQueue) } else { // 存在旧虚拟节点 // 判断旧虚拟节点是否是真实的DOM元素 const isRealElement = isDef(oldVnode.nodeType) // 如果不是真实DOM节点并且新旧虚拟节点根节点相同 if (!isRealElement && sameVnode(oldVnode, vnode)) { // 执行比较新旧节点更新DOM操作 // patch existing root node patchVnode(oldVnode, vnode, insertedVnodeQueue, removeOnly) } else { // 新旧节点不相同的情况 // 旧节点是DOM元素时先将旧节点转换成虚拟节点 if (isRealElement) { // 挂在到真实DOM元素 // 检查是否是服务器渲染,然后执行合并操作 // mounting to a real element // check if this is server-rendered content and if we can perform // a successful hydration. // 下面这两个if语句里的操作都是服务器渲染相关,暂不去了解 if (oldVnode.nodeType === 1 && oldVnode.hasAttribute(SSR_ATTR)) { oldVnode.removeAttribute(SSR_ATTR) hydrating = true } if (isTrue(hydrating)) { if (hydrate(oldVnode, vnode, insertedVnodeQueue)) { invokeInsertHook(vnode, insertedVnodeQueue, true) return oldVnode } elseif (process.env.NODE_ENV !== 'production') { warn( 'The client-side rendered virtual DOM tree is not matching ' + 'server-rendered content. This is likely caused by incorrect ' + 'HTML markup, for example nesting block-level elements inside ' + '<p>, or missing <tbody>. Bailing hydration and performing ' + 'full client-side render.' ) } } // 如果不是服务器渲染或合并失败,生成空的虚拟节点 // either not server-rendered, or hydration failed. // create an empty node and replace it oldVnode = emptyNodeAt(oldVnode) }
// 根据新虚拟节点创建新DOM元素,并且会插入到DOM树中 // create new node createElm( vnode, insertedVnodeQueue, // 以下参数是#4590问题的解决处理 // 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) )
// 如果新的虚拟节点有父级则以递归方式更新父占位符节点元素 // cbs是在生成patch函数时初始化好的事件监听器 // 在此条件中也会被逐一触发 // update parent placeholder node element, recursively if (isDef(vnode.parent)) { let ancestor = vnode.parent const patchable = isPatchable(vnode) while (ancestor) { for (let i = 0; i < cbs.destroy.length; ++i) { cbs.destroy[i](ancestor) } ancestor.elm = vnode.elm if (patchable) { for (let i = 0; i < cbs.create.length; ++i) { cbs.create[i](emptyNode, ancestor) } // #6513 // invoke insert hooks that may have been merged by create hooks. // e.g. for directives that uses the "inserted" hook. const insert = ancestor.data.hook.insert if (insert.merged) { // start at index 1 to avoid re-invoking component mounted hook for (let i = 1; i < insert.fns.length; i++) { insert.fns[i]() } } } else { registerRef(ancestor) } ancestor = ancestor.parent } }
// 定义createElm函数,一系列参数主要记住vnode,parentElm functioncreateElm ( vnode, insertedVnodeQueue, parentElm, refElm, nested, ownerArray, index ) { // 如果新虚拟节点存在真实DOM元素和ownerArray, // 则代表它在之前的渲染中用过。 // 现在要被用作新节点时有潜在的错误 // 所以将它改为从本身克隆的节点 if (isDef(vnode.elm) && isDef(ownerArray)) { // This vnode was used in a previous render! // now it's used as a new node, overwriting its elm would cause // potential patch errors down the road when it's used as an insertion // reference node. Instead, we clone the node on-demand before creating // associated DOM element for it. vnode = ownerArray[index] = cloneVNode(vnode) }
// 设置isRootInsert,为检查过度动画入口 vnode.isRootInsert = !nested // for transition enter check // 下面判断用于keep-alive组件,若是普通组件则会返回undefined继续往下执行 if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) { return }
// 获取虚拟节点信息、子节点和标签名称 const data = vnode.data const children = vnode.children const tag = vnode.tag // 下面三种情况创建普通节点、注释节点和文字节点 if (isDef(tag)) { // 具有标签名称,则创建普通节点 // 非生产环境简则是否是正确的元素 if (process.env.NODE_ENV !== 'production') { if (data && data.pre) { creatingElmInVPre++ } if (isUnknownElement(vnode, creatingElmInVPre)) { warn( 'Unknown custom element: <' + tag + '> - did you ' + 'register the component correctly? For recursive components, ' + 'make sure to provide the "name" option.', vnode.context ) } }
// 如果是weex平台,可以根据参数调整节点树插入DOM的具体实现 /* istanbul ignore if */ if (__WEEX__) { // in Weex, the default insertion order is parent-first. // List items can be optimized to use children-first insertion // with append="tree". const appendAsTree = isDef(data) && isTrue(data.appendAsTree) if (!appendAsTree) { if (isDef(data)) { invokeCreateHooks(vnode, insertedVnodeQueue) } insert(parentElm, vnode.elm, refElm) } createChildren(vnode, children, insertedVnodeQueue) if (appendAsTree) { if (isDef(data)) { invokeCreateHooks(vnode, insertedVnodeQueue) } insert(parentElm, vnode.elm, refElm) } } else { // web平台则先创建子节点插入父级后再一次插入DOM中 createChildren(vnode, children, insertedVnodeQueue) if (isDef(data)) { invokeCreateHooks(vnode, insertedVnodeQueue) } insert(parentElm, vnode.elm, refElm) }
// 获取并设置新虚拟节点的真实DOM元素 const elm = vnode.elm = oldVnode.elm
// 异步占位符节点的特殊处理 if (isTrue(oldVnode.isAsyncPlaceholder)) { if (isDef(vnode.asyncFactory.resolved)) { hydrate(oldVnode.elm, vnode, insertedVnodeQueue) } else { vnode.isAsyncPlaceholder = true } return }
// 为静态树重用元素 // 只在克隆虚拟节点时使用,如非克隆节点则需要重新渲染 // reuse element for static trees. // note we only do this if the vnode is cloned - // if the new node is not cloned it means the render functions have been // reset by the hot-reload-api and we need to do a proper re-render. if (isTrue(vnode.isStatic) && isTrue(oldVnode.isStatic) && vnode.key === oldVnode.key && (isTrue(vnode.isCloned) || isTrue(vnode.isOnce)) ) { vnode.componentInstance = oldVnode.componentInstance return }
// 如果存在内联预处理钩子则调用 let i const data = vnode.data if (isDef(data) && isDef(i = data.hook) && isDef(i = i.prepatch)) { i(oldVnode, vnode) }
// 定义updateChildren函数,接受5个参数 functionupdateChildren (parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) { // 初始化逻辑需要的变量,由于此函数仅针对子节点,所以以下省略“子”字 let oldStartIdx = 0// 旧节点开始索引 let newStartIdx = 0// 新节点开始索引 let oldEndIdx = oldCh.length - 1// 旧节点结束索引 let oldStartVnode = oldCh[0] // 当前旧首节点 let oldEndVnode = oldCh[oldEndIdx] // 当前旧尾节点 let newEndIdx = newCh.length - 1// 新节点结束索引 let newStartVnode = newCh[0] // 当前新首节点 let newEndVnode = newCh[newEndIdx] // 当前新尾节点 let oldKeyToIdx, idxInOld, vnodeToMove, refElm
// removeOnly是仅用于<transition-group>情况下的特殊标识, // 确保移除的元素在离开过渡期间保持在正确的相对位置。 // removeOnly is a special flag used only by <transition-group> // to ensure removed elements stay in correct relative positions // during leaving transitions const canMove = !removeOnly
// 检查新节点中有无重复key if (process.env.NODE_ENV !== 'production') { checkDuplicateKeys(newCh) }