Re-learn Vue source code, according to Huang Yi big man Vue technology revealed, one by one, consolidate the Vue source knowledge points, after all, chewed up is their own, all the articles are synchronized in the public number (road in the front stack) and github.

The body of the

This article takes a look at what the Vue instance mount, vm.$mount, does.

If you open SRC /platforms/web/entry-runtime-with-compiler.js, you’ll see a vue.prototype.$mount method:

const idToTemplate = cached(id= > {
  const el = query(id)
  return el && el.innerHTML
})

const mount = Vue.prototype.$mount
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
) :Component {
  el = el && query(el)

  /* istanbul ignore if */
  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
  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)}}}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 */
      if(process.env.NODE_ENV ! = ='production' && config.performance && mark) {
        mark('compile')}const { render, staticRenderFns } = compileToFunctions(template, {
        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')}}}return mount.call(this, el, hydrating)
}
Copy the code

Prototype.$mount = Vue. Prototype.$mount = Vue. Prototype. Can be found in the SRC/platforms/web/runtime/index. The definition of js see it:

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

There are run-time Complier and run-time Only versions of Vue:

Run-time Only is run in compile phase, that is, use webpack vue-loader to compile.vue files into JavaScript for use.

Run-time Complier is used to compile the render function from the template in the page and render it on the page.

The original vue.prototype.$mount was used for run-time Only, so it will need to be rewritten for run-time Complier.

$mount(vm.$options.el) :

// src/core/instance/init.js
if (vm.$options.el) {
  vm.$mount(vm.$options.el)
}
Copy the code

$mount(vm.$options.el) is actually the rewritten $mount function called. Let’s see what this function does:

We first process the passed EL argument, which can be either a String or an Element, and then call the query method. Let’s see what the query method does:

export function query (el: string | Element) :Element {
  if (typeof el === 'string') {
    const selected = document.querySelector(el)
    if(! selected) { process.env.NODE_ENV ! = ='production' && warn(
        'Cannot find element: ' + el
      )
      return document.createElement('div')}return selected
  } else {
    return el
  }
}
Copy the code

It calls the native method Document. querySelecto to get the passed EL, which gets the DOM if it’s a string, returns an empty div if it can’t find it, or returns the DOM object if el is a DOM object. The returned EL must be a DOM object.

Then, once you have the EL, determine if the EL is a body or document tag, and if so, report an error saying that you cannot mount the Vue to < HTML > or .

Because it will be overridden if it can be hung in to<html>or<body>The entire body will be replaced! Therefore, we generally use an ID as APP to use it

Then you get options, followed by an if (! Options.render), which defines the render method, and then the template.

new Vue({
  el: "#app".template: ` `.data(){
    return{
      name: "abc"}}})Copy the code

If template is a string, do something to it. If template.nodeType is a DOM object, innerHTML. Otherwise, getOuterHTML is used:

/** * Get outerHTML of elements, taking care * of SVG elements in IE as well. */
function getOuterHTML (el: Element) :string {
  if (el.outerHTML) {
    return el.outerHTML
  } else {
    const container = document.createElement('div')
    container.appendChild(el.cloneNode(true))
    return container.innerHTML
  }
}
Copy the code

GetOuterHTML checks if the passed EL has a outerHTML method. If it doesn’t, it wraps a div around the EL and then innerHTML. The template is a string.

Then the compile phase begins to determine if there is a template, which basically means taking a Render function of complieToFunctions and a staticRenderFns function and assigning it.

Going through the whole thing, $mount does something:

First parse the EL to see if it has a render method, then convert it to a template, which is then compiled into a Render method.

Call (this, el, hyrating); render (this, el, hyrating); render (this, el, hyrating) Convert template to the render function.

Render (this, el, hyrating); render (this, el, hyrating);

const mount = Vue.prototype.$mount
Copy the code

Prototype.$mount method:

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

Then mountComponent method, defined as in SRC/core/instance/lifecycle. In js:

export function mountComponent (vm: Component, el: ? Element, hydrating? : boolean) :Component {
  vm.$el = el
  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
        )
      }
    }
  }
  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 = () = > {
      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) {
        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

Now analyze what the mountComponent does:

$el is used to render the vm.$el, then check if there is a render function. If there is no or no render function for template, define a createEmptyVNode, a virtual DOM. The first template in the development environment is not #.

/ / render the Runtime version of Vue/template/render the Runtime version of Vue/template / / render the Runtime version of Vue/template / / render the Runtime version of Vue/template / / render the Runtime version of Vue/template / /

You are using the runtime-only build of Vue where the templatecompiler is not available. Either pre-compile the templates intorender functions, or use the compiler-included build.

/ / render function: / / render function: / / render function: / / render function: / / render function: / / render function:

Failed to mount component: template or render function not defined.

This mistake should be familiar. Vue only recognizes the render function because there is no correct render function.

Next, we define an updateComponent, which is a validation of some performance buried points and usually goes straight to the end:

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

The vm._update method is called, and the first argument is to render a VNode using render, and the second argument is to render a VNode on the server side, which is ignored for now and defaults to false.

And then, we call a new Watcher function, which is a render Watcher, and remember this, usually when you’re writing code, watch is used to listen for things, so this new Watcher is a class that has a strong correlation with listening, which is the observer mode. There can be a lot of custom Watcher in the code, internal logic will have a render Watcher. Look at the rendering watcher is why, in the SRC/core/observer/watcher. Js, a particularly large watcher definition:


/** * A watcher parses an expression, collects dependencies, * and fires callback when the expression value changes. * This is used for both the $watch() api and directives. */
export default class Watcher {
  vm: Component;
  expression: string;
  cb: Function;
  id: number;
  deep: boolean;
  user: boolean;
  computed: boolean;
  sync: boolean;
  dirty: boolean;
  active: boolean;
  dep: Dep;
  deps: Array<Dep>;
  newDeps: Array<Dep>;
  depIds: SimpleSet;
  newDepIds: SimpleSet;
  before: ?Function;
  getter: Function;
  value: any;

  constructor (
    vm: Component,
    expOrFn: string | Function,
    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.computed = !! options.computedthis.sync = !! options.syncthis.before = options.before
    } else {
      this.deep = this.user = this.computed = this.sync = false
    }
    this.cb = cb
    this.id = ++uid // uid for batching
    this.active = true
    this.dirty = this.computed // for computed 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 = function () {} 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
        )
      }
    }
    if (this.computed) {
      this.value = undefined
      this.dep = new Dep()
    } else {
      this.value = this.get()
    }
  }

  /** * Evaluate the getter, and re-collect dependencies. */
  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 {
      // "touch" every property so they are all tracked as
      // dependencies for deep watching
      if (this.deep) {
        traverse(value)
      }
      popTarget()
      this.cleanupDeps()
    }
    return value
  }

  /** * Add a dependency to this directive. */
  addDep (dep: Dep) {
    const id = dep.id
    if (!this.newDepIds.has(id)) {
      this.newDepIds.add(id)
      this.newDeps.push(dep)
      if (!this.depIds.has(id)) {
        dep.addSub(this)}}}/** * Clean up for dependency collection. */
  cleanupDeps () {
    let i = this.deps.length
    while (i--) {
      const dep = this.deps[i]
      if (!this.newDepIds.has(dep.id)) {
        dep.removeSub(this)}}let tmp = this.depIds
    this.depIds = this.newDepIds
    this.newDepIds = tmp
    this.newDepIds.clear()
    tmp = this.deps
    this.deps = this.newDeps
    this.newDeps = tmp
    this.newDeps.length = 0
  }

  /** * Subscriber interface. * Will be called when a dependency changes. */
  update () {
    /* istanbul ignore else */
    if (this.computed) {
      // A computed property watcher has two modes: lazy and activated.
      // It initializes as lazy by default, and only becomes activated when
      // it is depended on by at least one subscriber, which is typically
      // another computed property or a component's render function.
      if (this.dep.subs.length === 0) {
        // In lazy mode, we don't want to perform computations until necessary,
        // so we simply mark the watcher as dirty. The actual computation is
        // performed just-in-time in this.evaluate() when the computed property
        // is accessed.
        this.dirty = true
      } else {
        // In activated mode, we want to proactively perform the computation
        // but only notify our subscribers when the value has indeed changed.
        this.getAndInvoke(() = > {
          this.dep.notify()
        })
      }
    } else if (this.sync) {
      this.run()
    } else {
      queueWatcher(this)}}/** * Scheduler job interface. * Will be called by the scheduler. */
  run () {
    if (this.active) {
      this.getAndInvoke(this.cb)
    }
  }

  getAndInvoke (cb: Function) {
    const value = this.get()
    if( value ! = =this.value ||
      // Deep watchers and watchers on Object/Arrays should fire even
      // when the value is the same, because the value may
      // have mutated.
      isObject(value) ||
      this.deep
    ) {
      // set new value
      const oldValue = this.value
      this.value = value
      this.dirty = false
      if (this.user) {
        try {
          cb.call(this.vm, value, oldValue)
        } catch (e) {
          handleError(e, this.vm, `callback for watcher "The ${this.expression}"`)}}else {
        cb.call(this.vm, value, oldValue)
      }
    }
  }

  /** * Evaluate and return the value of the watcher. * This only gets called for computed property watchers. */
  evaluate () {
    if (this.dirty) {
      this.value = this.get()
      this.dirty = false
    }
    return this.value
  }

  /** * Depend on this watcher. Only for computed property watchers. */
  depend () {
    if (this.dep && Dep.target) {
      this.dep.depend()
    }
  }

  /** * Remove self from all dependencies' subscriber list. */
  teardown () {
    if (this.active) {
      // remove self from vm's watcher list
      // this is a somewhat expensive operation so we skip it
      // if the vm is being destroyed.
      if (!this.vm._isBeingDestroyed) {
        remove(this.vm._watchers, this)}let i = this.deps.length
      while (i--) {
        this.deps[i].removeSub(this)}this.active = false}}}Copy the code

ExpOrFn, CB, Option, isRenderWatcher.

Above, the arguments passed in are:

	vm,  / / vm instances
    
  updateComponent,  / / vm. _update method
    
  noop,  // an empty function
    
  { // a lifecycle function
    before () {
      if (vm._isMounted) {
        callHook(vm, 'beforeUpdate')}}},true // Is not a render watcher
Copy the code

Next, determine if the isRenderWatcher is true, that is, if it is a render Watcher, add a _watcher under the VM, and push everything inside the _watcher. Exporfn.tostring () specifies the exporfn.tostring () expression, which is exporfn.tostring ().

Next, check whether expOrFn is a function, if so, assign to the getter, otherwise call parsePath and assign to the getter. This.puted is about calculating properties, ignore it for now. Value = this.getter.call(vm, vm), which calls the this.getter call that was just passed in to updateComponent, Vm._update (vm._render(), hydrating) is executed. Vm. _update and vm._render are the functions that eventually mount to the real DOM.

First execute vm._render, remember that render above finally generates a VNode, and then call _update to pass the VNode in.

At this point, the Vue instance is mounted.

To give you an overview:

$mount = Vue; $mount = Vue; $mount = Vue; $mount = Vue;

  1. Write directly the 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)}}}Copy the code
  2. Template is a DOM

    if (template.nodeType) {
      template = template.innerHTML
    }
    Copy the code
  3. And get template by el without writing template

    if (el) {
      template = getOuterHTML(el)
    }
    
    function getOuterHTML (el: Element) :string {
      if (el.outerHTML) {
        return el.outerHTML
      } else {
        const container = document.createElement('div')
        container.appendChild(el.cloneNode(true))
        return container.innerHTML
      }
    }
    Copy the code

We then convert the template to the render function through a bunch of operations and call the mountComponent method, which defines the updateComponent method:

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

Then drop updateComponent into render Watcher (new Watcher) and mount it successfully!

The updateComponent function performs an actual render. In addition to the initial _render and _update, the render watcher(new Watcher) is triggered when the data is updated. Execute updateComponent again, which is a process that listens to the execution, and when the data changes, the entry is updateComponent as well.