When I mentioned new Vue, I did a bunch of initializations and I did a mount function called mount so let’s go from mount so let’s go from mount so let’s go from mount to see how Dom renders and updates.

What does Vue’s $mount function do?

The $mount function defines:

src/platforms/web/runtime/index.js

import { mountComponent } from 'core/instance/lifecycle'
// 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

The $mount function returns mountComponent

src/core/instance/lifecycle

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') {
      ***
  }
  callHook(vm, 'beforeMount')

  let updateComponent
  /* istanbul ignore if */
  if(process.env.NODE_ENV ! = ='production' && config.performance && mark) {
    ***
  } else {
    updateComponent = () = > {
      vm._update(vm._render(), hydrating)
    }
  }

  vm._watcher = new Watcher(vm, updateComponent, noop)
  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

The mountComponent function does two main things

1. If there is no render function, give a render function

2. Initialize the watch function and apply the observer mode. When the observed data changes, the _update function is triggered to update the DOM

In addition, in the Runtime + Compiler version, vue overloads $mount, which converts tmplate and EL to render functions. The Runtime-only version does not. Specific see

src/platforms/web/entry-runtime-with-compiler.js

src/platforms/web/entry-runtime.js

So what does the Watch class do

What does Watch do?

src/core/observer/watcher.js

export default class Watcher {
  vm: Component;
  expression: string;
  cb: Function;
  id: number;
  deep: boolean;
  user: boolean;
  lazy: boolean;
  sync: boolean;
  dirty: boolean;
  active: boolean;
  deps: Array<Dep>;
  newDeps: Array<Dep>;
  depIds: ISet;
  newDepIds: ISet;
  getter: Function;
  value: any;

  constructor (
    vm: Component,
    expOrFn: string | Function,
    cb: Function, options? :Object
  ) {
    this.vm = vm
    vm._watchers.push(this)
    // options
    if (options) {
      this.deep = !! options.deepthis.user = !! options.userthis.lazy = !! options.lazythis.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 = 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
        )
      }
    }
    this.value = this.lazy
      ? undefined
      : this.get()
  }
Copy the code
vm._watcher = new Watcher(vm, updateComponent, noop)
Copy the code

You’re essentially passing in the VM Vue itself and the updateComponent function

UpdateComponent is executed when changes are observed

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

The _update function is executed and the _render result is passed in

What does _render do?

src/core/instance/render.js

export function initRender (vm: Component) {
  ***
  vm._vnode = null // the root of the child tree
  vm._c = (a, b, c, d) = > createElement(vm, a, b, c, d, false)
  // normalization is always applied for the public version, used in
  // user-written render functions.
  vm.$createElement = (a, b, c, d) = > createElement(vm, a, b, c, d, true)
  ***
}
Vue.prototype._render = function () :VNode {
    const vm: Component = this
    const { render, _parentVnode } = vm.$options

    if (vm._isMounted) {
      // if the parent didn't update, the slot nodes will be the ones from
      // last render. They need to be cloned to ensure "freshness" for this render.
      for (const key in vm.$slots) {
        const slot = vm.$slots[key]
        if (slot._rendered) {
          vm.$slots[key] = cloneVNodes(slot, true /* deep */)
        }
      }
    }

    vm.$scopedSlots = (_parentVnode && _parentVnode.data.scopedSlots) || emptyObject

    // 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 {
      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') {
        ***
      } else {
        vnode = vm._vnode
      }
    }
    // return empty vnode in case the render function errored out
    if(! (vnodeinstanceof VNode)) {
      if(process.env.NODE_ENV ! = ='production' && Array.isArray(vnode)) {
        ***
      }
      vnode = createEmptyVNode()
    }
    // set parent
    vnode.parent = _parentVnode
    return vnode
  }
Copy the code

Refine the code

const { render, _parentVnode } = vm.$options
***
vnode = render.call(vm._renderProxy, vm.$createElement)
***
return vnode
Copy the code

As you can see, the main logic of the _render function is to create a vNode using the render function and return it.

vm.$createElement = (a, b, c, d) = > createElement(vm, a, b, c, d, true)
Copy the code

Vnodes are created using the createElement method

In addition

new Vue({
  render: (h) = > h(App),
}).$mount("#app");
Copy the code

The h function in the render function is createElement

/ SRC /core/vdom/create-element.js createElement

What does _update do?

src/core/instance/lifecycle.js

Vue.prototype._update = function (vnode: VNode, hydrating? : boolean) {
    const vm: Component = this
    if (vm._isMounted) {
      callHook(vm, 'beforeUpdate')}const prevEl = vm.$el
    const prevVnode = vm._vnode
    const prevActiveInstance = activeInstance
    activeInstance = 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 */,
        vm.$options._parentElm,
        vm.$options._refElm
      )
      // no need for the ref nodes after initial patch
      // this prevents keeping a detached DOM tree in memory (#5851)
      vm.$options._parentElm = vm.$options._refElm = null
    } else {
      // updates
      vm.$el = vm.__patch__(prevVnode, vnode)
    }
    ***
  }
Copy the code

The core logic is this

vm.$el = vm.__patch__(prevVnode, vnode)
Copy the code

src/platforms/web/runtime/index.js

Vue.prototype.__patch__ = inBrowser ? patch : noop
Copy the code

src/platforms/web/runtime/patch.js

export const patch: Function = createPatchFunction({ nodeOps, modules })
Copy the code

When running in a browser environment:

You can see that the _update and core code executes the createPatchFunction method

Patch ->=createPatchFunction does what?

src/platforms/web/runtime/patch.js ```js import * as nodeOps from 'web/runtime/node-ops' import { createPatchFunction } from 'core/vdom/patch' import baseModules from 'core/vdom/modules/index' import platformModules from 'web/runtime/modules/index' // the directive module should be applied last, after all // built-in modules have been applied. const modules = platformModules.concat(baseModules) export const patch:  Function = createPatchFunction({ nodeOps, modules })Copy the code

CreatePatchFunction accepts an object with nodeOps, modules

NodeOps encapsulates functions that manipulate the native DOM

Modules contains hook methods for various modules

Let’s look at the implementation of createPatchFunction

/src/core/vdom/patch.js

export const emptyNode = new VNode(' ', {}, [])

const hooks = ['create'.'activate'.'update'.'remove'.'destroy']

export function createPatchFunction (backend) {
  const { modules, nodeOps } = backend
  
  for (i = 0; i < hooks.length; ++i) {
    cbs[hooks[i]] = []
    for (j = 0; j < modules.length; ++j) {
      if (isDef(modules[j][hooks[i]])) {
        cbs[hooks[i]].push(modules[j][hooks[i]])
      }
    }
  }
  
  ***
  return function patch (oldVnode, vnode, hydrating, removeOnly, parentElm, refElm) {
    if (isUndef(vnode)) {
      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, parentElm, refElm)
    } else {
      const isRealElement = isDef(oldVnode.nodeType)
      if(! isRealElement && sameVnode(oldVnode, vnode)) {// patch existing root node
        patchVnode(oldVnode, vnode, insertedVnodeQueue, 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)) {
            oldVnode.removeAttribute(SSR_ATTR)
            hydrating = true
          }
          if (isTrue(hydrating)) {
            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
          oldVnode = emptyNodeAt(oldVnode)
        }
        // replacing existing element
        const oldElm = oldVnode.elm
        const parentElm = nodeOps.parentNode(oldElm)
        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)
        )

        if (isDef(vnode.parent)) {
          // component root element replaced.
          // update parent placeholder node element, recursively
          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
          }
        }

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

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

Simplify the code of Patch

return function patch (oldVnode, vnode, hydrating, removeOnly, parentElm, refElm) {
    if (isUndef(oldVnode)) {
      // empty mount (likely as component), create new root element
      isInitialPatch = true
      createElm(vnode, insertedVnodeQueue, parentElm, refElm)
    } else {
      if(! isRealElement && sameVnode(oldVnode, vnode)) {// patch existing root node
        patchVnode(oldVnode, vnode, insertedVnodeQueue, removeOnly)
      } else {
          if (isRealElement) {
            ***
            oldVnode = emptyNodeAt(oldVnode)
          }
          const oldElm = oldVnode.elm
        const parentElm = nodeOps.parentNode(oldElm)
        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)
        )
      }
    }
  return vnode.elm
}
Copy the code
// First render
vm.$el = vm.__patch__(
        vm.$el, vnode, hydrating, false /* removeOnly */,
        vm.$options._parentElm,
        vm.$options._refElm
      )
// Update render
vm.$el = vm.__patch__(prevVnode, vnode)
Copy the code

Consider the logic of the first render, not the update logic

Patch calls createElm and patchVnode

1. When no old Vnode exists, createElm is executed directly and the children of Vnode are traversed to create the create child node

2. If it has been rendered before, compare and update the old and new DOM, and analyze the complicated logic later.

3. For the first rendering, we pass in what is actually an Element node, so we will use emptyNodeAt to convert oldVnode into a Vnode object, and then execute createElm

So eventually the core logic executes the createElm function

See what createElm did?

function createElm (vnode, insertedVnodeQueue, parentElm, refElm, nested) { 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)) {
      vnode.elm = vnode.ns
        ? nodeOps.createElementNS(vnode.ns, tag)
        : nodeOps.createElement(tag, vnode)
      setScope(vnode)

      /* istanbul ignore if */
      if (__WEEX__) {
        ***
      } else {
        createChildren(vnode, children, insertedVnodeQueue)
        if (isDef(data)) {
          invokeCreateHooks(vnode, insertedVnodeQueue)
        }
        insert(parentElm, vnode.elm, refElm)
      }
    } 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

Simplified code can be seen slightly more clearly

1. CreateComponent is used to try to create a component node

2. The system checks whether vNode. tag is a valid label

There are three cases

2.1 If the label is valid, call the createElement method to create an empty node for inserting child nodes, and then call the createChildren method to traverse the child nodes.

2.2 If it is a comment node, create the comment node and insert into the parent node

Create a text node and insert into the parent node

function createChildren (vnode, children, insertedVnodeQueue) {
    if (Array.isArray(children)) {
      for (let i = 0; i < children.length; ++i) {
        createElm(children[i], insertedVnodeQueue, vnode.elm, null.true)}}else if (isPrimitive(vnode.text)) {
      nodeOps.appendChild(vnode.elm, nodeOps.createTextNode(vnode.text))
    }
  }
Copy the code

CreateChildren creates a depth-first traversal of vnode’s children, which are also vnodes. Call createElm recursively and set vnode. Elm as the parent node to mount children to the parent node step by step. And render to the real DOM tree.

Conclusion:

new Vue({
  render: (h) = > h(App),
}).$mount("#app");
Copy the code

After we perform $mount with new Vue(), Vue does the following overall

1. In new Vue(), _init() is executed, the lifecycle hooks are initialized, events are mounted to the prototype, etc

function Vue (options) {
  this._init(options)
}

Vue.prototype._init = function (options? :Object) {
    ***
    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

2. Then run the $mount() function to mount it. The logic is as follows:

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

2.1. Execute the mountComponent function to create an observer to listen for data changes and change the DOM tree based on the changes. The main logic is to execute the _update function, which is executed once on the first render and on each data update.

function mountComponent (vm: Component, el: ? Element, hydrating? : boolean) :Component {
  updateComponent = () = > {
    vm._update(vm._render(), hydrating)
  }
  vm._watcher = new Watcher(vm, updateComponent, noop)
  return vm
}
Copy the code

2.2. The _update function has the following logic:

2.2.1. The vnode generated by the render function is passed the _update function for the first time, and the patch function is executed to create a new DOM tree. 2.2.2 When the patch function is executed, the DIff algorithm will be executed to update the DOM tree

Vue.prototype._update = function (vnode: VNode, hydrating? : boolean) {
  if(! prevVnode) { vm.$el = vm.__patch__( vm.$el, vnode, hydrating,false /* removeOnly */,
        vm.$options._parentElm,
        vm.$options._refElm
      )
      vm.$options._parentElm = vm.$options._refElm = null
    } else {
      // updates
      vm.$el = vm.__patch__(prevVnode, vnode)
    }
}
Copy the code
Vue.prototype.__patch__ = inBrowser ? patch : noop

export const patch: Function = createPatchFunction({ nodeOps, modules })
Copy the code
export function createPatchFunction (backend) {
  return function patch (oldVnode, vnode, hydrating, removeOnly, parentElm, refElm) {
    if (isUndef(oldVnode)) {
      createElm(vnode, insertedVnodeQueue, parentElm, refElm)
    } else {
      const isRealElement = isDef(oldVnode.nodeType)
      if(! isRealElement && sameVnode(oldVnode, vnode)) {// patch existing root node
        patchVnode(oldVnode, vnode, insertedVnodeQueue, removeOnly)
      } else {
        if (isRealElement) {
          oldVnode = emptyNodeAt(oldVnode)
        }
        const oldElm = oldVnode.elm
        const parentElm = nodeOps.parentNode(oldElm)
        createElm(
          vnode,
          insertedVnodeQueue,
          oldElm._leaveCb ? null : parentElm,
          nodeOps.nextSibling(oldElm)
        )
    }
  }
}
Copy the code

If the vnode is not a real DOM, patchVnode is used to compare the old and new vNodes. If not, createElm is used

PatchVnode is updated by comparing the old and new virtual DOM

CreateElm is created by iterating through the entire VNode tree, mounting it recursively from child to parent, and updating it to the real DOM using a native API wrapped in nodeOps.

At this point dom rendering is complete.

Several core logic lines:

init() -> mount() -> Watch -> update -> render -> patch

render -> createElement

patch -> createElm / patchVnode