Vue instance source code analysis

Next I will use two examples to explain the process of a VM instance from scratch to render a page. It may be complicated, but I hope you can calm down.

This section only discusses the creation of the instance and the creation and mounting of the virtual node, not the data processing after the instance is generated. The data processing part will be covered in more detail in a future article.

Example 1:

The code for example 1 is as follows:

<body>
    <div id="app">
        <span>{{message}}</span>
    </div>
    <script src="./vue-dev/dist/vue.js"></script>
    <script>
        var vm = new Vue({
            el:'#app'.data: {message:123}})</script>
</body>
Copy the code

This example is simple and does not use nested components. Next, let’s explain the specific process. Each component instance goes through three phases (without data changes) : the data processing phase, the vNode generation phase, and the real node mount generation phase.

Data processing stage:

When we execute new Vue, we execute the Vue constructor:

function Vue (options) { 
  // Options is the configuration item we passed in
  // This is the famous vUE constructor, where all VUE projects start
  if(process.env.NODE_ENV ! = ='production' &&
    !(this instanceof Vue)
    // This is the vm instance, see if new Vue is used
  ) {
    warn('Vue is a constructor and should be called with the `new` keyword')}// This is the instance object generated inside the constructor when we execute new Vue
  this._init(options)
  // From this function go to the _init function we added with initMixin(Vue) initialization
}
Copy the code

Pass our configuration item options to the Vue constructor. At this point the VM instance has been created and we call this._init(options) to initialize our data.

Vm. _init () function is located in the SRC/core/instance/init. Js. The specific code is as follows:

 Vue.prototype._init = function (options? :Object) {
    // Define a VM and point to this
    const vm: Component = this
    // a uid
    // Add a unique UID to the VM instance. 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 VMS when listening for object changes
    // a flag to avoid this being observed
    vm._isVue = true
    // merge options
    // _isComponent is a property that is added to true when internally creating child components
    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(
        / / resolveConstructorOptions function is defined in the rear
        // This function is passed to vm.constructor, which is Vue
        resolveConstructorOptions(vm.constructor),
        options || {},
        vm
      )
      //vm.$options merge the 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)
    }
  }
Copy the code

This code is pretty straightforward, starting with mounting a UID on our VM instance to identify each component instance, and then adding _isVue. Represents a Vue component and then determines if it is a component. Although our root component is also a component, what it means here is a child component. Let’s say you introduce a component into your template. The mergeOptions function is called to merge Vue. Options and the options we passed into one total option and add it to vm.$options. This is followed by data proxy processing. When the processing is complete:

  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')
Copy the code

These do some initialization of events and data, with some hook function executed during the initialization process. The data processing of the component is concentrated here. We will not expand on the specific operations of the data processing here, because the data processing part will be covered separately later. Once the VUE data processing is complete, it’s time to move on to the next stage: vNode generation

Vnode generation phase:

When the data is processed, it executes:

 if (vm.$options.el) {
      vm.$mount(vm.$options.el)
    }
Copy the code

Because the $EL of the root component instance exists, the vm.$mount(vm.$options.el) function is executed. This function is located in:. / SRC/platforms/web/entry – the runtime – with – complier. Js file. The specific code is as follows:

Vue.prototype.$mount = function (el? : string | Element,// We pass in two types of EL, one is a string '#app' and the other is an element object, such as document.getelementById ('app').hydrating? : boolean) :Component {  
  el = el && query(el)// The query function returns an element object. If the el we passed exists, it returns the form of the element. If not, it defaults to a div element

  /* istanbul ignore if */
  if (el === document.body || el === document.documentElement) {
    // This tells us that el cannot be body or HTML. The reason is that it overwrites, which completely overwrites the original template.process.env.NODE_ENV ! = ='production' && warn(
      `Do not mount Vue to <html> or <body> - mount to normal elements instead.`
    )
    return this
  }

  const options = this.$options// This refers to the VM instance object
  // resolve template/el and convert to render function
  if(! options.render) {// If we don't have the render function. Then it goes to the area code.
    let template = options.template // Get the template
    if (template) {
      // If the template configuration item exists,
      if (typeof template === 'string') {// If the configuration item type is a string.
        if (template.charAt(0) = = =The '#') {// Here we only deal with templates of the form # XXX, i.e., template:'#app'
          template = idToTemplate(template)// This function returns a string of nodes inside the template template.
          /* istanbul ignore if */
          // here is the error handling of template
          if(process.env.NODE_ENV ! = ='production' && !template) {
            warn(
              `Template element not found or is empty: ${options.template}`.this)}}}else if (template.nodeType) {
        // If the template we pass in is a node object, we get the innerHTML from that node object, which is, of course, a string
        template = template.innerHTML
      } else {
        // If not, an error is thrown
        if(process.env.NODE_ENV ! = ='production') {
          warn('invalid template option:' + template, this)}return this}}else if (el) {
      // If the template configuration item does not exist, get el.outerhtml as our template. It also returns a string
      template = getOuterHTML(el)
    }
    
    if (template) {
      // This is the processed template. There are two sources of this template. The first is set up by ourselves, and the other is El.outerhtml, which acts as a template
      /* istanbul ignore if */
      if(process.env.NODE_ENV ! = ='production' && config.performance && mark) {
        mark('compile')}// The next code is to compile our template into a jS-described object, the virtual DOM, and then convert the virtual DOM to render.
      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 

      /* istanbul ignore if */
      if(process.env.NODE_ENV ! = ='production' && config.performance && mark) {
        mark('compile end')
        measure(`vue The ${this._name} compile`.'compile'.'compile end')}}}// If we didn't have the render function, we would have gotten the render function by compiling the template,
  // Then call the mount.call() function.
  // If we have our own render function, we can call the mount.call function directly, without compiling it.
  return mount.call(this, el, hydrating)//this -> vm ; El -> element object
}
Copy the code

This function does a lot of things, so let’s break it down. First, we get our EL element node, and then determine if our EL is body/ HTML. Vue does not allow our root node to be body/ HTML because of overwriting issues. We then get the options.render function on the VM instance. Since we didn’t define the render function, we check if there is a template. We didn’t define the template either, so we branch off to else if:

