Initialize instance properties

  • Vue mounts properties to the instance via the initLifecycle function
export function initLifecycle(vm) {
    const options - vm.$options;
    
    // Find the first non-abstract parent class
    let parent = options.parent;
    if(parent && ! options.abstract) {while(parent.$options.abstract && parent.$parent) {// If the parent is an abstract class, assign the value to vm.$parent until the first non-abstract parent is encountered
            parent = parent.$parent;
        }
        parent.#children.push(vm);
    }
    
    vm.$parent = parent;
    vm.$root = parent? parent.$root: vm; Represents the root Vue instance of the current component tree
    
    vm.children = []; // represents the immediate child component that contains the current instance
    vm.$refs = {};
    
    vm._watcher = null;
    vm._isDestroyed = false;
    vm._isBeingDestroyed = false;
}
Copy the code

Initialization event

  • Vue performs initialization events through the initEvents function, as shown in the above flowchart
export function initEvents(vm) {
    vm._events = Object.create(null);
    // Initializes the event attached by the parent component
    const listrens = vm.$options.parentListeners;
    if(listrens) {
        updateComponentListenners(vm,listeners)
    }
}

/ / updateComponentListenners function source code
 let target;
 function add(event,fn,once) {
     if(once) {
         target.$once(event,fn);

     }else{ target.$on(event,fn); }}function remove(event,fn) {
    target.$off(event,fn);
}
export function updateComponentListenners(vm,listeners,oldListeners) {
    target = vm;
    updateListenners(vm,listeners|| {}, add, remove,fn);// If a Key exists in the Listenners object but not in the oldListeners, it indicates that the listeners need to add a key
}
 
/ / updateListenners function

