This is the 14th day of my participation in Gwen Challenge

preface

As anyone who has written Vue code knows, there are two ways to create a Vue instance:

First: instantiate a Vue object

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

Second: instantiate child components

import App from './App.vue';
new Vue({
    router,
    store,
    render: h= > h(App)
}).$mount('#app');
Copy the code

Either way, the Vue constructor is called, so what exactly does this new Vue procedure do?

Vue initialization

Vue constructor defined in SRC/core/instance/index, js, it basically is to call a _init method

function Vue (options) {
  if(process.env.NODE_ENV ! = ='production' &&
    !(this instanceof Vue)
  ) {
    warn('Vue is a constructor and should be called with the `new` keyword')}this._init(options)
}
Copy the code

_init methods defined in SRC/core/instance/init. Js

Vue.prototype._init = function (options? :Object) {
    const vm: Component = this
    // a uid
    vm._uid = uid++

    let startTag, endTag
    
    ...

    // a flag to avoid this being observed
    vm._isVue = true
    // merge options
    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 {
      vm.$options = mergeOptions(
        resolveConstructorOptions(vm.constructor),
        options || {},
        vm
      )
    }
    /* 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')


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

When the Vue instance is a component, the initInternalComponent method is executed. Otherwise, execute mergeOptions

initInternalComponent

The method as a whole is to add attributes to the vm.$options, the instantiation component will be explained later.

export function initInternalComponent (vm: Component, options: InternalComponentOptions) {
  const opts = vm.$options = Object.create(vm.constructor.options)
  // doing this because it's faster than dynamic enumeration.
  const parentVnode = options._parentVnode
  opts.parent = options.parent
  opts._parentVnode = parentVnode

  const vnodeComponentOptions = parentVnode.componentOptions
  opts.propsData = vnodeComponentOptions.propsData
  opts._parentListeners = vnodeComponentOptions.listeners
  opts._renderChildren = vnodeComponentOptions.children
  opts._componentTag = vnodeComponentOptions.tag

  if (options.render) {
    opts.render = options.render
    opts.staticRenderFns = options.staticRenderFns
  }
}
Copy the code

mergeOptions

MergeOptions merges the options configuration parameters of parent and child, where parent is the options of the constructor and child is the options passed in when instantiated. When child._base is true, the definition is also merged in extends and mixins.

DefaultStrat and Strats contain the specific merge strategy, which is not covered here but will be covered in a chapter later.

export function mergeOptions (
  parent: Object,
  child: Object, vm? : Component) :Object {
  if(process.env.NODE_ENV ! = ='production') {
    checkComponents(child)
  }

  if (typeof child === 'function') {
    child = child.options
  }

  normalizeProps(child, vm)
  normalizeInject(child, vm)
  normalizeDirectives(child)

  // Apply extends and mixins on the child options,
  // but only if it is a raw options object that isn't
  // the result of another mergeOptions call.
  // Only merged options has the _base property.
  if(! child._base) {if (child.extends) {
      parent = mergeOptions(parent, child.extends, vm)
    }
    if (child.mixins) {
      for (let i = 0, l = child.mixins.length; i < l; i++) {
        parent = mergeOptions(parent, child.mixins[i], vm)
      }
    }
  }

  const options = {}
  let key
  for (key in parent) {
    mergeField(key)
  }
  for (key in child) {
    if(! hasOwn(parent, key)) { mergeField(key) } }function mergeField (key) {
    const strat = strats[key] || defaultStrat
    options[key] = strat(parent[key], child[key], vm, key)
  }
  return options
}

Copy the code

Several initialization functions are not expanded here, but will be expanded individually later.

The _init method is used to initialize the VM. The life cycle hooks beforeCreate and created are executed during initialization. Initialize the event, data, etc.

$mount(vm.$options.el); $mount(vm.$options.el); If no EL is passed, we will manually call $mount(‘#app’). We can either instantiate the Vue component or Vue object by passing the el argument, or we can call $mount manually

$mount (); $mount (); $mount ();

Vue mounts the instance

Vue mounts the VM through the $mount instance method. The $mount method has multiple definitions, as its implementation depends on the platform and how it is built, but is extended by the mount method defined on the prototype.

Look at the Vue prototype defined on $mount method: SRC/platform/web/runtime/index. Js

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 method takes two main parameters: the first is the mounted element (either a string or a DOM object), and the second is the server render parameter. The last is to call the real mountComponent methods: SRC/core/instance/lifecycle. Js

Take a look at the highlights of the mountComponent method:

export function mountComponent (vm: Component, el: ? Element, hydrating? : boolean) :Component {
  vm.$el = el
  if(! vm.$options.render) { vm.$options.render = createEmptyVNode ... } callHook(vm,'beforeMount')

  let updateComponent
  /* istanbul ignore if */
  if(process.env.NODE_ENV ! = ='production' && config.performance && mark) {
    updateComponent = () = >{... }}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 && ! 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

MountComponet first calls the beforeMount hook. Then we define updateComponent

Next instantiate a render Watcher, which is the core of the mountComponent. The Watcher executes the updateComponent callback function on the new Vue instance. Execute the callback function when monitoring data changes in the VM instance (added later in the watcher implementation)

Vm. $vnode represents the parent virtual node of the Vue instance. If it is null, it indicates that it is currently an instance of the root Vue.

The updateComponent callback is simply defined as vm._update(vm._render(), hydrating), which calls two methods: _render renders the Vue instance as a Vnode, and _update renders the Vnode as a real DOM

conclusion

The Vue project entry file does two main things during the new Vue instance, one is to initialize the VM (various events, parameters, etc.), and one is to mount the Vue instance to ‘#app’