// If the template configuration item does not exist, get el.outerhtml as our template. It also returns a string
      template = getOuterHTML(el)
Copy the code

Since we didn’t define a template template, Vue uses our root node as our template template (in string form). The vm instance object now has the template template, so it executes:

 const { render, staticRenderFns } = compileToFunctions(template, {
        outputSourceRange: process.env.NODE_ENV ! = ='production',
        shouldDecodeNewlines,
        shouldDecodeNewlinesForHref,
        delimiters: options.delimiters,
        comments: options.comments
      }, this)
 // Mount the generated render function to options.render
      options.render = render
      options.staticRenderFns = staticRenderFns 
Copy the code

What does this code do? What it does is turn our tempalte into a render function via Vue’s built-in template compiler. Then mount the function to vm.options.render. The internals of compileToFunctions are more complex if you are interested. A reminder that the Render function is a function, not a VDOM.

When this is done, the mount. Call (this, el, hydrating) function is executed, you might ask, what is the mount function. The first version is our Runtime + Complier version and the other is runtimeOnly version. What is the difference between the two versions? The former allows us to write template, or to add el attributes, because the version has a built-in compiler, while the latter allows us to write render, which does not do template compilation, and the latter mounts the latter. The reason is that although the former does not have the render function, it will still be generated by template compilation. After the generation, it still needs to be mounted, so we will still use the mount function. Ok, let’s look at the code implementation inside this function:

// /web/runtime/index.js
Vue.prototype.$mount = function (el? : string | Element, hydrating? : boolean) :Component {
  el = el && inBrowser ? query(el) : undefined$mount ($runtime-only); $mount ($runtime-only);
  return mountComponent(this, el, hydrating)
}
Copy the code

Once again, get the el element node and execute the mountComponent() function. The code is pretty simple, but let’s look at the mountComponent function. The code for this function is as follows:

//./src/core/instance/lifecycle.js
export function mountComponent (vm: Component, el: ? Element, hydrating? : boolean) :Component {
  vm.$el = el// Mount the el element object to vm.$el, that is, vm.$el is mounted when $mount is performed. Macroscopically, it is mounted after created hook functions, before mounting hook functions.
  if(! vm.$options.render) {Vm. $options.render = vm.$options.render = vm.$options.render = vm.$options.render = vm.$options.render = vm.$options.render = vm.$options.render
    vm.$options.render = createEmptyVNode// If this is true, then we assign vm.$options.render a render function consisting of empty virtual DOM.
    if(process.env.NODE_ENV ! = ='production') {
      /* istanbul ignore if */
      // If you use runtime-only and you write template/el, you will get an error that only accepts the render function. It's a version problem
      if ((vm.$options.template && vm.$options.template.charAt(0)! = =The '#') ||
        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
        )
      }
    }
  }
  // Triggers the berforeMount hook function
  callHook(vm, 'beforeMount')

  let updateComponent
  /* istanbul ignore if */
  // This is performance related and can be ignored for now
  if(process.env.NODE_ENV ! = ='production' && config.performance && mark) {
 		......
  } else {
    // Finally define the updateComponent function
    updateComponent = () = > {
      Vm._update is defined in lifecycleMixin(Vue)
      //vm._render is defined in renderMixin.
      //hydrating:false
      // This function is actually executed in new Watcher(), so we'll just focus on its execution, not where it is triggered.
      vm._update(vm._render(), hydrating)
    }
  }

  // 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
  new Watcher(vm, updateComponent, noop, {
    before () {
      if(vm._isMounted && ! vm._isDestroyed) { callHook(vm,'beforeUpdate')}}},true /* isRenderWatcher */)
  hydrating = false

  // manually mounted instance, call mounted on self
  // mounted is called for render-created child components in its inserted hook
  if (vm.$vnode == null) {
    vm._isMounted = true
    callHook(vm, 'mounted')}return vm
}
Copy the code

Mount the EL node to the vm instance’s $EL property and check if the vm.$options.render function exists. Since the VM instance already gets the Render function by compiling the root node, proceed down. Trigger the beforeMount hook function here.

Then we define the updateComponent function as follows:

  updateComponent = () = > {
      vm._update(vm._render(), hydrating)
    }
Copy the code

This code is very important, so we’ll talk about it a lot later.

The following code defines a Watcher instance object:

  new Watcher(vm, updateComponent, noop, {
    before () {
      if(vm._isMounted && ! vm._isDestroyed) { callHook(vm,'beforeUpdate')}}},true /* isRenderWatcher */)
Copy the code

Why is Watcher defined? Each component has its own unique instance of Watcher. Watcher refers to the component that we want to identify. If our component references a property, the component’s Watcher is added to the property’s DEPS to indicate that the component references the property. It’s tracking relationships. Now let’s go inside the Watcher constructor and parse its code:

//D:\vue-src-jx\vue-dev\vue-dev\src\core\observer\watcher.js
constructor (
    vm: Component,
    expOrFn: string | Function.//updateComponent
    cb: Function,
    options?: ?Object, isRenderWatcher? : boolean) {
    this.vm = vm
    if (isRenderWatcher) {
      vm._watcher = this
    }
    vm._watchers.push(this)
    // options
    if (options) {
      this.deep = !! options.deepthis.user = !! options.userthis.lazy = !! options.lazythis.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(a)this.newDepIds = new Set(a)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()
  }
Copy the code

Here we release only the Constructor function of the Watcher constructor, because the other functions are auxiliary and do not need to be released at once. First, mount the VM instance object to Watcher. Vm, and then determine whether to render Watcher. The watcher in Vue is divided into two kinds: renderWatcher/userWatcher. The property of the deps watcher is the renderWatcher. This is because the component references some properties when generating the VDOM, and the properties put the component’s Watcher in their DEPS. The watcher added when rendering the VDOM is generated is called the render Watcher. The userWatcher refers to the attribute we introduced in the vm.$watch function, which also puts the component’s watcher into the attribute’s deps, except that the watcher is the userWatcher instead of being added at rendering time. Let’s go back to our code because we passed isRenderWatcher === true when we executed new Watcher. So render the watcher, which will be added to vm._watcher. The following code adds various attributes to the watcher of the instance. Here are a few lines to note:

this.before = options.before
Copy the code

This is used to add the following function:

  before () {
      if(vm._isMounted && ! vm._isDestroyed) { callHook(vm,'beforeUpdate')}}Copy the code

Then add a uid for each watcher:

this.id = ++uid
Copy the code

Although this line of code feels trivial, it will play a big role in our component update, which is the component update order.

Then add the following attributes to the Watcher instance:

  this.deps = []
    this.newDeps = []
    this.depIds = new Set(a)this.newDepIds = new Set(a)Copy the code

This is for dependency collection, so we won’t expand it much here. ExpOrFn = expOrFn, expOrFn, expOrFn, expOrFn, expOrFn, expOrFn, expOrFn, expOrFn, expOrFn, expOrFn, expOrFn, expOrFn, expOrFn

 this.value = this.lazy
      ? undefined
      : this.get()
Copy the code

Let’s see what the watcher.get() function does:

  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 "The ${this.expression}"`)}else {
        throw e
      } 
    } finally {
      if (this.deep) {
        traverse(value)
      }
      popTarget()//
      this.cleanupDeps()
    }
    return value
  }
Copy the code

PushTarget (this), where this refers to the watcher instance. What does pushTarget do?

export function pushTarget (target: ? Watcher) {
  targetStack.push(target)
  Dep.target = target
}
Copy the code

This function adds our watcher instance object to the targetStack, and dep. target = target. Why do you do that? This is very important. First, if Vue is creating the root component instance object, it will generate the VNode later, so that when we access a property, it must trigger their get function, so that it knows that the component refers to it. But, yes I’m going to say but, it doesn’t know who quoted it, it’s like when you’re blindfolded and someone in class kisses you, you know you’re kissed, but you don’t know who kissed you. So how does this attribute determine who kissed (pooh, who referenced) it? When a component is rendered, it puts the component’s watcher in the global variable dep. target. When a component has a reference to the component, the attribute depends on whose watcher is stored in the dep. target. Then it puts the component’s watcher in its dePS pocket. Each watcher is notified through a loop when the value of this property changes. How do you know that when a property is referenced, it must be referenced by the component corresponding to the wathcer on dep.target? Well, that’s a good question. It’s a philosophical question…… When our component is about to render, it will put its corresponding watcher on dep. target, so it is occupying dep. target. Now that our component is rendering to generate a VNode, it must reference properties in the process. Which properties do you think will trigger the GET function? Of course, the watcher component reference property, the property is intended to be referenced by other components, but other components have not rendered, how can reference it, only the current root component reference it, of course, the root component reference it. It’s like being blindfolded in class, and there’s only one person in the class, so who do you think kissed you? The answer is pretty obvious. So I hope everyone can find their other half. Ok, so much for pushTarget for now. Let’s go back to the watcher.get() function, get the component instance that corresponds to watcher, and execute:

this.getter.call(vm, vm)
Copy the code

This is equivalent to executing:

updateComponent.call(vm,vm)
Copy the code

Ok, here we go. Let’s review the definition of this function:

  updateComponent = () = > {
      vm._update(vm._render(), hydrating)
    }
Copy the code

To enter this function, we first execute the vm._render() function, which is used to generate the vNode corresponding to the component. So how exactly is it generated? Let’s look at the vm._render code:

  
  Vue.prototype._render = function () :VNode {
    const vm: Component = this
    const { render, _parentVnode } = vm.$options// Get the render function from vm.$options.

    if (_parentVnode) {
      vm.$scopedSlots = normalizeScopedSlots(
        _parentVnode.data.scopedSlots,
        vm.$slots,
        vm.$scopedSlots
      )
    }

    // set parent vnode. this allows render functions to have access
    // to the data on the placeholder node.
    vm.$vnode = _parentVnode
    // render self
    let vnode
    try {
      // There's no need to maintain a stack because all render fns are called
      // separately from one another. Nested component's render fns are called
      // when parent component is patched.
      currentRenderingInstance = vm
      //vm._renderProxy is actually a VM in production. Vnodes are produced by calling the Render function.
      //
      vnode = render.call(vm._renderProxy, vm.$createElement)
    } catch (e) {
      handleError(e, vm, `render`)
      // return error render result,
      // or previous vnode to prevent render error causing blank component
      /* istanbul ignore else */
      if(process.env.NODE_ENV ! = ='production' && vm.$options.renderError) {
        try {
          vnode = vm.$options.renderError.call(vm._renderProxy, vm.$createElement, e)
        } catch (e) {
          handleError(e, vm, `renderError`)
          vnode = vm._vnode
        }
      } else {
        vnode = vm._vnode
      }
    } finally {
      currentRenderingInstance = null
    }
    // if the returned array contains only a single node, allow it
    if (Array.isArray(vnode) && vnode.length === 1) {
      vnode = vnode[0]}// return empty vnode in case the render function errored out
    if(! (vnodeinstanceof VNode)) {
      if(process.env.NODE_ENV ! = ='production' && Array.isArray(vnode)) {
        warn(
          'Multiple root nodes returned from render function. Render function ' +
          'should return a single root node.',
          vm
        )
      }
      vnode = createEmptyVNode()
    }
    // set parent
    vnode.parent = _parentVnode
    return vnode
  }
Copy the code

Let’s start with the last line of code:

 return vnode
Copy the code

You know what I mean. $options: render/_parentVnode is not executed because the vm instance does not have _parentVnode. Then execute:

vnode = render.call(vm._renderProxy, vm.$createElement)
Copy the code

Because the RENDER function of the VM instance object is not passed in by us, but obtained by compiling the template, so the render function is an internal function of Vue, so it can not be directly obtained, but this does not affect our next explanation.

We can get its internal code through code debugging:

(function anonymous(
) {
with(this){return _c('div', {attrs: {"id":"app"}},[_c('span',[_v(_s(message))])])}
})
Copy the code

We see that it ends up calling the _c method, so let’s see what the vm._c method is.

//D:\vue-src-jx\vue-dev\vue-dev\src\core\instance\render.js
vm._c = (a, b, c, d) = > createElement(vm, a, b, c, d, false)
Copy the code

In the code above we can see what render looks like after compiling. Let’s see what a/ B /c/d stands for, in that order: tag name, data, childrend, normalizationType. Vue calls _c twice, but with different arguments. The first one takes three arguments and the second one takes two. So it’s not clear what data stands for, either as an attribute object of that node or as an array of children of that node. Either way, this is done:

// This is the code in createElement
if (Array.isArray(data) || isPrimitive(data)) {
    normalizationType = children
    children = data
    data = undefined
  }
Copy the code

Then it executes:

  return _createElement(context, tag, data, children, normalizationType)
Copy the code

Before we move on, let’s go back to the code above:

_c('div', {attrs: {"id":"app"}},[_c('span',[_v(_s(message))])])
Copy the code

As you can see from the above code, Vue calls _c twice in total. Each call can be quite complicated, but to give you an idea of what is going on, I will cover both calls. The second _c function is called first in the order in which it is called (actually _s/_v is essentially called first, but that’s not the point), and the arguments passed in are matched one by one:

a:'span'
b:[VNode]// This vNode is actually a generated text node, so it is omitted because it is a bit troublesome to talk about text nodes further
c:undefined
d:undefined
Copy the code

Then we go inside the _c function, which calls createElement(vm, a, b, c, d, false). Let’s look at the actual code inside this function:

export function createElement (context: Component, tag: any, data: any, children: any, normalizationType: any, alwaysNormalize: boolean)
Copy the code

Here we show the arguments it passes first, and the internal code will be shown later. Let’s map the arguments one by one:

context:vm
tag:'span'
data:[VNode]
children:undefined
normalizationType:undefined
alwaysNormalize:false
Copy the code

We then walk through the code inside the function line by line, starting with:

  if (Array.isArray(data) || isPrimitive(data)) {
    normalizationType = children
    children = data
    data = undefined
  }
Copy the code

After this operation, the variable becomes:

context:vm
tag:'span'
data:undefined
children:[VNode]
normalizationType:undefined
alwaysNormalize:false
Copy the code

This is equivalent to the parameter value moving backwards, right? Right, back. Then execute:

  if (isTrue(alwaysNormalize)) {
    normalizationType = ALWAYS_NORMALIZE
  }
Copy the code

It is obvious that the branch cannot go, so execute:

return _createElement(context, tag, data, children, normalizationType)
Copy the code

Let’s look at the _createElement() function. As above, we’ll show the argument first:

export function createElement (context: Component, tag: any, data: any, children: any, normalizationType: any, alwaysNormalize: boolean)
Copy the code

Then we put the parameters into one-to-one correspondence:

context:vm
tag:'span'
data:undefined
children:[VNode]
normalizationType:undefined
alwaysNormalize:false
Copy the code

Then we’ll go through the code line by line, first executing:

  if(isDef(data) && isDef((data: any).__ob__)) { process.env.NODE_ENV ! = ='production' && warn(
      `Avoid using observed data object as vnode data: The ${JSON.stringify(data)}\n` +
      'Always create fresh vnode data objects in each render! ',
      context
    )
    return createEmptyVNode()
  }
Copy the code

Since our data doesn’t exist, we can’t go to this branch, and then:

 if (isDef(data) && isDef(data.is)) {
    tag = data.is
  }
Copy the code

We can’t get there. Let’s go on:

  if (isDef(data) && isDef(data.is)) {
    tag = data.is
  }
Copy the code

Can’t go, we continue to watch:

 if(! tag) {// in case of component :is set to falsy value
    return createEmptyVNode()
  }
Copy the code

Can’t go, we continue to watch:

  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
      )
    }
  }
Copy the code

Can’t go, we continue to watch:

 if (Array.isArray(children) &&
    typeof children[0= = ='function'
  ) {
    data = data || {}
    data.scopedSlots = { default: children[0] }
    children.length = 0
  }
Copy the code

Ok, this code also does not go, let’s continue:

  if (normalizationType === ALWAYS_NORMALIZE) {
    children = normalizeChildren(children)
  } else if (normalizationType === SIMPLE_NORMALIZE) {
    children = simpleNormalizeChildren(children)
  }
Copy the code

This code also doesn’t go anywhere, which is actually flattening the children array of the VM instance. Then proceed:

  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))) {
      // component
      debugger
      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)
  }
Copy the code

First we define two variables vnode/ns, then determine if our tag is of type string, obviously yes, and then execute:

 let Ctor
    ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag)
Copy the code

Because we haven’t generated a VNode yet. So there is no $vnode attribute, so config.getTagNamespace is called. This function is very simple, and its code looks like this:

  function getTagNamespace (tag) {
    if (isSVG(tag)) {
      return 'svg'
    }
    // basic support for MathML
    // note it doesn't support other MathML elements being component roots
    if (tag === 'math') {
      return 'math'}}Copy the code

So ns is undefined. Then execute:

config.isReservedTag(tag)
Copy the code

This function checks whether the tag is a reserved tag, that is, whether the tag is an HTML tag, and that’s it. Let’s continue with the code:

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
        )
      }
Copy the code

The branch is not accessible, so the following code is executed:

 vnode = new VNode(
        config.parsePlatformTagName(tag), data, children,
        undefined.undefined, context
      )
Copy the code

Ok, we are ready to create a vNode.

Before creating a VNode, we need to understand whose vNode we are creating. Vue executes the second _c() function, indicating that it is creating a VNode of the child element node SPAN. The VNode constructor doesn’t do anything special. Its main purpose is to create an object that has properties in it.

  constructor (tag? : string, data? : VNodeData, children? :?Array<VNode>, text? : string, elm? : Node, context? : Component, componentOptions? : VNodeComponentOptions, asyncFactory? :Function
  ) {
      /* Label name of the current node */
      this.tag = tag
      /* The object corresponding to the current node, which contains specific data information, is a VNodeData type, you can refer to the VNodeData type data information */
      this.data = data
      /* The child of the current node is an array */
      this.children = children
      /* The text of the current node */
      this.text = text
      /* The actual DOM node corresponding to the current virtual node */
      this.elm = elm
      /* Namespace of the current node */
      this.ns = undefined
      /* compile scope */
      this.context = context
      /* Functional component scope */
      this.functionalContext = undefined
      /* The key attribute of the node, which is used as the node's identifier, is used to optimize */
      this.key = data && data.key
      /* The component's option */
      this.componentOptions = componentOptions
      /* The instance of the component corresponding to the current node */
      this.componentInstance = undefined
      /* The parent of the current node */
      this.parent = undefined
      InnerHTML is true, and textContent is false*/
      this.raw = false
      /* Static node flag */
      this.isStatic = false
      /* Whether to insert as the heel node */
      this.isRootInsert = true
      /* Is a comment node */
      this.isComment = false
      /* Whether the node is a clone */
      this.isCloned = false
      /* Whether there is a v-once instruction */
      this.isOnce = false
    this.asyncFactory = asyncFactory
    this.asyncMeta = undefined
    this.isAsyncPlaceholder = false
  }
Copy the code

This is just a part of the code to initialize our vNode instance. Let’s take a look at what a completed VNode looks like:

//span 的 vnode
{
asyncFactory: undefined
asyncMeta: undefined
children: [VNode]
componentInstance: undefined
componentOptions: undefined
context: Vue {_uid: 0._isVue: true.$options: {... },_renderProxy: Proxy._self: the Vue,... }data: undefined
elm: undefined
fnContext: undefined
fnOptions: undefined
fnScopeId: undefined
isAsyncPlaceholder: false
isCloned: false
isComment: false
isOnce: false
isRootInsert: true
isStatic: false
key: undefined
ns: undefined
parent: undefined
raw: false
tag: "span"
text: undefined
child: undefined
__proto__: Object
}
Copy the code

Here’s what it looks like when it’s expanded, but for convenience, let’s see what it looks like when it’s not expanded:

VNode {tag: "span".data: undefined.children: Array(1), text: undefined.elm: undefined,... }Copy the code

We can see from the unexpanded VNode: First, a VNode is just an object, and this object is used to describe the SPAN node. A vnode is our object description of a SPAN node.

Ok, when the VM instance generates a VNode, the code rolls back. Let’s go back to the _craeteElement function. Next, execute:

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()
  }
Copy the code

We can see that this code only executes the else if branch, but the other branches within this branch do not execute, that is, this code does nothing to vNode and returns directly. It then falls back to the createElement function, because it’s the last line of code to fall back again. And then it goes back to where does it stop? The answer is to go back to this code and stop:

(function anonymous(
) {
with(this){return _c('div', {attrs: {"id":"app"}},[_c('span',[_v(_s(message))])])}
})
Copy the code

Now that the second _c() function has been executed, it is time to execute the first _c() function. Ok, so we’re going to analyze the first _c() execution, and we’re going to execute the first function a little faster because of the second one. First go inside the _c() function again, and then execute the following code:

vm._c = (a, b, c, d) = > createElement(vm, a, b, c, d, false)
Copy the code

Before diving inside the createElement function, let’s do a one-to-one mapping of the parameters:

context:vm
tag:'div'
data: {attrs: {"id":"app"}}
children: VNode {tag: "span".data: undefined.children: Array(1), text: undefined. }normalizationType:undefined
alwaysNormalize:false
Copy the code

Then go to the following code:

  if (Array.isArray(data) || isPrimitive(data)) {
    normalizationType = children
    children = data
    data = undefined
  }
Copy the code

In fact, the above code will not execute, the first VM data is not an array, and then enter isPrimitive function. Let’s see what this function does:

  function isPrimitive (value) {
    return (
      typeof value === 'string' ||
      typeof value === 'number' ||
      // $flow-disable-line
      typeof value === 'symbol' ||
      typeof value === 'boolean')}Copy the code

Is to determine whether the data passed in is equal to these values, which is obviously not, so it will not be executed.

Then execute:

_createElement(context, tag, data, children, normalizationType)
Copy the code

Again, let’s look at the one-to-one correspondence of parameters:

context:vm
tag:'div'
data: {attrs: {"id":"app"}}
children:VNode {tag: "span".data: undefined.children: Array(1), text: undefined. }normalizationType:undefined
Copy the code

Now let’s go to the _createElement function. None of the following code will execute:

 if(isDef(data) && isDef((data: any).__ob__)) { process.env.NODE_ENV ! = ='production' && warn(
      `Avoid using observed data object as vnode data: The ${JSON.stringify(data)}\n` +
      '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)
  }
Copy the code

Then it starts to determine the type of tag, and then it does the same thing we did with the span vNode, which ends up with this code:

vnode = new VNode(
        config.parsePlatformTagName(tag), data, children,
        undefined.undefined, context
      )
Copy the code

Ok, let’s carry out the one-to-one correspondence of parameters:

tag:'div'
data: {attrs: {"id":"app"}}
children:children:VNode {tag: "span".data: undefined.children: Array(1), text: undefined. }text:undefined
elm:undefined
context:vm
Copy the code

We then enter the VNode constructor. The process is the same as the SPAN tag, and here is the result:

//div 的 vnode
VNode {tag: "div".data: {... },children: Array(1), text: undefined.elm: undefined,... }Copy the code

After the expansion:

{
asyncFactory: undefined
asyncMeta: undefined
children: [VNode]
componentInstance: undefined
componentOptions: undefined
context: Vue {_uid: 0._isVue: true.$options: {... },_renderProxy: Proxy._self: the Vue,... }data: {attrs: {... }}elm: undefined
fnContext: undefined
fnOptions: undefined
fnScopeId: undefined
isAsyncPlaceholder: false
isCloned: false
isComment: false
isOnce: false
isRootInsert: true
isStatic: false
key: undefined
ns: undefined
parent: undefined
raw: false
tag: "div"
text: undefined
child: (...).__proto__: Object
}
Copy the code

Then go back to the _createElement function and continue with the following code:

  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()
  }
Copy the code

Unlike the SPAN vNode, it executes this code:

if (isDef(data)) registerDeepBindings(data)
Copy the code

We go into the registerDeepBindings function. The specific code is as follows:

function registerDeepBindings (data) {
  if (isObject(data.style)) {
    traverse(data.style)
  }
  if (isObject(data.class)) {
    traverse(data.class)
  }
}
Copy the code

This function mainly handles style/class, so id is ignored. So keep going back. All the way back to the vue. _render function because we did it in that function:

vnode = render.call(vm._renderProxy, vm.$createElement)
Copy the code

So it’s going to keep falling back into this function.

Back to the vm._render function, let’s continue with the code that executes later, when finished:

vnode = render.call(vm._renderProxy, vm.$createElement)
Copy the code

Then it executes:

currentRenderingInstance = null
Copy the code

Then execute the subsequent code, in fact, most of the subsequent code will not go, such as:

   // if the returned array contains only a single node, allow it
    if (Array.isArray(vnode) && vnode.length === 1) {
      vnode = vnode[0]}// return empty vnode in case the render function errored out
    if(! (vnodeinstanceof VNode)) {
      if(process.env.NODE_ENV ! = ='production' && Array.isArray(vnode)) {
        warn(
          'Multiple root nodes returned from render function. Render function ' +
          'should return a single root node.',
          vm
        )
      }
      vnode = createEmptyVNode()
    }
Copy the code

It then executes:

 vnode.parent = _parentVnode;
Copy the code

Mount _parentVnode to vnode.parnet. Undefined because the root vnode does not have a parent node. The vnode is returned after the execution. When it returns, it returns to a function that looks like this:

 updateComponent = function () {
        vm._update(vm._render(), hydrating);
};
Copy the code

The vnode return is done in vm._render(), which confirms our previous statement that the vm._render() function is used to create a vnode. Next comes the execution of the vm._update function. Let’s look at the code inside:

//D:\vue-src-jx\vue-dev\vue-dev\src\core\instance\lifecycle.js  
Vue.prototype._update = function (vnode: VNode, hydrating? : boolean) {
    const vm: Component = this// Here is the VM instance
    const prevEl = vm.$el
    const prevVnode = vm._vnode//undefined
    const restoreActiveInstance = setActiveInstance(vm)
    vm._vnode = vnode
    // Vue.prototype.__patch__ is injected in entry points
    // based on the rendering backend used.
    if(! prevVnode) {// initial render
      vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
        $el is the real DOM node object. Vnode is the virtual DOM generated after rendering
    } else {
      // Updates render data
      vm.$el = vm.__patch__(prevVnode, vnode)
    }
    restoreActiveInstance()
    // update __vue__ reference 
    if (prevEl) {
      prevEl.__vue__ = null
    }
    if (vm.$el) {
      vm.$el.__vue__ = vm
    }
    // if parent is an HOC, update its $el as well
    if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {
      vm.$parent.$el = vm.$el
    }
    // updated hook is called by the scheduler to ensure that children are
    // updated in a parent's updated hook.
  }
Copy the code

The first half of this function is an assignment to some variables, so prevVnode is undefined since we are rendering for the first time. So it goes this way:

if(! prevVnode) {// initial render
      vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
        $el is the real DOM node object. Vnode is the virtual DOM generated after rendering
    }
Copy the code

This branch is for instance objects that are first rendered. The first rendering will perform:

 vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
Copy the code

Let’s look at the internal implementation of this function, because in essence the vm.__patch__ call is actually calling patch. So let’s look at the internal implementation of patch function:

//D:\vue-src-jx\vue-dev\vue-dev\src\core\vdom\patch.js
return function patch (oldVnode, vnode, hydrating, removeOnly) {
   
    if (isUndef(vnode)) {// Delete logic
      if (isDef(oldVnode)) invokeDestroyHook(oldVnode)
      return
    }

    let isInitialPatch = false
    const insertedVnodeQueue = []
    if (isUndef(oldVnode)) {
      // empty mount (likely as component), create new root element
      isInitialPatch = true
      createElm(vnode, insertedVnodeQueue)
    } else {
      const isRealElement = isDef(oldVnode.nodeType)//isRealElement: true
      if(! isRealElement && sameVnode(oldVnode, vnode)) {// patch existing root node
        patchVnode(oldVnode, vnode, insertedVnodeQueue, null.null, removeOnly)
      } else {
        if (isRealElement) {
          // mounting to a real element
          // check if this is server-rendered content and if we can perform
          // a successful hydration.
          if (oldVnode.nodeType === 1 && oldVnode.hasAttribute(SSR_ATTR)) {// It is not server-side rendering, so this logic cannot be used
            oldVnode.removeAttribute(SSR_ATTR)
            hydrating = true
          }
          if (isTrue(hydrating)) {//false
            if (hydrate(oldVnode, vnode, insertedVnodeQueue)) {
              invokeInsertHook(vnode, insertedVnodeQueue, true)
              return oldVnode
            } else if(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
          // This code means to convert our real DOM to VDOM.
          oldVnode = emptyNodeAt(oldVnode)
        }

        // replacing existing element
        const oldElm = oldVnode.elm // This is the real DOM. Although we converted the real DOM into the virtual DOM, we still kept the original dom
      
        const parentElm = nodeOps.parentNode(oldElm)// This is the actual parent node.

        // create new node
        createElm(
          vnode,
          insertedVnodeQueue,
          // 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)
        )

        // 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
          }
        }

        // destroy old node
        if (isDef(parentElm)) {
          removeVnodes([oldVnode], 0.0)}else if (isDef(oldVnode.tag)) {
          invokeDestroyHook(oldVnode)
        }
      }
    }

    invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch)
    return vnode.elm
  }
Copy the code

Before parsing the function code, let’s take a brief look at the parameters. Vm.$el is the root element node of the root component, which is the div. It is a real node, not a VNode. The second parameter is the vNode of the root component. Let’s go inside the function and see how it is executed.

    if (isUndef(vnode)) {// Delete logic
      if (isDef(oldVnode)) invokeDestroyHook(oldVnode)
      return
    }
Copy the code

First check if the vnode exists. If the Vue does not exist, decide that you want to delete the node, so we can’t get to the branch. Then execute down:

if (isUndef(oldVnode)) {
    ......
}
Copy the code

This code is not available because the oldVnode for the VM instance exists. Then proceed down:

var isRealElement = isDef(oldVnode.nodeType);
        if(! isRealElement && sameVnode(oldVnode, vnode)) {// patch existing root node
          patchVnode(oldVnode, vnode, insertedVnodeQueue, null.null, removeOnly);
        }
Copy the code

Since oldVnode is a real node, isRealElement is true, so go directly to the else branch:

 if (isRealElement) {
          // mounting to a real element
          // check if this is server-rendered content and if we can perform
          // a successful hydration.
          if (oldVnode.nodeType === 1 && oldVnode.hasAttribute(SSR_ATTR)) {// It is not server-side rendering, so this logic cannot be used
            oldVnode.removeAttribute(SSR_ATTR)
            hydrating = true
          }
          if (isTrue(hydrating)) {//false
            if (hydrate(oldVnode, vnode, insertedVnodeQueue)) {
              invokeInsertHook(vnode, insertedVnodeQueue, true)
              return oldVnode
            } else if(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
          // This code means to convert our real DOM to VDOM.
          oldVnode = emptyNodeAt(oldVnode)
        }
Copy the code

The above code is the first half of the else branch, and Vue evaluates isRealElement first. This is obviously true, so it executes downwards. None of the following code executes:

  if (oldVnode.nodeType === 1 && oldVnode.hasAttribute(SSR_ATTR)) {// It is not server-side rendering, so this logic cannot be used
            oldVnode.removeAttribute(SSR_ATTR)
            hydrating = true
          }
          if (isTrue(hydrating)) {//false
            if (hydrate(oldVnode, vnode, insertedVnodeQueue)) {
              invokeInsertHook(vnode, insertedVnodeQueue, true)
              return oldVnode
            } else if(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.')}}Copy the code

Then execute this code:

  oldVnode = emptyNodeAt(oldVnode)
Copy the code

At this point you might wonder, what is if(isRealElement) used for? In fact, this involves the rendering principle of patch. We can think about the circumstances under which patch will be implemented. There are two answers: first, when rendering the component for the first time. Second, when data updates are triggered again render. That means twice. One thing Vue must do during patch rendering is to compare old and new VDOM. That is, the new one will be compared to the old one, and the best solution for rendering will be found. However, components do not have old VNodes when they are first rendered. Since the old dom and the old DOM are the same for the first rendering, an old VNode will be created to make the function run compatible, and then the patch comparison will be performed.

Now that we have covered the reasons for the creation of old nodes, we can proceed:

const oldElm = oldVnode.elm // This is the real DOM. Although we converted the real DOM to the virtual DOM, we still kept the original DOM
const parentElm = nodeOps.parentNode(oldElm)// This is the actual parent node.
Copy the code

The following code execution is critical. This is because the component is about to enter another phase: generating the actual node mount phase.

Generate the actual node mount phase

At the stage of generating and mounting the real node, Vue mainly performs the createElm function. The specific code is as follows:

function createElm (vnode, insertedVnodeQueue, parentElm, refElm, nested, ownerArray, index) {
    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) } 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
          )
        }
      }

      //nodeOps is a wrapper object of the native DOM API. Nodeops.createelement (tag) is actually document.createElement(tag).
      //vnode.elm is now the actual node object created by nodeops.createElement ().
      vnode.elm = vnode.ns
        ? nodeOps.createElementNS(vnode.ns, tag)
        : nodeOps.createElement(tag, vnode)
      setScope(vnode)

      /* istanbul ignore if */
      if (__WEEX__) {
        //WEEX render
        // 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 {
        //
        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)) {
      vnode.elm = nodeOps.createComment(vnode.text)
      insert(parentElm, vnode.elm, refElm)
    } else {
      vnode.elm = nodeOps.createTextNode(vnode.text)
      insert(parentElm, vnode.elm, refElm)
    }
  }

Copy the code

This function creates a corresponding real node using vNode. There are four parts to creating a real node:

Part one: Determine whether the node is a component node

 if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {
      return
    }   
Copy the code

Part two: Create a vNode for a real node

vnode.elm = vnode.ns
        ? nodeOps.createElementNS(vnode.ns, tag)
        : nodeOps.createElement(tag, vnode)
Copy the code

Part three: Creating real child nodes

createChildren(vnode, children, insertedVnodeQueue)
Copy the code

CreateElm is also executed in the createChildren function:

 createElm(children[i], insertedVnodeQueue, vnode.elm, null.true, children, i)
Copy the code

When we call createElm, we pass the parent vnode.elm of the current node to createElm. When we create the child node, the parentElm of the child node is the vnode.elm element.

Part four: Insert the real node into the parent node.

insert(parentElm, vnode.elm, refElm)
Copy the code

We explain each part in detail, and then analyze the whole wave. The first part is to determine whether the vNode we passed is a component vNode by calling the createComponent function. If it is a component VNode. Go back and create a component instance, then execute the corresponding component creation code. Obviously, this is not a component vNode. The second part is to create the corresponding node of our current vNode, and then mount it to vNode.elm on the current node. This mount is useful, as shown later in the insert. This means that in part 2, the corresponding node for our vNode has already been created. The third step is to create the child node, which is an independent node with its own VNode. So createElm is still called in the createChildren function. The fourth step is to insert the current node we created into the parent node of the parent node. There are two basic things that we need to know when we insert, and that is who we want to insert and where we want to insert it. First, we’ll tackle the first basic point, which is who to insert. It’s easy to insert the actual node created by the current VNode. The second basic point, where do we plug in, plug in the parent node, so how do we get the parent node? It’s really simple. As an example, we have a template:

<div>
    <span></span>
</div>
Copy the code

If we want to get these two nodes, we need to call createElm twice, the first time creating the parent div. The second call creates the child node span. After the parent node is created, we start to create the child node. How do we insert the child node into the parent div when the child node is created? Vue does this by passing a third argument to createElm before the second node is created. This parameter is the parent node’s vNode. elm, the newly created div element node. This node is passed in as the parent of the child, and is inserted when the child is created. Since the div node has no parent element, createElm is not passed as a third argument the first time it is called, so it defaults to undefined.

Ok, the above four parts are finished, we now comb again, we take the above template as an example. The generated vNode is as follows:

// Easy version
{
    tag:'div'.data:undefined.children: [{tag:'span'.data:undefined.children:undefined. }],... }Copy the code

Let’s go through the above four steps using the template above.

Nodeops.createelement (tag, vNode) code creates a real div element node. Mount the node to the div’s vnode.elm. Now we have created our first div element. CreateChildren (vNode, Children, insertedVnodeQueue) is then executed. When this function is executed, it loops through createElm.

So let’s go into this function for the second time. On the second call, when the SPAN is generated, we pass the parent element vnode.elm as the third parameter to createElm, so that when the SPAN is created, the parent node will be inserted.

If not true then execute:

vnode.elm = vnode.ns
        ? nodeOps.createElementNS(vnode.ns, tag)
        : nodeOps.createElement(tag, vnode)
Copy the code

The tag is already span. So you create a real node of a span. Then mount the created SPAN node to the ELM property of the SPAN’s vNode and execute createChildren. Since our SPAN node has no children, we perform insert. Since we have passed div’s vnode.elm as parentElm, the newly created SPAN node will be inserted into the div node. Ok, so after the insertion, the rollback is done, back into the createChildren function, and since there are no other children, the rollback continues. We then go back to the first call to createElm and do the insert of the div element, where parentElm is the parent node.

Now that we have the framework for generating and mounting nodes, let’s go back to our original example and walk through the createElm function.

Let’s first review what our example looks like, using the following template:

<div id="app">
      <span>{{message}}</span>
</div>
Copy the code

The rendering function looks like this:

_c('div', {attrs: {"id":"app"}},[_c('span',[_v(_s(message))])])
Copy the code

Now that we know our template and render function, let’s take a look at the createElm function.

The first step is to determine whether the node is a component node, which is clearly not a component node. Then we define some variables and assign values to them to determine the condition. Then we check if our tag exists, which it obviously does. Then we call:

vnode.elm = vnode.ns
        ? nodeOps.createElementNS(vnode.ns, tag)
        : nodeOps.createElement(tag, vnode)
Copy the code

Our first element, div, is created, and we assign it to the elm attribute of the vNode to which the div corresponds. Then we execute:

createChildren(vnode, children, insertedVnodeQueue)
Copy the code

The createChildren function creates child nodes in a loop:

 for (let i = 0; i < children.length; ++i) {
        // This is deep traversal and node creation.
        createElm(children[i], insertedVnodeQueue, vnode.elm, null.true, children, i)
      }
Copy the code

When createElm is executed to create a SPAN node, pass the div real node as parentElm. Similarly, the createChildern function is called to create a span node, and createElm is also called to create a text node. At this point, we pass the span node as parentElm into createElm. In createElm, we create the text node first. Since the text node has no children, we perform insert because parentElm is a SPAN node. So we insert the text into that node, and then we back it up, back into the createChildren function. Because the SPAN node has no other children, we insert it into the div node because the parent of the span is the div node we passed in. The div node has no other children, so insert. At this point, the parentElm of the div is the body node, so insert it into the body. This means that we have changed the DOM structure of the document, causing the browser to rerender. At this point, our mount phase of generating real nodes is over, and we see that the nodes are created from parent to child, and the nodes are mounted from child to parent. The node is created and mounted, but there is a problem. When debugging the code, you will find two elements in the page:

 <div id="app">
        <span>{{message}}</span>
 </div>
 <div id="app">
        <span>123</span>
 </div>
Copy the code

Vue is mounted, but it does not remove the original node from the page, so all Vue needs to do is remove the original node from the page, so it calls:

 if (isDef(parentElm)) {
          removeVnodes([oldVnode], 0.0)}Copy the code

The removeVnodes function removes the existing nodes. When this function is finished, it executes:

   invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch)
Copy the code

The code for this function is as follows:

  function invokeInsertHook (vnode, queue, initial) {
    // delay insert hooks for component root nodes, invoke them after the
    // element is really inserted
    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])
      }
    }
  }
Copy the code

Because our test case does not trigger this function, we will explain this later when we talk about nested components.

Then go back to the patch function and execute:

return vnode.elm
Copy the code

_update(), and the rest of the code does some column mounting, and then it rolls back, all the way back to watcher.get(), because that’s where we called updateComponent. Then execute the code in Watcher. Get, first checking this.deep. This is false at the beginning. Then we call popTarget to indicate that our current component is done, and we push that component’s Watcher off the stack. Once properties are accessed, it’s time for the next watcher to do so. Then execute watcher.cleanupdeps (). Why we do this, we’ll talk about it later. Omitted here. It then continues to back up to the mountComponent function and executes:

 if (vm.$vnode == null) {
      vm._isMounted = true;
      callHook(vm, 'mounted');
    }
Copy the code

Since the vm.$vnode does not yet exist, the mounted hook function is executed to mark the component instance as mounted before execution. When the above code has been executed, even if it has actually been executed, it will then revert all the way back to the code in the

So far, we have finished this simple case, you are to jun can understand.

Earlier we talked about why we need to execute cleanDeps. The code for this function is as follows:

  cleanupDeps () {
    let i = this.deps.length// Get the number of deps stored in watcher
    while (i--) {
      const dep = this.deps[i]// Get the corresponding deps
      if (!this.newDepIds.has(dep.id)) {// Check whether the old DEps exists in the new DEPS. There will be a new DEPS when rerendering is triggered. So compare the new to the old
        dep.removeSub(this)// If the new deps has the same DEP, the component is still dependent on the deps, so keep it. If the new deps does not have the same DEP, the component is no longer dependent on the DEps. The DEP in DEPS has the new DEPS, but the DEP in the new DEPS does not necessarily have the DEPS.}}let tmp = this.depIds
    this.depIds = this.newDepIds
    this.newDepIds = tmp
    this.newDepIds.clear()
      // Just look at the following section
    tmp = this.deps
    this.deps = this.newDeps
    this.newDeps = tmp
    this.newDeps.length = 0
  }
Copy the code

Why delete the old DEPs? Because when we update the data it causes vUE to re-render the VDOM. In the process of rendering, the property will be accessed again, and in the process of accessing the property, the get function of the property will be triggered again, and then the dependency will be collected again. But what if we had the old dependency before, then we must delete the old dependency and save the new dependency. It then drops the new dependency into the old dependency and waits for the next update.

In example 1, because we are not updating the data, this operation has little impact.