In chapter 1, we covered Vue source code parsing series (1) — initializing new Vue, Vue dependency Collection and view update for a responsive system (Vue , and we have implemented a two-way binding can be hanged the interviewer source code analysis series (four) — to achieve a two-way binding bar (hanged the interviewer), the next few chapters will talk about Vue component system

Let’s focus on that$mountIs to achieve what function bar:We open the source pathcore/instance/init.js:

export function initMixin (Vue: Class<Component>) {... initLifecycle(vm)// Event listener initialization
    initEvents(vm)
    initRender(vm)
    callHook(vm, 'beforeCreate')
    initInjections(vm) // resolve injections before data/props
    // Initialize the VM status prop/data/computed/watch Completes the initialization
    initState(vm)
    initProvide(vm) // resolve provide after data/props
    callHook(vm, 'created')...// If the configuration item has an EL attribute, it will be mounted to the real DOM to complete the rendering of the view
    / / $mount method here, essentially calls the core/instance/liftcycle mountComponent method in js
    if (vm.$options.el) {
      vm.$mount(vm.$options.el)
    }
  }
}
Copy the code

How do we understand this mount state here? Let’s start with a description from Vue officials

  • ifVueThe instance was not received at instantiation timeelOption, it is in"Not mounted"State, unassociatedDOMElements.
  • You can usevm.$mount()Manually mount an unmounted instance.
  • If not providedelementOrSelectorParameter, the template will be rendered as an element outside of the document.
  • And you have to useThe native DOM APIInsert it into the document.

Let’s take a look at the internal mechanics of $mount:

* caches the previous $mount method for later return instances, */const mount = Vue.prototype.$mount
/** * Manually mount an unmounted root element and return the instance itself (Vue instance) */
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
) :Component {
  el = el && query(el)

  /* istanbul ignore if */
  /** * The mount object cannot be body and HTML tags */
  if (el === document.body || el === document.documentElement) { 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
  // resolve template/el and convert to render function
  /** * Check if $options has a render method * yes: check if it's a String or Element, get their innerHTMl * none: Create an empty comment node in Vue as shown in createEmptyVNode */
  if(! options.render) {let template = options.template
    if (template) {
      if (typeof template === 'string') {
        if (template.charAt(0) = = =The '#') {
          template = idToTemplate(template)
          /* istanbul ignore if */
          if(process.env.NODE_ENV ! = ='production' && !template) {
            warn(
              `Template element not found or is empty: ${options.template}`.this)}}/ * * * detailed see https://developer.mozilla.org/zh-CN/docs/Web/API/Element/outerHTML for the type of Element * * /
      } else if (template.nodeType) {
        template = template.innerHTML
      } else {
        if(process.env.NODE_ENV ! = ='production') {
          warn('invalid template option:' + template, this)}return this}}else if (el) {
      template = getOuterHTML(el)
    }
    if (template) {
      /* istanbul ignore if */
      /** * used to monitor compile's performance */
      if(process.env.NODE_ENV ! = ='production' && config.performance && mark) {
        mark('compile')}// If no render function exists, the template will be converted to render function
      const { render, staticRenderFns } = compileToFunctions(template, {
        shouldDecodeNewlines,
        shouldDecodeNewlinesForHref,
        delimiters: options.delimiters,
        comments: options.comments
      }, this)
      options.render = render
      options.staticRenderFns = staticRenderFns

      /* istanbul ignore if */
       /** * used to monitor compile's performance */
      if(process.env.NODE_ENV ! = ='production' && config.performance && mark) {
        mark('compile end')
        measure(`vue The ${this._name} compile`.'compile'.'compile end')}}}return mount.call(this, el, hydrating)
}
Copy the code

$mount implements the mountComponent function

// public mount method
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
) :Component {
  el = el && inBrowser ? query(el) : undefined
  return mountComponent(this, el, hydrating)
}
Copy the code

So let’s go back to the mountComponent function:

export function mountComponent (vm: Component, el: ? Element, hydrating? : boolean) :Component {
  vm.$el = el
  // Create an empty VNode if no render function exists
  if(! vm.$options.render) { vm.$options.render = createEmptyVNodeif(process.env.NODE_ENV ! = ='production') {
      /* istanbul ignore if */
      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
        )
      }
    }
  }
  // After checking render, start calling beforeMount declaration cycles
  callHook(vm, 'beforeMount')

  let updateComponent
  /* istanbul ignore if */
  if(process.env.NODE_ENV ! = ='production' && config.performance && mark) {
    updateComponent = () = > {
      const name = vm._name
      const id = vm._uid
      const startTag = `vue-perf-start:${id}`
      const endTag = `vue-perf-end:${id}`

      mark(startTag)
      const vnode = vm._render()
      mark(endTag)
      measure(`vue ${name} render`, startTag, endTag)

      mark(startTag)
      vm._update(vnode, hydrating)
      mark(endTag)
      measure(`vue ${name} patch`, startTag, endTag)
    }
  } else {
    updateComponent = () = > {
       Note that the second expOrFn parameter is a function
      // Will be executed once on new Watcher via the get method
      // The first Dom update is triggered
      vm._update(vm._render(), hydrating)
    }
  }
  
vm._watcher = new Watcher(vm, updateComponent, noop, null.true /* isRenderWatcher */)
  hydrating = false
  
  // Trigger the $mount function
  if (vm.$vnode == null) {
    vm._isMounted = true
    callHook(vm, 'mounted')}return vm
}
Copy the code

To sum up:

  • performvm._watcher = new Watcher(vm, updateComponent, noop).
  • The triggerWatcherThe inside of thegetMethod, setDep.target = watcher.
  • performupdateComponent.

During this process, the data we bound will be read, and since we have been hijacking the data through the Observer, this will trigger the get method for the data. At this point watcher is added to the corresponding DEP. Notify Watcher with dep.notify() when data is updated, and then execute the Update method in Watcher. At this point, updateComponent is executed again, and the view is rerendered.

Let’s focus on vm._update(vm._render(), hydrating):

.let vnode
    try {
      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') {
        if (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
        }
      } else {
        vnode = vm._vnode
      }
    }
Copy the code

$createElement = VNode; $createElement = VNode; $createElement = VNode; $createElement = VNode;

render: function (createElement) {
  return createElement('h1'.'title')}Copy the code

Data we know how to update, componentstamplateThe realdomHow is it updated?

  • parsingtamplateGenerating string
  • render FunctionHandling string generationVNode
  • patch diffAlgorithm to deal withVNode
  • realdomApply colours to a drawing

So the next chapter we will talk about Vue source code parsing series (6) – how to parse the template tamplate compilation