function isUndef(v){
    return v === undefined || v === null
}
export function updateListenners(on,oldOn,add,remove,vm) {
    let name ,cur,old, event;
    for (name in on) {
        cur = on[name];
        old = oldOn[name];
        event = normalizeEvent(name);
        if(isUndef(cur)) { // Determine whether the value corresponding to the event name is undefined or null, if so, trigger a warning on the consoleprocess.env.NODE_ENV ! = ='production' && warn(`Invalid handler for event '${event.name}':got` + String(cur),vm);
        } else if (isUndef(old)) { // Determine whether the event name exists in oldOn. If not, call add to register the event
          if (isUndef(cur.fns)) {
            cur = on[name] = createFnInvoker(cur);
          }
          add(event.name, cur, event.once, event.capture, event.passive) 
        } else if(cur ! == old) {// If both exist, but they are not the same, replace the event callback with the callback in onold.fns = cur; on[name] = old; }}for (name in oldOn) {
        if (isUndef(on[name])) {
          event = normalizeEvent(name)
          remove(event.name, oldOn[name], event.capture)
        }
    }

}
Copy the code

Initialization state

  • The initialization status includes props, Methods, data, conputed, and Watch
graph TD
initState --> initProps
initState --> initMethods
initState --> initData
initState --> initComputed
initState --> initWatch
/ / initState source code
initState function initState(vm) {
    vm._watchrs = [];
    const opts = vm.$options;
    if(opts.props) initProps(vm.opts.props); // if vm.$options has props, call props if it does
    if (opts.methods) initMethods(vm,opts.methods);// If the methods attribute is present, the call initializes methods
    if (opts.data) {
        initData(vm);
    } else {
        observe(vm._data = {}, true /* asRootData */);// If it does not exist. Use the observe function directly to observe the empty object
    }
    if(opts.computed) initComputed(vm,opts.computed);
    if(opts.wathch && opts.watch !== nativeWatch) {
        initWathch(vm,opts.wathch);
    }

}
Copy the code

1. Initialize props

  • The parent component provides the data, and the child component selects what it needs through the props field

Normalizing props source code:

function normalizeProps(options,vm) {
    const props = options.props;
    if(! props)return; // If there is no props attribute, the user is not using props to receive any data
    const res = {};
    let i,val,name;
    if(Array.isArray(porps)) { // If this is an Array, normalize props of type Array to object through each item of the while Array
     i = props.length;
     while(i--) {
         val = props[i];
         if(typeof val === 'string') { 
             name = camelzie(val);
             res[name]= {type: null};
         }else if(process.env.NODE_ENV ! = ='production') { // Print warnings in non-production environments
         
             warn('poprs must be strings when using array syntax'); }}}else if(isPlanObjcet(props)) { // The types of the normalized props can be basic functions or arrays,
        for(cosnt key inprops) { val = props[key]; name = camelize(key); res[name] = isPlanObject(val)? val: {type:val}
        }
    
    } else if(process.env.NODE_ENV ! = ='production') {
         
         warn(`Invalid value for option "props":expected an Array or an Object`.`+ but got ${toRawType(props)}. `,vm);
   }
    options.props =res; // Returns the normalized result
}
Copy the code

2. Initialize methods

  1. Iterate over the Methods object in the options, and mount each attribute in turn to the VM
function initMethods (vm,methods) {
    const props - vm.$options.props;
    for (const key in methods) {
        if(process.env.NODE_ENV ! = ='production') {
            if (methods[key] == null) { // In a non-production environment, verify that the method is valid and issue a warning on the console
                warn(
                `Method "${key}" has an undefined value in the compontent definition.` + `Did you reference the function correctly? `,vm); }}if (props && hasOwn(props, key)) {
            warn(
            `Method "${key}" has alreay been defined as a props.`,vm);
        }
        if ((key in vm) && isReserved(key)) {
            warn(
            `Method "${key}" conflicts with an existing Vue instance methods .` +
            `Avoid defining component methods that start with_or $.`,vm);
        } 
    }
    vm[key] = methosd[key] == null ? noop :bind(methods[key],vm)// Mount the method to the VM [key]
}
Copy the code

4. Initialize computed

  • The result of the computed genus we know is cached and recomputed only if the return value of the dependent reactive attribute changes
  • Look at the relationship below

  • Compute properties computed source code
const computedWatcherOptions = {lazy, true};
function initComputed(vm,computed) {
        const watcher = vm._computedWatcher = Object.create(null); // Note that the created object has no prototype. It does not have a _proto_ attribute
    // Calculate attributes in SSR, just a normal getter method
    const isSSR = isServerRending();
    for (const key in computed) {
        const userDef = computed[key];
        const getter = typeof userDef === 'function' ? userDef : userDef.get;
        if(process.env.NODE_ENV ! = ='production' && getter == null) {
            warn(`Getter is missing for computed property "${key}"`);
        }
        // In non-SSR, create an internal observer for the calculated attributes
        if(! isSSR) { watcher[key] =new Watcher(
                vm,
                getter || noop,
                noop,
                computedWatcherOptions
            );
        }
        if(! (keyin vm)) {
            defineComputed(vm, key, userDef);

        } else if(process.env.NODE_ENV ! = ='production') {
            if(key in vm.$data) {
                warn(`The .... "${key} in data `, vm);
            } else if(vm.$options.props && key in vm.$options.props) {
                warn(`The .... "${key} in prop `, vm); }}}}// initComputed initializes computed properties, vm is vue instance context (this)
Copy the code

The initial watch

// initWatch receives 2 arguments, one VM, one watch object,
function initWatch(vm,watch) {
    for(const key in watch) {
        const handler = watch[key];
        if(Array.isArray(handler)) {
            for(let i =0; i<handler.length;i++) {
                createWatcher(vm,key,hnadler);
            }
        } else{ createWatcher(vm,key,handler); }}}// The createWatcher function is responsible for handling other types of handlers and calling the vm.$watch watcher to observe the expression

function createWatcher(vm,expOrFn,handler,optons) {
   if(isPlainObject(handler)) { // If an object is included, set a special included object, so options is set to handler; And handler object handler method
      options = handler; 
      handler = handler.handler;
   }
   if(type handler === 'string') {
       handler = vm[handler];
   }
   return vm.$watch(expOrFn,handler,optons);   
}
// vm:vue execution context
// expOrFn: Representation or audit attribute functions
// Handler watch object value
// options: Options object to pass to vm.$watch

Copy the code

Hook function

  • The same as a callback function. When the system executes somewhere, it checks for a hook and executes a callback if it does

The VUE lifecycle can be divided into eight phases:

  • Before beforeCreate instance creation:

    The first hook fired after new Vue() cannot be accessed in the current phase for data, methods, computed, and data and methods on watch.

  • Created instance created:

    Occurs after the instance has been created. The current phase has completed data observation, that is, data can be used and changed. You can do some initial data fetching, you can’t interact with the Dom at this stage, you can access the Dom via vm.$nextTick if you want to.

  • BeforeMount Before mounting:

    Before this, the Template template has been imported into the rendering function for compilation. At this stage, the virtual Dom has been created and is about to start rendering. Data can also be changed at this point without triggering the updated

  • Mounted: In this stage, the Dom is mounted and data is bidirectional bound. You can access the Dom node and use the $refs attribute to perform operations on the Dom. You can also send a request to the background and get the data back

  • BeforeUpdate Before the update:

    Before the update, which is triggered before the responsive data is updated and the virtual DOM is re-rendered, you can make changes to the data at this stage without re-rendering

  • Updated:

    The component Dom has been updated at this stage. It is important to avoid changing the data during this period, as this may result in an infinite loop of updates

  • BeforeDestory before destruction: The current phase is when the instance is fully available and we can clean up after, such as clearing timers.

  • Destoryed destruction completed:

    At this point, all that’s left is the DOM shell. Components have been disassembled, data bindings removed, listeners removed, and subinstances destroyed

What happens when new Vue() is called

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)
}
exprot default Vue

