Example 2
The template of this instance is as follows:
<div id="app"> <mycom></mycom> <div></div> </div> <script> var vm = new Vue({ el:'#app', data:{ message:123 },components:{ "mycom":{ template:'<div><aa></aa></div>', data(){ return {} }, components:{ "aa":{ template:'<span></span>', data(){ return {} } } } } } }) </script>
It is mainly the creation process of component instances.
Basic introduction of example 2
For our example 2, you may because Example 1 Because instance 1 is only a root component, but so many processes are written. In fact, it's normal for you to think so, but we need to understand that the process of instance 1 is to create a component, the root component is a component, and the sub component is also a component, that is, the sub component will follow the same process as the root component. In other words, in the next process of sub component nesting, most of the code is the same, so don't worry. In order to simplify the process, we will briefly talk about the same parts and explain in detail the different key points. OK, let's start our explanation of example 2.
First, let's sort out the structure of the components we will create:
<div id="app"> <mycom></mycom> <div></div> </div>
< mycom > component:
<div> <aa></aa> </div>
< AA > components
<span></span>
The above is the basic structure of our components. After understanding the basic structure, we formally enter the source code to analyze.
Creation of root component instance
First, create an instance object of the root component through the new Vue constructor, which we call vm_0
Root component data initialization
Then execute vm_0._init() function, as we mentioned earlier, is mainly used to initialize the data of component instances. We enter this function and mainly call:
initLifecycle(vm) initEvents(vm) initRender(vm) callHook(vm, 'beforeCreate') initInjections(vm) // resolve injections before data/props initState(vm) initProvide(vm) // resolve provide after data/props callHook(vm, 'created')
Get render function
After these are executed, the data initialization of the root component is basically completed, and the mount part starts after the initialization is completed. The mount call is: VM$ Mount (VM. $options. EL) function, which mainly judges VM_ Whether the 0 instance object has a render function, obviously not. Then, the render function will be generated by parsing the template and mounted to the vm_0.render.
Here we review the process of obtaining the render function again. First, judge whether we have a render function written by ourselves. If we do not start parsing, whether we have defined the template configuration. If so, use this configuration item to generate the render function through compilation. If we do not define the template, we can only use the template corresponding to the id of our root component as the template. Then compile to generate the render function. Then assign the generated render function to vm_0.render.
After mounting the rendering function, call mount Call (this, El, hydrating) function to carry out the function module of the next part. The mountComponent function is called internally by the mount function. Let's look at the mountComponent function
Create a watcher instance
In the mountComponent function, you mainly do these things. First, judge the render function, then execute the beforeMount hook function, then define the updateComponent function, and then execute the new Watcher.
In the watcher constructor, first create a_ The watcher instance object corresponding to the 0 instance object, which is used to represent the vm_0 this component is called watcher. when watcher is created. Get function.
This function first pushes the watcher into the target stack to indicate that it is now VM_ The process in which the 0 component is rendering. Then execute updateComponent. This function is used for mounting. Let's take a look at the internal operations of this function.
Generate vnode of root component
Enter the inside of the updateComponent function:
updateComponent = () => { //vm._update is defined in lifecycle mixin (Vue) //vm._render is defined in renderMixin. //hydrating:false //The function is actually executed in new Watcher(). We only focus on its execution for the time being, not on where it is triggered. vm._update(vm._render(), hydrating) }
The first is to call vm_0._render function, which we talked about in example 1, is used to generate vnode s of components. Now let's go into this function and go through its specific process roughly. Enter into_ In the render function, the render function is parsed first, and then the vm_0 instance mounts some properties. Then it calls render Call function. The inside of the render function is as follows:
_c('div',{attrs:{"id":"app"}},[_c('mycom'),_v(" "),_c('div')],1)
About_ The internal processing mechanism of c function has been explained in example 1. But I still want to talk about it here, because it involves a special node, that is, the component node. When called_ When using the c function, its internal call is the createElement function, and the internal call of this function is_ CreateElement function. Let's look at the internal code logic of this function:
export function _createElement ( context: Component, tag?: string | Class<Component> | Function | Object, data?: VNodeData, children?: any, normalizationType?: number ): VNode | Array<VNode> { if (isDef(data) && isDef((data: any).__ob__)) { process.env.NODE_ENV !== 'production' && warn( ``Avoid using observed data object as vnode data: ${JSON.stringify(data)} 'Always create fresh vnode data objects in each render!', context ) return createEmptyVNode() } // object syntax in v-bind if (isDef(data) && isDef(data.is)) { tag = data.is } if (!tag) { // in case of component :is set to falsy value return createEmptyVNode() } // warn against non-primitive key if (process.env.NODE_ENV !== 'production' && isDef(data) && isDef(data.key) && !isPrimitive(data.key) ) { if (!__WEEX__ || !('@binding' in data.key)) { warn( 'Avoid using non-primitive value as key, ' + 'use string/number value instead.', context ) } } // support single function children as default scoped slot if (Array.isArray(children) && typeof children[0] === 'function' ) { data = data || {} data.scopedSlots = { default: children[0] } children.length = 0 } if (normalizationType === ALWAYS_NORMALIZE) { children = normalizeChildren(children) } else if (normalizationType === SIMPLE_NORMALIZE) { children = simpleNormalizeChildren(children) } let vnode, ns if (typeof tag === 'string') { let Ctor ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag) if (config.isReservedTag(tag)) { // platform built-in elements if (process.env.NODE_ENV !== 'production' && isDef(data) && isDef(data.nativeOn)) { warn( ``The .native modifier for v-on is only valid on components but it was used on <${tag}>.``, context ) } vnode = new VNode( config.parsePlatformTagName(tag), data, children, undefined, undefined, context ) } else if ((!data || !data.pre) && isDef(Ctor = resolveAsset(context.$options, 'components', tag))) { vnode = createComponent(Ctor, data, context, children, tag) console.log(vnode) } else { vnode = new VNode( tag, data, children, undefined, undefined, context ) } } else { vnode = createComponent(tag, data, context, children) } if (Array.isArray(vnode)) { return vnode } else if (isDef(vnode)) { if (isDef(ns)) applyNS(vnode, ns) if (isDef(data)) registerDeepBindings(data) return vnode } else { return createEmptyVNode() } }
If it is a normal element node, there is no doubt that it will go:
vnode = new VNode( config.parsePlatformTagName(tag), data, children, undefined, undefined, context )
This branch, and then create the vnode of the element node.
Component node encountered
But interestingly, there is one of the child nodes of this node_ c('mycom'). In other words, the tag passed in is not a native element tag, but a custom tag. Let's see which branch it will take. Obviously, he will go:
else if ((!data || !data.pre) && isDef(Ctor = resolveAsset(context.$options, 'components', tag))) { // component debugger vnode = createComponent(Ctor, data, context, children, tag) console.log(vnode) }
That is, the createComponent function will be called. Let's enter this function to see the internal code structure:
export function createComponent ( Ctor: Class<Component> | Function | Object | void, data: ?VNodeData, context: Component, children: ?Array<VNode>, tag?: string ): VNode | Array<VNode> | void { if (isUndef(Ctor)) { return } const baseCtor = context.$options._base// Vue // plain options object: turn it into a constructor if (isObject(Ctor)) { Ctor = baseCtor.extend(Ctor)//Turn an object into a function } // if at this stage it's not a constructor or an async component factory, // reject. if (typeof Ctor !== 'function') {//Determine whether it is a function if (process.env.NODE_ENV !== 'production') { warn(`Invalid Component definition: ${String(Ctor)}`, context) } return } // async component let asyncFactory if (isUndef(Ctor.cid)) { asyncFactory = Ctor Ctor = resolveAsyncComponent(asyncFactory, baseCtor) if (Ctor === undefined) { // return a placeholder node for async component, which is rendered // as a comment node but preserves all the raw information for the node. // the information will be used for async server-rendering and hydration. return createAsyncPlaceholder( asyncFactory, data, context, children, tag ) } } data = data || {} // resolve constructor options in case global mixins are applied after // component constructor creation //Resolve constructor options when global mixing is applied after the component constructor is created resolveConstructorOptions(Ctor) // transform component v-model data into props & events if (isDef(data.model)) { transformModel(Ctor.options, data) } // extract props const propsData = extractPropsFromVNodeData(data, Ctor, tag) // functional component if (isTrue(Ctor.options.functional)) { return createFunctionalComponent(Ctor, propsData, data, context, children) } // extract listeners, since these needs to be treated as // child component listeners instead of DOM listeners const listeners = data.on // replace with listeners with .native modifier // so it gets processed during parent component patch. data.on = data.nativeOn if (isTrue(Ctor.options.abstract)) { // abstract components do not keep anything // other than props & listeners & slot // work around flow const slot = data.slot data = {} if (slot) { data.slot = slot } } // install component management hooks onto the placeholder node installComponentHooks(data) // return a placeholder vnode const name = Ctor.options.name || tag const vnode = new VNode( `vue-component-${Ctor.cid}${name ? `-${name}` : ''}`, data, undefined, undefined, undefined, context, { Ctor, propsData, listeners, tag, children }, asyncFactory ) //...... return vnode }
First, let's explain some parameters:
Ctor:{template: "<div><aa></aa></div>", components: {...}, _Ctor: {...}, data: ƒ}//In fact, it is the configuration type we passed in. data:undefined context:vm_0 children:undefined tag:"mycom"
First get the Vue constructor, and then judge whether Ctor is an object. Obviously, it is, and then pass Ctor in:
Ctor = baseCtor.extend(Ctor)//Change object to function baseCtor === Vue
Because Vue. Is called Extend function, let's take a look at the specific code implementation inside the function:
Vue.extend = function (extendOptions: Object): Function { extendOptions = extendOptions || {} const Super = this const SuperId = Super.cid const cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {}) if (cachedCtors[SuperId]) { return cachedCtors[SuperId] } const name = extendOptions.name || Super.options.name if (process.env.NODE_ENV !== 'production' && name) { validateComponentName(name) } const Sub = function VueComponent (options) { this._init(options) } Sub.prototype = Object.create(Super.prototype) Sub.prototype.constructor = Sub Sub.cid = cid++ Sub.options = mergeOptions( Super.options, extendOptions ) Sub['super'] = Super // For props and computed properties, we define the proxy getters on // the Vue instances at extension time, on the extended prototype. This // avoids Object.defineProperty calls for each instance created. if (Sub.options.props) { initProps(Sub) } if (Sub.options.computed) { initComputed(Sub) } // allow further extension/mixin/plugin usage Sub.extend = Super.extend Sub.mixin = Super.mixin Sub.use = Super.use // create asset registers, so extended classes // can have their private assets too. ASSET_TYPES.forEach(function (type) { Sub[type] = Super[type] }) // enable recursive self-lookup if (name) { Sub.options.components[name] = Sub } // keep a reference to the super options at extension time. // later at instantiation we can check if Super's options have // been updated. Sub.superOptions = Super.options Sub.extendOptions = extendOptions Sub.sealedOptions = extend({}, Sub.options) // cache constructor cachedCtors[SuperId] = Sub return Sub }
In fact, it is also very simple. The interior of the function mainly does two things: first, declare a constructor, then mount various properties on the constructor, and then the function. Let's go back to the createComponent function and continue to execute the code. The subsequent code is the processing of asynchronous components and some events. We will ignore it for the time being. Let's look at the main parts first. When the above code is executed, it will execute:
installComponentHooks(data)
The installComponentHook function is mainly used to add some hook functions to the data object. It mainly includes the following four aspects:
const componentVNodeHooks = { init (vnode: VNodeWithData, hydrating: boolean): ?boolean { //..... }, prepatch (oldVnode: MountedComponentVNode, vnode: MountedComponentVNode) { //.... }, insert (vnode: MountedComponentVNode) { //.... }, destroy (vnode: MountedComponentVNode) { //....... } }
After adding, execute:
const vnode = new VNode( `vue-component-${Ctor.cid}${name ? `-${name}` : ''}`, data, undefined, undefined, undefined, context, { Ctor, propsData, listeners, tag, children }, asyncFactory )
At this time, the component vnode will be generated, which is Vue's processing of the component node. Let's take a look at the complete vnode generation results.
//Easy version { tag:'div', data:{attrs: {...}}, children:[ { tag:"vue-component-1-mycom", data:{ hooks: {init: ƒ, prepatch: ƒ, insert: ƒ, destroy: ƒ}, on:undefined }, children:undefined }, { tag: undefined, data: undefined, children: undefined, }, { tag:'div', data:undefined, children:undefined } ] }
Nodes are created by calling vm_0._updata function to create a real node.
Create a real node object
We enter VM_ 0._ In the update function, because this is the first call, it will call:
vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
We enter vm_0.__patch__ Function. When we first render DOM, oldVnode is vm_0.$el. Is a real dom. vnode is a virtual node that we customize or generate through compilation. When we execute the patch function for the first time, it will execute the following code:
oldVnode = emptyNodeAt(oldVnode)
The emptynodeatt function converts a real DOM into a virtual dom. In fact, the reason why we should do this is understandable. If we modify the data, then render again. Before rendering, Vue will compare each node through diff algorithm in order to reduce the number of DOM operations. However, when comparing nodes, it is not through the real DOM, but through the original virtual Dom and the virtual dom of the modified data. At this time, we need to compare our real VM$ El into our virtual dom.
When the above data is ready, we start to execute the following code:
createElm( vnode, insertedVnodeQueue, oldElm._leaveCb ? null : parentElm, nodeOps.nextSibling(oldElm) )
Starting with this function, start creating the real node object. Let's see how Vue handles when there are component nodes in the vnode of the component instance object. Let's take another look at vnode.
{ tag:'div', data:{attrs:{"id":"app"}}, children:[ { tag:"vue-component-1-mycom", data:undefined, children:undefined }, { text:" " }, { tag:'div', data:undefined, children:undefined } ] }
When entering the createElm function, first judge whether our vnode is a component. Obviously, it is not. Then create a div element node, and then traverse the children to call the createElm function one by one.
Component node encountered
The first is children[0], our component vnode. Call first:
if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) { return }
Because it is a component vnode, this condition is met, so let's take a look at the internal code of the function:
function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) { let i = vnode.data if (isDef(i)) { const isReactivated = isDef(vnode.componentInstance) && i.keepAlive if (isDef(i = i.hook) && isDef(i = i.init)) { i(vnode, false /* hydrating */) } // after calling the init hook, if the vnode is a child component // it should've created a child instance and mounted it. the child // component also has set the placeholder vnode's elm. // in that case we can just return the element and be done. if (isDef(vnode.componentInstance)) { initComponent(vnode, insertedVnodeQueue) insert(parentElm, vnode.elm, refElm) if (isTrue(isReactivated)) { reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm) } return true } } }
The first step is to get vnode There are four functions in the data object. As we mentioned above, we can judge whether the init() function exists. Obviously, it does exist, so the init(vnode) function will be called. Let's see what the init function does internally.
init (vnode: VNodeWithData, hydrating: boolean): ?boolean { if ( vnode.componentInstance && !vnode.componentInstance._isDestroyed && vnode.data.keepAlive ) { // kept-alive components, treat as a patch const mountedNode: any = vnode // work around flow componentVNodeHooks.prepatch(mountedNode, mountedNode) } else { const child = vnode.componentInstance = createComponentInstanceForVnode( vnode, activeInstance ) child.$mount(hydrating ? vnode.elm : undefined, hydrating) } }
The if branch is used to handle keep alive, so we ignore it for the time being. We take the else branch. When the else branch is executed, the following steps will be executed:
const child = vnode.componentInstance = createComponentInstanceForVnode( vnode, activeInstance )
In fact, this function is used to create a component instance. We enter this function to explain the specific process.
Create an instance of the mycom subcomponent
export function createComponentInstanceForVnode ( vnode: any, // we know it's MountedComponentVNode but flow doesn't parent: any, // activeInstance in lifecycle state ): Component { const options: InternalComponentOptions = { _isComponent: true, _parentVnode: vnode, parent } // check inline-template render functions const inlineTemplate = vnode.data.inlineTemplate if (isDef(inlineTemplate)) { options.render = inlineTemplate.render options.staticRenderFns = inlineTemplate.staticRenderFns } return new vnode.componentOptions.Ctor(options) }
The createComponentInstanceForVnode function is relatively simple internally. Let's take a look at what it does. First, define an options configuration object, and then judge if (isdef (inline template)). This condition is not tenable, so it will be implemented:
new vnode.componentOptions.Ctor(options)
vnode. componentOptions. What is the Ctor function? Do you remember that when the component vnode was created, Vue passed through Vue The extends function turns our Ctor configuration object into a constructor for Ctor subcomponents. That is, the function is the Sub constructor.
const Sub = function VueComponent (options) { this._init(options) }
When we execute new Sub(), it is actually equivalent to executing new Vue. Their results are the same, and the purpose is to create a component instance. Only the new Vue function creates the root component instance, while the new Sub constructor creates the child component instance. Why is it equivalent to executing the new Vue function? The reason is that when we define the Sub constructor internally, we mount most of the properties of the Vue constructor to the Sub constructor, which is equivalent to a small Vue. But not exactly, because the main task of Vue constructor is to create root component instances, while the main task of Sub function is to create Sub component instances. Their purposes are different, but because they belong to the same component instance, they have similarities in many places. Let's go to the Sub constructor to see how it creates and processes Sub component instances.
When we the new Sub constructor, we have created a sub component instance object, and many things are mounted on its prototype. The first is to execute vm_1._init method to initialize our sub component instance vm_1.
Data processing of mycom subcomponent
vm_1._init function is inherited from Vue prototype._ In the init function. That is, it is called by the sub component instance_ The init function is essentially_ init() function. The specific code of this function is as follows:
Vue.prototype._init = function (options?: Object) { const vm: Component = this vm._uid = uid++ let startTag, endTag /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && config.performance && mark) { startTag = `vue-perf-start:${vm._uid}` endTag = `vue-perf-end:${vm._uid}` mark(startTag) } vm._isVue = true if (options && options._isComponent) { initInternalComponent(vm, options) } else { vm.$options = mergeOptions( resolveConstructorOptions(vm.constructor), options || {}, vm ) } /* istanbul ignore else */ if (process.env.NODE_ENV !== 'production') { initProxy(vm) } else { vm._renderProxy = vm } // expose real self vm._self = vm initLifecycle(vm) initEvents(vm) initRender(vm) callHook(vm, 'beforeCreate') initInjections(vm) // resolve injections before data/props initState(vm) initProvide(vm) // resolve provide after data/props callHook(vm, 'created') /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && config.performance && mark) { vm._name = formatComponentName(vm, false) mark(endTag) measure(`vue ${vm._name} init`, startTag, endTag) } if (vm.$options.el) { vm.$mount(vm.$options.el) } }
The first is to add a unique identification VM to the newly created sub component instance object_ 1._ uid = 1. Then the vm_1._isVue is set to true.
Here you will encounter a branch:
if (options && options._isComponent) { initInternalComponent(vm, options) }
Obviously, options exist, so options_ What is iscomponent? When we execute the createComponentInstanceForVnode function, there will be such an operation:
const options: InternalComponentOptions = { _isComponent: true, _parentVnode: vnode, parent }
Then pass options into the Sub function_ The isComponent attribute marks that the vnode is a component node. With call_ init function, the function will pass options_ isComponent to determine whether the instance object for initialization is a root component instance object or a child component instance object. Here, vm_1 is a Sub component instance object, so the initInternalComponent function will be used. Let's go inside the function and see how it affects the vm_1 to initialize the subcomponent instance.
export function initInternalComponent (vm: Component, options: InternalComponentOptions) { const opts = vm.$options = Object.create(vm.constructor.options) // doing this because it's faster than dynamic enumeration. const parentVnode = options._parentVnode opts.parent = options.parent opts._parentVnode = parentVnode const vnodeComponentOptions = parentVnode.componentOptions opts.propsData = vnodeComponentOptions.propsData opts._parentListeners = vnodeComponentOptions.listeners opts._renderChildren = vnodeComponentOptions.children opts._componentTag = vnodeComponentOptions.tag if (options.render) { opts.render = options.render opts.staticRenderFns = options.staticRenderFns } }
First give vm_1. Add the $options attribute to the instance object. The prototype of this attribute is Vue options. The next step is to vm_1. Add some necessary attributes to $options. Let's go back to_ The init function is followed by processing data and events:
vm._self = vm initLifecycle(vm) initEvents(vm) initRender(vm) callHook(vm, 'beforeCreate') initInjections(vm) // resolve injections before data/props initState(vm) initProvide(vm) // resolve provide after data/props callHook(vm, 'created')
Finally, make a judgment:
if (vm.$options.el) { vm.$mount(vm.$options.el) }
Because VM_ 1.$options. The node object is not mounted on El, so this branch will not be executed. So it will go back.
Get render function from mycom
Go back to the init function, which is data Init function in hooks. After returning to this function, the following code will be executed:
child.$mount(hydrating ? vnode.elm : undefined, hydrating)
The child here is the vm_1 subcomponent instance object. Because he inherited Vue prototype. So there will be a $mount function. We talked about this function earlier, but in order to better analyze the code, we have to put it out:
Vue.prototype.$mount = function ( el?: string | Element,//There are two types of el passed in. One is string '#app', and the other is an element object, such as document getElementById('app') hydrating?: boolean ): Component { el = el && query(el) if (el === document.body || el === document.documentElement) { } const options = this.$options//this here refers to the vm instance object // resolve template/el and convert to render function if (!options.render) { let template = options.template if (template) { if (typeof template === 'string') {//If the configuration item is of type string. if (template.charAt(0) === '#') {//Here, we only deal with templates in the format of #xxx, which is similar to template:'#app' template = idToTemplate(template)//This function returns the string form of the node inside the template. } } else if (template.nodeType) { //If the template passed in is a node object, the innerHTML in the node object will also be obtained in string form template = template.innerHTML } } else if (el) { template = getOuterHTML(el) } if (template) { const { render, staticRenderFns } = compileToFunctions(template, { outputSourceRange: process.env.NODE_ENV !== 'production', shouldDecodeNewlines, shouldDecodeNewlinesForHref, delimiters: options.delimiters, comments: options.comments }, this) options.render = render options.staticRenderFns = staticRenderFns } } return mount.call(this, el, hydrating) }
Because the code logic is too long, I deleted all the code that I can't get or don't need. Because it is a child component instance, there is no el element.
By analyzing the above code, we can find that Vue's processing of sub components and root components are surprisingly consistent in this regard, but there is no el element here. Therefore, when we do not define the template and render function, we cannot render. It is equivalent to an empty node. Although there will be sub component instances, it has no effect.
In fact, this function is to judge vm_1. Whether the instance object has a render function. If not, compile the tempalt template to generate the render function. Since we did not write the render function in the subcomponent, we will compile the template to generate the render function:
template:'<div><aa></aa></div>'
In vm_1 sub component, we found a strange phenomenon, that is, the template seems to be mixed with something. Yes, it's another sub component, that is, the sub component VM_ A sub component is nested inside 1. Then our next task is to analyze the process from compilation to vnode generation again. Let's continue to look at the code. When compileToFunctions is executed, the template will be compiled into the render function, and then the render function will be mounted to the vm_1.options.render. Prepare for our later vnode generation.
Then call mount.. Call function, mount The essence of call function call is to call:
mountComponent(this, el, hydrating)
So we go into this function. The code of this function is as follows:
export function mountComponent ( vm: Component, el: ?Element, hydrating?: boolean ): Component { vm.$el = el if (!vm.$options.render) { //....... } callHook(vm, 'beforeMount') let updateComponent /* istanbul ignore if */ //This is related to operation performance and can be ignored for the time being if (process.env.NODE_ENV !== 'production' && config.performance && mark) { //...... } else { updateComponent = () => { vm._update(vm._render(), hydrating) } } new Watcher(vm, updateComponent, noop, { before () { if (vm._isMounted && !vm._isDestroyed) { callHook(vm, 'beforeUpdate') } } }, true /* isRenderWatcher */) hydrating = false if (vm.$vnode == null) { vm._isMounted = true callHook(vm, 'mounted') } return vm }
Because the code is very long, a part is deleted. Let's look at the code. Because our component is a sub component, there is no el. vm_1.$el === undefined. Then the vm_1.$options.render makes a judgment because VM_ The render function of 1 exists, so it will not take this branch. Then define the updateComponent function and start executing: the new Watcher() constructor.
Create watcher instance from mycom
new Watcher(vm, updateComponent, noop, { before () { if (vm._isMounted && !vm._isDestroyed) { callHook(vm, 'beforeUpdate') } } }, true)
Like the root component, Vue is a vm_1 subcomponent creates an object that only belongs to its watcher instance. Let's go to this function to talk about the details.
//constructor function this.vm = vm if (isRenderWatcher) { vm._watcher = this } vm._watchers.push(this) // options if (options) { this.deep = !!options.deep this.user = !!options.user this.lazy = !!options.lazy this.before = options.before this.sync = !!options.sync } else { this.deep = this.user = this.lazy = this.sync = false } this.cb = cb this.id = ++uid // uid for batching this.active = true this.dirty = this.lazy // for lazy watchers this.deps = [] this.newDeps = [] this.depIds = new Set() this.newDepIds = new Set() this.expression = process.env.NODE_ENV !== 'production' ? expOrFn.toString() : '' // parse expression for getter if (typeof expOrFn === 'function') { this.getter = expOrFn } else { this.getter = parsePath(expOrFn) if (!this.getter) { this.getter = noop process.env.NODE_ENV !== 'production' && warn( `Failed watching path: "${expOrFn}" ` + 'Watcher only accepts simple dot-delimited paths. ' + 'For full control, use a function instead.', vm ) } } this.value = this.lazy ? undefined : this.get()
When watcher. is created and initialized, it calls the Get() function:
get () { pushTarget(this) let value const vm = this.vm try { value = this.getter.call(vm, vm) } catch (e) { if (this.user) { handleError(e, vm, `getter for watcher "${this.expression}"`) } else { throw e } } finally { if (this.deep) { traverse(value) } popTarget()// this.cleanupDeps() } return value }
This function is a little interesting. First, push tatget (this) is executed. This here points to vm_1.watcher object. We enter the pushTarget function:
export function pushTarget (target: ?Watcher) { targetStack.push(target) Dep.target = target }
See? It puts VM_ The watcher of component 1 is pushed into the targetStack stack, and then the VM_ The watcher of 1 is attached to Dep.target. The purpose of mounting to Dep.target is actually well understood, that is, VM is currently being rendered_ 1 component. At this time, any attribute is accessed, which must be vm_1 instance access. Why push it into the stack? In fact, it records the currently executed component instances, because we may nest many layers of components. How to distinguish which component to execute? In fact, targetStack stack has great auxiliary functions.
Let's go back to the get function and execute:
value = this.getter.call(vm, vm)
This function is updateComponent. We are already familiar with this. Execute inside this function:
vm._update(vm._render(), hydrating)
Generate vnode of mycom
The first is to call VM_ Render() in VM_ The render function executes:
render.call(vm._renderProxy, vm.$createElement)
The final appearance of the render function is:
with(this){return _c('div',[_c('aa')],1)}
The function is called internally_ c function because_ We've talked about the inside of the c function, so here we'll skip the inside and look directly at the vnode generated by it.
{ tag:'div', data:undefined, children:[ { tag:"vue-component-2-aa", data:{ on: undefined, hook: {init: ƒ, prepatch: ƒ, insert: ƒ, destroy: ƒ} }, child:undefined } ] }
In fact, it is very similar to the vnode generated by the root instance. When the vnode is finished, the vm_ is called. 1._ Update() function.
Create a real node object
We enter VM_ 1._ In the update function, because this is the first call, it will call:
vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
We enter vm_1.__patch__ Function. This is the first time to render the sub component, so the oldVnode passed into the patch function does not exist. When oldVnode does not exist. Vue knows that what needs to be rendered at this time is the sub component. So it calls:
isInitialPatch = true createElm(vnode, insertedVnodeQueue/* true*/)
We enter the createElm function:
function createElm ( vnode, insertedVnodeQueue, parentElm, refElm, nested, ownerArray, index ) { vnode.isRootInsert = !nested // for transition enter check 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 ) } } vnode.elm = vnode.ns ? nodeOps.createElementNS(vnode.ns, tag) : nodeOps.createElement(tag, vnode) setScope(vnode) /* istanbul ignore if */ if (__WEEX__) { //....... } else { // createChildren(vnode, children, insertedVnodeQueue) if (isDef(data)) { invokeCreateHooks(vnode, insertedVnodeQueue) } insert(parentElm, vnode.elm, refElm) } if (process.env.NODE_ENV !== 'production' && data && data.pre) { creatingElmInVPre-- } } else if (isTrue(vnode.isComment)) { //...... } else { //..... } }
Let's review vm_1. vnode of component instance:
{ tag:'div', data:undefined, children:[ { tag:"vue-component-2-aa", data:{ on: undefined, hook: {init: ƒ, prepatch: ƒ, insert: ƒ, destroy: ƒ} }, child:undefined } ] }
Because the vnode of the component instance is an element node, the if condition is not tenable. Then by calling:
vnode.elm = vnode.ns ? nodeOps.createElementNS(vnode.ns, tag) : nodeOps.createElement(tag, vnode)
Create a real div element node. After creating the node, start to create a child node object. Go through vnode one by one through the for loop in the createChildren function children. Execute the createElm function once for each child node vnode.
Component node encountered
When the first child node vnode is executed, it is found that the child node is a component vnode. The following is called:
if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) { return }
Pass in the vnode of the component node. Let's look at this function:
function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) { let i = vnode.data if (isDef(i)) { const isReactivated = isDef(vnode.componentInstance) && i.keepAlive if (isDef(i = i.hook) && isDef(i = i.init)) { i(vnode, false /* hydrating */) } if (isDef(vnode.componentInstance)) { initComponent(vnode, insertedVnodeQueue) insert(parentElm, vnode.elm, refElm) if (isTrue(isReactivated)) { reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm) } return true } } }
As before, it is also used to judge whether there is data on the node hook. If yes, it is determined that it is a component node. Obviously, it is, so it will execute:
i(vnode, false /* hydrating */) <===> init(vnode,false)
We go inside the function:
init (vnode: VNodeWithData, hydrating: boolean): ?boolean { //.......... else { const child = vnode.componentInstance = createComponentInstanceForVnode( vnode, activeInstance ) child.$mount(hydrating ? vnode.elm : undefined, hydrating) } }
Create aa subcomponent instance
The createComponentInstanceForVnode function will be called inside this function to create a new sub component instance. Let's go inside this function and have a look:
const options: InternalComponentOptions = { _isComponent: true, _parentVnode: vnode, parent } // check inline-template render functions const inlineTemplate = vnode.data.inlineTemplate if (isDef(inlineTemplate)) { options.render = inlineTemplate.render options.staticRenderFns = inlineTemplate.staticRenderFns } return new vnode.componentOptions.Ctor(options)
The function is very simple internally, that is, call vnode componentOptions. Ctor constructor, i.e. Sub constructor. Create VM through new Sub()_ 2 Sub component instance object.
Aasubcomponent data processing
When the creation is complete, execute vm_2._init() function, for vm_2. Initialize the subcomponent instance object.
Vue.prototype._init = function (options?: Object) { debugger //Define a vm and point to this const vm: Component = this // a uid //Add a unique uid for this vm instance, and each instance has a unique identifier vm._uid = uid++ let startTag, endTag /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && config.performance && mark) { startTag = `vue-perf-start:${vm._uid}` endTag = `vue-perf-end:${vm._uid}` mark(startTag) } //Used to filter vm when listening object changes // a flag to avoid this being observed vm._isVue = true // merge options // _ isComponent is a property that is added as true only when a child component is created internally if (options && options._isComponent) { // optimize internal component instantiation // since dynamic options merging is pretty slow, and none of the // internal component options needs special treatment. initInternalComponent(vm, options) } else { //Merge options vm.$options = mergeOptions( //The resolveConstructorOptions function is defined later //This function is passed VM Constructor is Vue resolveConstructorOptions(vm.constructor), options || {}, vm ) //vm.$options merge two items } /* istanbul ignore else */ if (process.env.NODE_ENV !== 'production') { initProxy(vm) } else { vm._renderProxy = vm } // expose real self vm._self = vm initLifecycle(vm) initEvents(vm) initRender(vm) callHook(vm, 'beforeCreate') initInjections(vm) // resolve injections before data/props initState(vm) initProvide(vm) // resolve provide after data/props callHook(vm, 'created') /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && config.performance && mark) { vm._name = formatComponentName(vm, false) mark(endTag) measure(`vue ${vm._name} init`, startTag, endTag) } if (vm.$options.el) { vm.$mount(vm.$options.el) } }
Initialization, that is, those steps. The first is like vm_2. Instance object mount unique ID vm_2._uid = 2. Then judge whether the initialized component is a sub component. Obviously, execute:
initInternalComponent(vm, options)
This function is to VM_ 2. Mount this attribute on $options. Then execute:
vm._self = vm initLifecycle(vm) initEvents(vm) initRender(vm) callHook(vm, 'beforeCreate') initInjections(vm) // resolve injections before data/props initState(vm) initProvide(vm) // resolve provide after data/props callHook(vm, 'created')
Perform vm_2 initialization of data and events of sub component instances.
Get render function
When the initialization is completed, go back to the init function and call vm_. 2. $mount function.
Vue.prototype.$mount = function ( el?: string | Element,//There are two types of el passed in. One is string '#app', and the other is an element object, such as document getElementById('app') hydrating?: boolean ): Component { el = el && query(el) if (el === document.body || el === document.documentElement) { } const options = this.$options//this here refers to the vm instance object // resolve template/el and convert to render function if (!options.render) { let template = options.template if (template) { if (typeof template === 'string') {//If the configuration item is of type string. if (template.charAt(0) === '#') {//Here, we only deal with templates in the format of #xxx, which is similar to template:'#app' template = idToTemplate(template)//This function returns the string form of the node inside the template. } } else if (template.nodeType) { //If the template passed in is a node object, the innerHTML in the node object will also be obtained in string form template = template.innerHTML } } else if (el) { template = getOuterHTML(el) } if (template) { const { render, staticRenderFns } = compileToFunctions(template, { outputSourceRange: process.env.NODE_ENV !== 'production', shouldDecodeNewlines, shouldDecodeNewlinesForHref, delimiters: options.delimiters, comments: options.comments }, this) options.render = render options.staticRenderFns = staticRenderFns } } return mount.call(this, el, hydrating) }
The main function of this function is to generate the render function through template compilation, and then mount it to VM_ 2.options. On the render property. Then call mount.. Call function mount The essence of call function call is to call:
mountComponent(this, el, hydrating)
So we go into this function. The code of this function is as follows:
export function mountComponent ( vm: Component, el: ?Element, hydrating?: boolean ): Component { vm.$el = el if (!vm.$options.render) { //....... } callHook(vm, 'beforeMount') let updateComponent /* istanbul ignore if */ //This is related to operation performance and can be ignored for the time being if (process.env.NODE_ENV !== 'production' && config.performance && mark) { //...... } else { updateComponent = () => { vm._update(vm._render(), hydrating) } } new Watcher(vm, updateComponent, noop, { before () { if (vm._isMounted && !vm._isDestroyed) { callHook(vm, 'beforeUpdate') } } }, true /* isRenderWatcher */) hydrating = false if (vm.$vnode == null) { vm._isMounted = true callHook(vm, 'mounted') } return vm }
Because the code is very long, a part is deleted. Let's look at the code. Because our component is a sub component, there is no el. vm_2.$el === undefined. Then the vm_2.$options.render makes a judgment because VM_ The render function of 2 exists, so it will not take this branch. Then define the updateComponent function and start executing: the new Watcher() constructor.
Aacreate a watcher instance
Inside the watcher function, a watcher instance object will be created. This object only belongs to the aa component instance object, and:
vm._watcher = this
Mount the watcher to the vm_2_ On the watcher property. From here, we can see that each component instance will have its own watcher, and the watcher instance is equivalent to another unique identifier of the component instance. When vm_2 after the instance object creates its own watcher instance object, it calls watcher.. Get() function, and then VM_ Press the watcher of 2 into the watcher stack, and then change Dep.Target to VM_ watcehr of 2 indicates that VM is currently being rendered_ 2 component instance, it does any access to properties. Then the VM is called_ updateComponent function of 2. The following code is executed in this function:
updateComponent = () => { vm._update(vm._render(), hydrating) }
Generate vnode of aa
First call VM_ The render () function creates the vnode corresponding to the aa component. We entered_ In the render() function, this function mainly calls:
vnode = render.call(vm._renderProxy, vm.$createElement)
Let's look inside the render function.
with(this){return _c('span')}
So it calls_ The vnode generated by the c function should be as follows:
{ tag:'sapn', data:undefined, children:undefined ........ }
When vnode is obtained, fallback is started. Go back to the updateComponent function.
Create a real node object
After executing VM_ The render() function returns vm_2. The vnode corresponding to the component. Then execute vm_update() function, which is used to create real node objects. Because the subcomponent is rendered for the first time, the following code will be executed:
vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
Because__ patch__ It's patch. So let's enter the patch function to see the internal execution process. Because the sub component instance is now created, VM$ El is undefined. This is also a way to judge whether the rendering is a root component or a sub component. When isundefe (oldvnode) is true, Vue knows that it is rendering subcomponents, so it will execute:
isInitialPatch = true createElm(vnode, insertedVnodeQueue)
When the createElm function is called, the vnode of aa component is passed in. Let's look at the internal implementation of this function:
function createElm ( vnode, insertedVnodeQueue, parentElm, refElm, nested, ownerArray, index ) { if (isDef(vnode.elm) && isDef(ownerArray)) { vnode = ownerArray[index] = cloneVNode(vnode) } vnode.isRootInsert = !nested // for transition enter check 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 ) } } vnode.elm = vnode.ns ? nodeOps.createElementNS(vnode.ns, tag) : nodeOps.createElement(tag, vnode) setScope(vnode) /* istanbul ignore if */ if (__WEEX__) { //...... } else { // createChildren(vnode, children, insertedVnodeQueue) if (isDef(data)) { invokeCreateHooks(vnode, insertedVnodeQueue) } insert(parentElm, vnode.elm, refElm) } if (process.env.NODE_ENV !== 'production' && data && data.pre) { } } else if (isTrue(vnode.isComment)) { } else { } }
In fact, the code logic is very simple. First, judge whether this vnode is a component vnode. Obviously not, and then continue:
vnode.elm = vnode.ns ? nodeOps.createElementNS(vnode.ns, tag) : nodeOps.createElement(tag, vnode)
Create the span real node object corresponding to the vnode. The createChildren function is started after creation, because the span node does not have child elements, so executing this function is not what it does, then calling insert(parentElm, vnode.elm, refElm).
Insert into the specified container of the parent component
When executing the insert() function, we need to find out the parameters we passed in:
parentElm:Parent component root element node vnode.elm:Real node to be inserted refElm:Substitute node
Let's look at the specific insertion operations:
function insert (parent, elm, ref) { if (isDef(parent)) { if (isDef(ref)) { if (nodeOps.parentNode(ref) === parent) { nodeOps.insertBefore(parent, elm, ref) } } else { nodeOps.appendChild(parent, elm) } } }
When we really observe the parameters we passed in, we will find that the values of the parameters passed in correspond to the following:
parent:undefined elm:[Object] ref:undefined
We found that the parent was undefined. In other words, there is no way to insert???? In fact, it is not. The operation performed by this insert function is aimed at ordinary nodes. What does it mean? Do we want to insert the node of the child component into the container of the parent component, so as to carry out cross component insertion. This function is used in the same component, for example:
<div> <span>1111</span> </div>
If a component template is like this, the insert function handles inserting the span node into the div or 111 text node into the span node. That is, the insertion inside the component. However, it is not so easy to insert the div node into the container of the parent component, at least not using this function.
So what should be used to insert the root node of the child component into the specified location of the parent component? Let's continue to execute the code first. After the insert function is executed (just walking through the motions, nothing is really executed). Perform function fallback, fallback to the patch function, and then execute:
invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch)
Note that vnode Elm contains the real node object of the root component of the child component aa. Let's look at the invokInsertHook function:
function invokeInsertHook (vnode, queue, initial) { if (isTrue(initial) && isDef(vnode.parent)) { vnode.parent.data.pendingInsert = queue } else { for (let i = 0; i < queue.length;++i){ queue[i].data.hook.insert(queue[i]) } } }
The logic of this function is relatively simple. Let's first look at the values of the passed in parameters:
vnode:[Object] queue:[] //empty initial:true
When we enter the function, we first judge whether the if branch is true or false, because initial === true, the first condition is true, and then judge the second condition: isDeff(vnode.parent) is to judge vnode Whether parnet exists. Does this vnode exist? To know the answer, we need to know vnode Where is the parent attribute defined and mounted? It is obviously defined when creating vnode. Let's look at the process it defines. To generate vnode. You must execute render call. And the internal call of this function_ c function, in execution_ When inside the c function, the createElement function will be executed, and an important thing will be passed in, that is, context. That is, vm_2. Our aa subcomponent instance. Then the vm_2 pass to_ CreateElement function. Execute the new VNode constructor in this function, and perform some initialization in the constructor. At this time, vnode parent === undefined. Then the code goes back, all the way back to vm_2._ In the render() function, this line of code is executed:
vnode.parent = _parentVnode;
_ parentVnode is vm_2 instance object.
Now we know vnode Who is the parent, so you know what to do next:
vnode.parent.data.pendingInsert = queue
This code will vm_2. Set pendingInsert in the data attribute of the instance object to []. Then exit the function and enter the patch function, which directly returns vnode elm. That is, the root node of our aa sub component. Then continue the fallback function, all the way back to Vue_ Update() function, and then:
if (vm.$el) { vm.$el.__vue__ = vm; }
The subcomponent is a VM that exists$ Yes. Each sub component has its own VM$ el. The value points to the root node of the component instance. However, it exists when rendering components for the first time. Only after executing this code can it be:
vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */);
This function returns vnode elm. That is, the root node element.
Back to the point. Then continue to execute the subsequent code, and then go back to the watcher In the get () function, because the updateComponent function executed in this function.
The popTarget() function is then executed, which will vm_2. The watcher of the instance object pops up from our watcher stack, indicating that the rendering of the component has ended, and the access of attributes has nothing to do with it. Then execute watcher cleacDeps. Then go back to the createComponent function, in which we call:
if (isDef(i = i.hook) && isDef(i = i.init)) { i(vnode, false /* hydrating */); }
That's why I fell back here. Then execute:
initComponent(vnode, insertedVnodeQueue);
Let's take a look at what is done inside this function:
function initComponent (vnode, insertedVnodeQueue) { if (isDef(vnode.data.pendingInsert)) { insertedVnodeQueue.push.apply(insertedVnodeQueue, vnode.data.pendingInsert) vnode.data.pendingInsert = null } vnode.elm = vnode.componentInstance.$el if (isPatchable(vnode)) { invokeCreateHooks(vnode, insertedVnodeQueue) setScope(vnode) } else { registerRef(vnode) insertedVnodeQueue.push(vnode) } }
First, let's look at the parameters passed in by the initComponent function. The first is vnode. This vnode is no longer the span root element node we used before, but the aa component node. This node is relative to the nodes in the mycom component template. The vnode of this node data. The pendinginsert property does not exist, and then get vnode componentInstance.$ el. This node is the span node we mentioned above, that is:
vnode.componentInstance.$el === span
Then replace the aa component node with the span node. From this moment on, vnode Elm is the span node.
Although there are only a few above, it has completed a very important thing, that is, the replacement of component nodes. Because when our customized components appear in the template, it is inappropriate and unreasonable to insert our customized components into the element node, because the browser does not know the customized components we write, and what we really want to insert is the component template. Therefore, replace our component node with the root node of the component template, which is:
vnode.elm = vnode.componentInstance.$el
The function of this code.
Then execute the code downward, and then call the invokechreatehooks function. Perform some operations in this function, and then execute:
if (isDef(i.insert)) { insertedVnodeQueue.push(vnode); }
Set vnode. That is, the vnode of the aa component instance is added to the vnode insertion queue. Then go back to the createComponent function and execute:
insert(parentElm, vnode.elm, refElm);
This function is the real insertion operation. Let's take a look at the parameters it passes in:
parentElm: div vnode.elm:span refElm:undefined
The insert function here needs a special explanation. First, let's look at the execution environment of the insert. In fact, the insert performs the insertion of elements in the mycom component. However, at this time, the aa node in the mycom component has been replaced with the root node (span) of the aa component instance. Therefore, aa component node is equivalent to span. You know what I mean. In other words, it used to be:
<div> <aa></aa> </div>
Now it has been replaced, so it is equivalent to:
<div> <span></span> </div>
Insert the span node into the specified location of the mycom component, which is the location of the < AA > component node.
The above is the insertion of our component nodes. In fact, looking back, it is troublesome but easy to understand.
Continue creating real nodes
Now we have replaced the aa component node and mounted it to the root node of our mycom component, and then continue to traverse the remaining child nodes. Let's go back to the createElm function, which is called when the mycom component traverses the child components.
if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) { return }
The above code is our processing of aa component. When the processing is completed, let's look at the subsequent code, and then return to the createChildren function:
for (var i = 0; i < children.length; ++i) { createElm(children[i], insertedVnodeQueue, vnode.elm, null, true, children, i); }
The next step is to continue to traverse the child nodes of the mycom component instance. In fact, through the code, we can find that there is only one child node here, that is, our aa component instance. In fact, if there is an element node behind, we will still call the createElm function, and then create and execute the insert function to mount it to the root node of the mycom component. If the child node is a component node, it will be handled like an aa component node, such as creating an instance, obtaining the render function, and then a watcher, and then replacing and mounting the node.
When there is no child node, it will go back to the createElm function, which is used to create our mycom component node, because in our root component instance, we introduced the modified component:
<div id="app"> <mycom></mycom> <div></div> </div>
The mounting of the modified component cannot be the same as that of an ordinary element node, so the subsequent insert function does not work. Namely:
insert(parentElm, vnode.elm, refElm);
How to mount the generated mycom real DOM node is actually very simple. Some students may have thought of it, which is the same as the aa component. In order to consolidate the mounting process of component nodes, let's explain the specific process again.
After the insert function is executed (it doesn't have any effect in essence, it's just a passing through). Then go back. After fallback to the patch function, start to execute the invokeInsertHook function, which is used to execute the following code:
vnode.parent.data.pendingInsert = queue;
Vnode here refers to the root node of aa component, that is, the vnode of Div. And vnode Parent refers to the vnode of aa component node. Then return to vnode elm. The vnode here refers to the vnode of the root node of the aa component. So, of course, vnode Elm points to the div node.
That is to say, the above operations are still in the root node of the mycom component, and we have not jumped out of the mycom component to the root component instance template. In other words, we are now operating the mycom component instead of the vm_0 root components. Then continue to fall back, all the way back to the watcher In the get () function, the watcher here is the watcher of the root component instance. Because the updateComponent function of the root component is executed in this function.
Then execute the watcher The cleandeos function, and then back out the code, all the way back to the createComponent function. Then execute:
if (isDef(vnode.componentInstance)) { initComponent(vnode, insertedVnodeQueue); insert(parentElm, vnode.elm, refElm); if (isTrue(isReactivated)) { reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm); } return true }
Let's review when we implemented it`createComponent`Function? In fact, when the component goes to cycle through the internal template`vnode`When generating real nodes, execute this function when a component node is encountered.
Let's go back to this function and take a look at the subsequent operations. All is the if judgment, which is true. Then execute the initComponent() function. This function does a very important thing, that is, it executes:
vnode.elm = vnode.componentInstance.$el;
That is, replace our component node with the root node inside the component. Why do you do this? We talked about it earlier. Then it calls:
invokeCreateHooks(vnode, insertedVnodeQueue); setScope(vnode);
This function puts the vnode we need to insert into a queue. Then exit the function, then go back to the createComponent function, and then perform the insert operation. At this point, what we are doing now is in vm_0 is the root component instance environment. The parentElm passed into the insert function is the root node of the root component, vnode Elm is the root node element object div of the mycom component. Although vnode is the vnode of the mycom component node. Normally, vnode Elm is the node element corresponding to the sub component. We replaced it earlier due to various factors. After executing the insert function, the DOM of our mycom component is inserted into the specified location on the root node of the root component instance. Then it goes back all the way to the createChildren function. This function is used to traverse the child nodes inside the root component one by one, and then call the createElm function one by one. The createChildren function is called when creating the root node of the root component instance.
After the createComponent function is executed, it indicates that the component node has been inserted into the corresponding node of the mycom component. Then go back to the createElm function. We find that when we finish processing the component node, we return to the createElm function to perform the return operation, that is, when we finish processing the component node, the current task is completed, and then return to the createChildren function, and then continue to execute:
for (var i = 0; i < children.length; ++i) { createElm(children[i], insertedVnodeQueue, vnode.elm, null, true, children, i); }
Continue to traverse other nodes in the mycom component, because there is only aa one child component in the mycom component, so the createhelpren function completes execution and returns. When this function returns, it means that the child nodes inside the root node of the mycom component are created, and then judge whether the mycom root node has data. There is no root node for the mycom component. So skip the branch. Then execute:
insert(parentElm, vnode.elm, refElm)
Because the value of parentElm passed into the insert() function is undefined. Because the value we want to insert is the root node of the mycom component node, executing the insert() function will not have any effect, just a passing through. Because this is an insert operation on the root node of the child component, the insert at this time has no effect. Then go back to the patch function and execute:
invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch);
When the function is completed, it returns vnode elm. The vnode here is the vnode of the root node element of the mycom component. So vnode Elm is the real root node, that is, the div element node.
Then it goes all the way back to the watcher In the get function, the watcher here refers to the watcher of the mycom component. Why? Because what we call the updateComponent function in this function is that mycom is back to the new Watcher function. Then we look back at what it does, and then we execute:
popTarget(); this.cleanupDeps();
First, vm_1. The watcher function is removed from our watcher stack, which means that the access of any attribute is related to the vm_1 instance has no relationship. The second is to clear all the old dependencies on the watcher.
Insert into the specified container of the component
After these operations are completed, we start to go back to the createComponent function. Now the root component instance, VM, is actually executing this function_ 0 The reason for executing this function is that we have introduced the mycom component into the root component, so this function will be executed during node creation. Well, let's see what the function executes next:
initComponent(vnode, insertedVnodeQueue); insert(parentElm, vnode.elm, refElm);
First, the initComponent function is executed. The parameters passed in are vnode and an empty array. A very important thing is done in this function, that is:
vnode.elm = vnode.componentInstance.$el;
Vnode refers to the instance of mycom component, which is of course vnode Elm should also be a component node, but as we said earlier, the browser does not know the component node we should use, so it assigns the root node element of the component node to vnode elm. Then return. The elm of the vnode node at this time is the root node element of the mycom component. This completes the migration of component nodes. Then execute the following code and return. Then return to the createComponent function. Then execute the insert() function. At this time, the parentElm is the parent node element of the execution location, vnode Elm is the root node element of the mycom component. After executing this function, it will be equivalent to inserting the DOM node inside the mycom component into the specified location of the root component. Why is it the specified location? Because sometimes it is such a structure:
<div> <span> <mycom></mycom> </span> </div>
This component does not exist directly under the root node, so parentElm refers to the parent node at the specified location. According to the example we just mentioned, it is the span element node.
After the insert function is executed, the elements inside mycom are inserted into the root node of our root component. Then start backing off.
Go back to the createChildren function.
Continue creating real nodes
When you go back to the createChildren function, it means that Vue's work is about to be completed. Because this function is vm_0 is called by the root component instance, which is used to create child node elements. When fallback to this function, the following code will be executed:
for (var i = 0; i < children.length; ++i) { createElm(children[i], insertedVnodeQueue, vnode.elm, null, true, children, i); }
Because this function encountered a component node in the middle of execution, and then solved it perfectly, and then continued to execute downward. Before execution, let's take a look at the nodes of the root component:
<div id="app"> <mycom></mycom> <div></div> </div>
The mycom component node has been processed, and then the text node is processed. The processing of text points is also very simple. The createElm function is still called. Because the node is a text node, it will execute:
vnode.elm = nodeOps.createTextNode(vnode.text); insert(parentElm, vnode.elm, refElm);
To create a text node, and then insert the text node into the root node of the root component. After execution, it returns to the createChildren function.
Then the createElm function is executed on Div. In fact, the creation of div is very simple. First, call createElm, which will execute:
vnode.elm = vnode.ns ? nodeOps.createElementNS(vnode.ns, tag) : nodeOps.createElement(tag, vnode);
Because the vnode at this time points to the span node, the span node is created here, that is, vnode Elm is a real span node. Because span node has no child nodes, calling createChildren has no effect. Then execute the insert function, which is used to insert the span node into the specified position in the root node of the root component, and then return. It means that the node at this time has been created and mounted, and then fallback. Go back to the createChildren function. So far, all child nodes in the root component have been processed. Then back out the function to the createElm function, which is vm_0 is called by the root component instance. At this time, the vnode here is even the root instance of the root component.
Next:
if (isDef(data)) { invokeCreateHooks(vnode, insertedVnodeQueue); }
Because it is the root node of the root component, there is a data option, so the invokechreatehook function will be called.
Insert the root component into the page
When the processing is complete, execute:
insert(parentElm, vnode.elm, refElm);
Some students may wonder why there is an insert operation here, which inserts the root instance of our root component into the dom of our page. In fact, we pursue its essence. The above insertion operations are actually DOM nodes existing in memory, but they have not been presented to the page. Insert our root component into the body through the above insert function, so as to complete the task of rendering our component to the page. The parentElm parameter passed into the insert function is actually the body element in the page. Then start the function fallback to the patch function, and then judge whether parentElm exists. The purpose of this function is to remove nodes. Remove node? Remove what node? In fact, when we mount components on the page, two root components will appear on the page. Of course, this is not allowed, so the removeVnodes function will be executed to remove the old root component from the page.
After removal, call the invokeInsertHook function. After the function is executed, start the function fallback to the Watcher function, and then execute:
popTarget(); this.cleanupDeps();
First, remove the watcher of the root component from the stack, indicating that there is access to any properties, regardless of the root component. Second, remove the old deps of the root component. Then it goes all the way back to the Vue constructor.
At this point, our Vue process ends.
Finish scattering flowers * (^ 0 ^) *.