Writing in the front

The article for reading notes to clone down Vue source code and debugging take ~~

Let’s take a look at how Vue goes from instance creation to DOM rendering:

For a basic rendering, the following analysis revolves around examples:

new Vue({
  el: '#app'.data: {
     msg: 'Hello Vue! ',}});Copy the code

So let’s see what new Vue() does.


import Vue from ‘vue’

When introducing Vue in a normal browser environment, SRC \platforms\web\ Runtime \index.js, SRC \platforms\web\entry-runtime-with-compiler.js, SRC \platforms\web\entry-runtime-with-compiler. They do some initialization of the Vue constructors and mount some static methods for specific environments.

In the first file, the initGlobalAPI method is called for the Vue constructor, which mounts some static methods and does some initialization for the Vue:

/* Execute */ when importing Vue
export function initGlobalAPI (Vue: GlobalAPI) {
  // config
  const configDef = {}
  configDef.get = () = > config
  if(process.env.NODE_ENV ! = ='production') {
    configDef.set = () = > {
      warn(
        'Do not replace the Vue.config object, set individual fields instead.')}}Object.defineProperty(Vue, 'config', configDef)

  // exposed util methods.
  // NOTE: these are not considered part of the public API - avoid relying on
  // them unless you are aware of the risk.
  Vue.util = {
    warn,
    extend,
    mergeOptions,
    defineReactive
  }

  Vue.set = set
  Vue.delete = del
  Vue.nextTick = nextTick

  // 2.6 explicit observable API
  Vue.observable = <T>(obj: T): T= > {
    observe(obj)
    return obj
  }

  Vue.options = Object.create(null)
  ASSET_TYPES.forEach(type= > {
    Vue.options[type + 's'] = Object.create(null)})// this is used to identify the "base" constructor to extend all plain-object
  // components with in Weex's multi-instance scenarios.
  Vue.options._base = Vue

  extend(Vue.options.components, builtInComponents)

  initUse(Vue)
  initMixin(Vue)
  initExtend(Vue)  
  initAssetRegisters(Vue)
}
Copy the code

The second file mounts the __patch__ and $mount methods to Vue for all runtime environments:

// install platform patch function
// Assign patch to __patch__ in the Web environment
// Otherwise assign an empty function, since Vue does not have DOM objects when used on other platforms
// Patch is defined in SRC \platforms\web\ Runtime \patch.js
Vue.prototype.__patch__ = inBrowser ? patch : noop

// public mount method
// Public entry to the $mount mount method in different environments
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
) :Component {
  // In the browser environment, get the DOM element corresponding to el
  el = el && inBrowser ? query(el) : undefined
  // Execute the real mount method
  return mountComponent(this, el, hydrating)
}
Copy the code

The third file ADAPTS to the $mount method for web environments:

// Cache public $mount
const mount = Vue.prototype.$mount
Vue.prototype.$mount = function () { / /... }
Copy the code

init

Vue’s constructor is defined in SRC \core\instance\index.js and is initialized before new is called. It initializes merge configuration, initializes lifecycle, initializes event center, initializes render, initializes Data, props, computed, watcher, and so on:

/* new Vue() from here */
function Vue (options) {
  // The new call must be used
  if(process.env.NODE_ENV ! = ='production' &&
    !(this instanceof Vue)
  ) {
    warn('Vue is a constructor and should be called with the `new` keyword')}// Performs the initialization method, defined in initMixin
  this._init(options)
}

// Mount the initializer _init and merge option to the vm.$option property
initMixin(Vue)
// block access to $data and $props, mount data operations on the prototype with $set, $delete, $watch methods
stateMixin(Vue)
// Mount event-related methods: $on, $once, $off, $emit
eventsMixin(Vue)
// Mount life-cycle methods: _update, $forceUpdate, $destroy
lifecycleMixin(Vue)
// Mount render methods: $nextTick, _render
renderMixin(Vue)

export default Vue
Copy the code

$mount

In initMixin, merge configuration and some initialization work is done, and finally $mount is called to enter the mount process:

// When we pass el, we actually call $mount() to start mounting
if (vm.$options.el) {
    vm.$mount(vm.$options.el)
}
Copy the code

The $mount function generates the template and render functions, and finally calls the cached public mount function:

// Cache public $mount
const mount = Vue.prototype.$mount
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
) :Component {
  // Query () is a wrapper around the element's DOM selector
  // Returns the mounted root element (#app), or an empty div if none exists
  el = el && query(el)

  /* istanbul ignore if */
  // Cannot be body or HTML
  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
  // Parse template/el and convert to render function
  if(! options.render) {let template = options.template
    // Whether template is used
    if (template) {
      if (typeof template === 'string') {
        if (template.charAt(0) = = =The '#') {
          // idToTemplate returns a string of child nodes to mount the root node
          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) {
        // If template is an element, take innerHTML directly
        template = template.innerHTML
      } else {
        if(process.env.NODE_ENV ! = ='production') {
          warn('invalid template option:' + template, this)}return this}}else if (el) {
      // getOuterHTML returns the string of child nodes to mount the root node
      template = getOuterHTML(el)
    }
    if (template) {
      /* istanbul ignore if */
      if(process.env.NODE_ENV ! = ='production' && config.performance && mark) {
        mark('compile')}// Use template to generate the render function and mount it to option
      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')}}}// Finally, call the previously cached public $mount
  return mount.call(this, el, hydrating)
}
Copy the code

The public mount ultimately calls the mountComponent method. In the previous example, we used _render to mount vnode and _update DOM:

vm._update(vm._render(), hydrating /* false */)
Copy the code

render

Let’s rewrite the example:

new Vue({
    el: '#app'.data: {
        msg: 'Hello'
    },
    render(createElement) {
        return createElement('div', {
            attrs: {
                id: 'my-app'}},this.msg); }});Copy the code

_render is also a layer of cremation, which actually calls the render function:

// $createElement corresponds to our example, which is the first argument to the render function createElement
// _renderProxy can be thought of as a proxy for Vue instances, intercepting access to instance properties during rendering
vnode = render.call(vm._renderProxy, vm.$createElement)
Copy the code

$createElement is defined in initRender called in the initMixin method, which is divided into automatically compiled render calls and user manual calls:

// _c automatically compiles the createElement call to the render function
vm._c = (a, b, c, d) = > createElement(vm, a, b, c, d, false)

// The createElement call to the render function passed in by the user
vm.$createElement = (a, b, c, d) = > createElement(vm, a, b, c, d, true)
Copy the code

createElement

CreateElement provides a VNode node for the render function, returns a VNode object or array, and is really just a layer of currization of the real _createElement function to handle some arguments for it:

export function createElement (
  context: Component,
  tag: any,  // Label name or component
  data: any,  // vNode data, such as attrs...
  children: any,  // Child node, tag, or array, which then needs to be normalized as a standard VNode array
  normalizationType: any,  // The type of the child node specification
  alwaysNormalize: boolean
) :VNode | Array<VNode> {
  if (Array.isArray(data) || isPrimitive(data)) {
    // If the second argument is an array or a value of a common type
    // it is directly children
    / / means we can use the createElement method like this: createElement method (' div ', this MSG | | [this MSG, 'hi'])
    normalizationType = children
    children = data
    data = undefined
  }
  if (isTrue(alwaysNormalize)) {
    // children represents the children of the current vNode, which can be of any type
    // It next needs to be normalized as a standard VNode array
    NormalizationType depends on whether the render function is automatically generated or manually called by the user
    $createElement or vm._c
    normalizationType = ALWAYS_NORMALIZE
  }

  // Actually call the _createElement method
  return _createElement(context, tag, data, children, normalizationType)
}
Copy the code

The children specification essentially converts the children parameter into a VNode tree, and converts all nodes at all levels in children into VNode objects, including text nodes, comment nodes, etc.

Complete process for creating a VNode

export function _createElement (context: Component, tag? : string | Class<Component> |Function | Object.// Label name or componentdata? : VNodeData,// vNode data, such as attrs...children? : any,// Child node, tag, or array, which then needs to be normalized as a standard VNode arraynormalizationType? : number// The type of the child node specification
) :VNode | Array<VNode> {
  // debugger
  if (isDef(data) && isDef((data: any).__ob__)) {
    // If reactive data is defined, a warning is issued and an empty vNode is returnedprocess.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)) {
    // According to the contents of the document
    // <component v-bind:is="currentView"></component>
    CreateElement (' Component ', {is: 'currentView'}, [...] )
    // Indicates that a vNode is created by a component
    tag = data.is
  }
  if(! tag) {// in case of component :is set to falsy value
    // An empty vNode is returned because the tag attribute was not passed in correctly
    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
  }
  // normalizationType is already handled in the _createElement call
  // Start normalizing the conversion of children
  if (normalizationType === ALWAYS_NORMALIZE) {
    // The user manually calls render
    children = normalizeChildren(children)
  } else if (normalizationType === SIMPLE_NORMALIZE) {
    // Automatically compile render
    children = simpleNormalizeChildren(children)
  }

  // Start creating a vnode
  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
        )
      }
      // tag Creates a vNode instance directly if it is a built-in node (div, span, etc.)
      vnode = new VNode(
        config.parsePlatformTagName(tag), data, children,
        undefined.undefined, context
      )
    } else if((! data || ! data.pre) && isDef(Ctor = resolveAsset(context.$options,'components', tag))) {
      // component
      // tag Creates a vNode of component type if it is a registered component name
      vnode = createComponent(Ctor, data, context, children, tag)
    } else {
      // unknown or unlisted namespaced elements
      // check at runtime because it may get assigned a namespace when its
      // parent normalizes children

      // Otherwise create a vnode with an unknown label
      vnode = new VNode(
        tag, data, children,
        undefined.undefined, context
      )
    }
  } else {
    // direct component options / constructor
    // Tag passes a component directly, creating a vNode of the component type
    vnode = createComponent(tag, data, context, children)
  }
  if (Array.isArray(vnode)) {
    return vnode
  } else if (isDef(vnode)) {
    if (isDef(ns)) applyNS(vnode, ns)
    if (isDef(data)) registerDeepBindings(data)
    return vnode
  } else {
    return createEmptyVNode()
  }
}
Copy the code

_update (patch)

_update actually calls __patch__ to mount:

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

As mentioned earlier in import, patch is defined by platform, and the actual patch function is a function returned via the createPatchFunction closure:

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

Patch flow for the example

Receiving parameters:

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

Get parent node of el element

// The actual DOM element in vNode, #app
const oldElm = oldVnode.elm
// oldElm's parent, in this case 
const parentElm = nodeOps.parentNode(oldElm)
Copy the code

Call createElm to convert the vNode generated by _render into a real DOM and insert it into the parent node of el:

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

Delete old DOM elements from el:

if (isDef(parentElm)) {
    
      
Hello Vue!
// After insertion, the old root node is destroyed due to the render function // Insert the DOM described by render (#my-app) and destroy the el template (# app) removeVnodes([oldVnode], 0.0)}else if (isDef(oldVnode.tag)) { invokeDestroyHook(oldVnode) } Copy the code

CreateElm process

Receiving parameters:

createElm(
    vnode,
    insertedVnodeQueue,
    oldElm._leaveCb ? null : parentElm,
    nodeOps.nextSibling(oldElm)
)
Copy the code

Create root element

// Save the real DOM on the vnode.elm property
vnode.elm = vnode.ns  // ns: namespace
    ? nodeOps.createElementNS(vnode.ns, tag)
    // The key step is to call the encapsulated native createElement
    CreateElement (tag); createElement(tag);
	: nodeOps.createElement(tag, vnode)
Copy the code

Insert DOM into the page:

Call appendChid to insert the current DOM into the parent node
insert(parentElm, vnode.elm, refElm)

// If the current vNode has no tag attribute, it is a comment node or text node
if (isTrue(vnode.isComment)) {
    vnode.elm = nodeOps.createComment(vnode.text)
    // Create and insert a comment node
    insert(parentElm, vnode.elm, refElm)
} else {
    vnode.elm = nodeOps.createTextNode(vnode.text)
    // Create and insert a text node
    insert(parentElm, vnode.elm, refElm)
}
Copy the code

Before the DOM is inserted, createElm is called by the createChildren loop to generate the DOM for all the children of the current vNode and insert them into the parent of the current child:

// The vnode has already generated a real DOM, and needs to generate the vnode in its children as well
// createElm is called internally for vNode. children because children are vNodes
// If children is a text node, insert it directly into vnode.elm
createChildren(vnode, children, insertedVnodeQueue)
Copy the code

At this point the __Patch__ method call is complete, and _update(_render(), false) is done creating vNodes and patches.