If vue is not called with new in a non-production environment, this throws an error warning at the console: Vue is a constructor and should be called with the new keyword
// 2. The initialization process of the declaration cycle is implemented in this._init
Copy the code

The inner workings of the _init method

Vue.prototype._init = function (options) {
    vm.$options = mergeOptons(
        resloveConstructorOptions(vm.construtor),
            options||{},
            vm
        );
}
initLifecycle(vm);
initEvents(vm);
initRender(vm);
callHook(vm,'beforeCreate');
initInjections(vm);// Initialize inject before data/props
initState(vm);
initProvide(vm);// Initialize provide after data/props
callHook(vm,'created');

// If the user passes the EL option when instantiating vue, the template compilation phase and mount phase are automatically enabled
// If the user is passing the el option. This does not go to the next lifecycle process
// If you need to execute vm.$mount to manually start the compilation and mounting phases of the template
if(vm.$options.el){
    vm.$mount(vm,$options.el)
}

Copy the code

Hook function source code

1. BeforeCreate and created

// src/core/instance/init
export function initMixin (Vue: Class<Component>) {
  Vue.prototype._init = function (options? :Object) {
    const vm: Component = this.// The merge options section has been omitted
    
    initLifecycle(vm)  
    $parent, $root, $children, and other lifecycle attributes are added to the VM object
    initEvents(vm) // Initializes the event-related attributes
    initRender(vm)  // THE VM adds attributes and methods related to the virtual DOM, slot, etc
    callHook(vm, 'beforeCreate')  // Call the beforeCreate hook
    // the initinjection (VM) and initProvide(VM) injections values defined in the parent component _provided into the child component, and these properties are not observed
    initInjections(vm) 
    initState(vm)   // Initialize data such as props, methods, data, watch, and computed
    initProvide(vm) 
    callHook(vm, 'created')  // Calls the created hook}}// src/core/instance/state
export function initState (vm: Component) {
  vm._watchers = []
  const opts = vm.$options
  if (opts.props) initProps(vm, opts.props)
  if (opts.methods) initMethods(vm, opts.methods)
  if (opts.data) {
    initData(vm)
  } else {
    observe(vm._data = {}, true /* asRootData */)}if (opts.computed) initComputed(vm, opts.computed)
  if(opts.watch && opts.watch ! == nativeWatch) { initWatch(vm, opts.watch) } }// 1. The beforeCreate hook is called before initState, and initState is used to initialize properties such as props, methods, data, computed, and watch.
//2. The beforeCreate hook has no access to data on props, methods, data, computed, or watch. In Created
Copy the code

2. BeforeMounted and mounted


// The mountComponent core is to instantiate a render Watcher
// The updateComponent method is called in its callback
// Two core methods vm._render(generate virtual Dom) and vm._update(map to real Dom)
// src/core/instance/lifecycle
export function mountComponent (vm: Component, el: ? Element, hydrating? : boolean) :Component {
  vm.$el = el
  if(! vm.$options.render) { vm.$options.render = createEmptyVNode ... } callHook(vm,'beforeMount')  // Call beforeMount hook

  let updateComponent
  if(process.env.NODE_ENV ! = ='production' && config.performance && mark) {
    updateComponent = () = > {
    // A function that maps the virtual Dom to the real Dom.
    Vm._render () is called before vm._update.const vnode = vm._render()
      ...
      vm._update(vnode, hydrating)
    }
  } else {
    updateComponent = () = > {
      vm._update(vm._render(), hydrating)
    }
  }

  new Watcher(vm, updateComponent, noop, {
    before () {
     // Check whether mouted is completed and is not destroyed
      if(vm._isMounted && ! vm._isDestroyed) { callHook(vm,'beforeUpdate')}}},true /* isRenderWatcher */)

  if (vm.$vnode == null) {
    vm._isMounted = true
    callHook(vm, 'mounted')  // Call mounted hook
  }
  return vm
}
BeforeMount hook function is executed before vm._render() is used to render VNode, and mouted hook is executed after vm._update() is used to patch VNode to the real Dom. Why did not get Dom properly until mounted stage

Copy the code

BeforeUpdate and updated

  // src/core/instance/lifecycle
 new Watcher(vm, updateComponent, noop, {
    before () {
     // Check whether mouted is completed and is not destroyed
      if(vm._isMounted && ! vm._isDestroyed) { callHook(vm,'beforeUpdate')  // Invoke the beforeUpdate hook}}},true /* isRenderWatcher */)
 
 // src/core/observer/scheduler 
 function callUpdatedHooks (queue) {
   let i = queue.length
   while (i--) {
     const watcher = queue[i]
     const vm = watcher.vm
     if(vm._watcher === watcher && vm._isMounted && ! vm._isDestroyed) {// Only if the current watcher is vm._watcher (i.e. the current render watcher)
       // The updated hook function is executed if the component is mounted and is not destroyed.
       callHook(vm, 'updated')  // Calls the updated hook}}}// src/instance/observer/watcher.js
export default class Watcher {...constructor (
    vm: Component,
    expOrFn: string | Function,
    cb: Function,
    options?: ?Object.// In its constructor, isRenderWatcher,
    // Then assign the current instance of watcher to vm._watcherisRenderWatcher? : boolean) {
    // Push the current wathcer instance to vm._watchers,
    // vm._watcher is used specifically to listen for changes in vm data and then re-render,
    // So it's a rendering dependent watcher, so in the callUpdatedHooks function,
    // The updated hook function is executed only after the vm._watcher callback completes
    this.vm = vm
    if (isRenderWatcher) {
      vm._watcher = this
    }
    vm._watchers.push(this)... }Copy the code

# beforeDestroy and destroyed

 // src/core/instance/lifecycle.js
  // During $destroy, it executes vm.__patch__(vm._vnode, null).
  // Trigger the destruct hook function of its child component, such a recursive call,
  // The destroy hook is executed in the same order as the mounted procedure.
  Vue.prototype.$destroy = function () {
    const vm: Component = this
    if (vm._isBeingDestroyed) {
      return
    }
    callHook(vm, 'beforeDestroy')  // Call the beforeDestroy hook
    vm._isBeingDestroyed = true
    // Some destruction work
    const parent = vm.$parent
    if(parent && ! parent._isBeingDestroyed && ! vm.$options.abstract) { remove(parent.$children, vm) }/ / remove the watchers
    if (vm._watcher) {
      vm._watcher.teardown()
    }
    let i = vm._watchers.length
    while (i--) {
      vm._watchers[i].teardown()
    }
    ...
    vm._isDestroyed = true
    // Invoke the destroy hook on the current Rendered tree
    // If a subcomponent is found, it is destroyed first
    vm.__patch__(vm._vnode, null)
    callHook(vm, 'destroyed')  // Call the Destroyed hook
    // Close all instance listeners.
    vm.$off()
    // Remove the __vue__ reference
    if (vm.$el) {
      vm.$el.__vue__ = null
    }
    // Release circular references
    if (vm.$vnode) {
      vm.$vnode.parent = null}}}Copy the code

Conclusion: