preface

When we use various frameworks, we can not avoid using a feature, is the lifecycle hook, these hooks, can provide us with a lot of convenience, let us in every phase of data update, can capture its changes.

What we mainly talk about is the life cycle of VUE. Here is an outline:

  • BeforeCreate (before initialization bounds)
  • Created (after you initialize the interface)
  • BeforeMount (before rendering the DOM)
  • Mounted (after dom rendering)
  • BeforeUpdate (before updating data)
  • Updated (data updated)
  • BeforeDestroy (before uninstalling components)
  • Destroyed (component uninstalled)

Today, I will analyze what vue is doing before calling each lifecycle.

The body of the

Take a look at the official lifecycle flowchart:

In fact, this diagram has roughly told us what we have done in each stage, but I think it is necessary to analyze it in detail, so that when we want to implement a framework like VUE in the future, we can know when, what we should do and how to achieve it.

BeforeCreate (before initialization bounds)

function initInternalComponent (vm, options) {
  var opts = vm.$options = Object.create(vm.constructor.options);
  // doing this because it's faster than dynamic enumeration.
  var parentVnode = options._parentVnode;
  opts.parent = options.parent;
  opts._parentVnode = parentVnode;
  opts._parentElm = options._parentElm;
  opts._refElm = options._refElm;

  var 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; }}function resolveConstructorOptions (Ctor) {
  var options = Ctor.options;
  if (Ctor.super) {
    var superOptions = resolveConstructorOptions(Ctor.super);
    var cachedSuperOptions = Ctor.superOptions;
    if(superOptions ! == cachedSuperOptions) {// The super option has changed and the new option needs to be resolved.
      Ctor.superOptions = superOptions;
      // Check for any late modifications/add-on options
      var modifiedOptions = resolveModifiedOptions(Ctor);
      // Update the basic extension options
      if (modifiedOptions) {
        extend(Ctor.extendOptions, modifiedOptions);
      }
      options = Ctor.options = mergeOptions(superOptions, Ctor.extendOptions);
      if(options.name) { options.components[options.name] = Ctor; }}}return options
}
if (options && options._isComponent) {
  initInternalComponent(vm, options);
} else {
  vm.$options = mergeOptions(
    resolveConstructorOptions(vm.constructor),
    options || {},
    vm
  );
}

if(process.env.NODE_ENV ! = ='production') {
  initProxy(vm);
} else {
  vm._renderProxy = vm;
}

vm._self = vm;
initLifecycle(vm);
initEvents(vm);
initRender(vm);
callHook(vm, 'beforeCreate');
Copy the code

If options exists and _isComponent is true, then call the initInternalComponent method. This method optimizes internal component instantiations primarily because dynamic option merging is very slow. And there are no internal component options that need special handling;

If the above conditions are not met, the mergeOptions method is called to merge the properties, and the final return value is assigned to $options. The implementation principle of mergeOptions is described in detail here. Have not understand the friend, can jump here to see;

Make a render interception. The interception is to create the DOM using the vm.$createElement method when the render method is called.

function initLifecycle (vm) {
  var options = vm.$options;

  // Find the first non-abstract parent
  var parent = options.parent;
  if(parent && ! options.abstract) {while (parent.$options.abstract && parent.$parent) {
      parent = parent.$parent;
    }
    parent.$children.push(vm);
  }

  vm.$parent = parent;
  vm.$root = parent ? parent.$root : vm;

  vm.$children = [];
  vm.$refs = {};

  vm._watcher = null;
  vm._inactive = null;
  vm._directInactive = false;
  vm._isMounted = false;
  vm._isDestroyed = false;
  vm._isBeingDestroyed = false;
}
Copy the code

Some parameters are initialized;

function initEvents (vm) {
  vm._events = Object.create(null);
  vm._hasHookEvent = false;
  // init parent additional event
  var listeners = vm.$options._parentListeners;
  if(listeners) { updateComponentListeners(vm, listeners); }}function updateComponentListeners (vm, listeners, oldListeners) {
  target = vm;
  updateListeners(listeners, oldListeners || {}, add, remove$1, vm);
  target = undefined;
}
Copy the code

Initialize events, if _parentListeners exist, to update the component’s event listeners;

function initRender (vm) {
  vm._vnode = null; // The root of the subtree
  vm._staticTrees = null; // v-once cache tree
  var options = vm.$options;
  var parentVnode = vm.$vnode = options._parentVnode; // Placeholder nodes in the parent tree
  var renderContext = parentVnode && parentVnode.context;
  vm.$slots = resolveSlots(options._renderChildren, renderContext);
  vm.$scopedSlots = emptyObject;
  // Bind the createElement FN to this instance so that we get the appropriate rendering context in it.
  vm._c = function (a, b, c, d) { return createElement(vm, a, b, c, d, false); };
  // Normalization always applies to the public version and is used in user-written rendering functions.
  vm.$createElement = function (a, b, c, d) { return createElement(vm, a, b, c, d, true); };

  // Expose $attrs and $Listeners to make it easier to create a HOC.
  // They need to be reactive in order to always update using their HOC
  var parentData = parentVnode && parentVnode.data;

  /* istanbul ignore else */
  if(process.env.NODE_ENV ! = ='production') {
    defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, function () {
      !isUpdatingChildComponent && warn("$attrs is readonly.", vm);
    }, true);
    defineReactive(vm, '$listeners', options._parentListeners || emptyObject, function () {
      !isUpdatingChildComponent && warn("$listeners is readonly.", vm);
    }, true);
  } else {
    defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, null.true);
    defineReactive(vm, '$listeners', options._parentListeners || emptyObject, null.true); }}Copy the code

Initialize rendering, defineReactive use and function, in Vue source code parsing (before instantiation) – the implementation principle of responsive data is explained here, you want to understand can see;

At this point, the beforeCreate method is called.

Created (after you initialize the interface)

initInjections(vm); // Resolve the injection before the data/item
initState(vm);
initProvide(vm); // Provide data/items after solving
callHook(vm, 'created');
Copy the code
function resolveInject (inject, vm) {
  if (inject) {
    // Because the traffic is not enough to clear the cache
    var result = Object.create(null);
    var keys = hasSymbol
      ? Reflect.ownKeys(inject).filter(function (key) {
        return Object.getOwnPropertyDescriptor(inject, key).enumerable
      })
      : Object.keys(inject);

    for (var i = 0; i < keys.length; i++) {
      var key = keys[i];
      var provideKey = inject[key].from;
      var source = vm;
      while (source) {
        if (source._provided && hasOwn(source._provided, provideKey)) {
          result[key] = source._provided[provideKey];
          break
        }
        source = source.$parent;
      }
      if(! source) {if ('default' in inject[key]) {
          var provideDefault = inject[key].default;
          result[key] = typeof provideDefault === 'function'
            ? provideDefault.call(vm)
            : provideDefault;
        } else if(process.env.NODE_ENV ! = ='production') {
          warn(("Injection \"" + key + "\" not found"), vm); }}}return result
  }
}
var shouldObserve = true;

function toggleObserving (value) {
  shouldObserve = value;
}
function initInjections (vm) {
  var result = resolveInject(vm.$options.inject, vm);
  if (result) {
    toggleObserving(false);
    Object.keys(result).forEach(function (key) {
      if(process.env.NODE_ENV ! = ='production') {
        defineReactive(vm, key, result[key], function () {
          warn(
            "Avoid mutating an injected value directly since the changes will be " +
            "overwritten whenever the provided component re-renders. " +
            "injection being mutated: \"" + key + "\" ",
            vm
          );
        });
      } else{ defineReactive(vm, key, result[key]); }}); toggleObserving(true); }}Copy the code

Provide/inject; provide/inject; provide/inject; provide/inject

function initState (vm) {
  vm._watchers = [];
  var 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);
  }
}
Copy the code

After processing inject, we initialize props, methods, data, computed, and watch.

function initProvide (vm) {
  var provide = vm.$options.provide;
  if (provide) {
    vm._provided = typeof provide === 'function'? provide.call(vm) : provide; }}Copy the code

The effect of Provide and Inject is actually the same, but the treatment is different. Please see the official document: Provide/Inject;

So once we’re done here, we’re going to go to the created hook.

BeforeMount (before rendering the DOM)

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

Before rendering the DOM, check to see if the render location exists. If not, it will not be registered.

Vue.prototype.$mount = function (el, hydrating) {
  el = el && inBrowser ? query(el) : undefined;
  return mountComponent(this, el, hydrating)
};
function mountComponent (vm, el, hydrating) {
  vm.$el = el;
  if(! vm.$options.render) { vm.$options.render = createEmptyVNode; } callHook(vm,'beforeMount');
}
Copy the code

BeforeMount does nothing but make a render method and bind it to createEmptyVNode if it exists;

After binding, the beforeMount hook is executed;

Mounted (after dom rendering)

  var updateComponent;
  if(process.env.NODE_ENV ! = ='production' && config.performance && mark) {
    updateComponent = function () {
      var name = vm._name;
      var id = vm._uid;
      var startTag = "vue-perf-start:" + id;
      var endTag = "vue-perf-end:" + id;

      mark(startTag);
      var 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 = function () {
      vm._update(vm._render(), hydrating);
    };
  }

  // We set it to vm._watcher in the constructor of the observer, because the initial patch of the observer might call $forceUpdate (for example, within the mount hook of the child component), which depends on the defined Vm._watcher
  new Watcher(vm, updateComponent, noop, null.true /* isRenderWatcher */);
  hydrating = false;

  // The manually mounted instance, in its own mounted call, mounts the child component call created for the render in its insert hook
  if (vm.$vnode == null) {
    vm._isMounted = true;
    callHook(vm, 'mounted');
  }
Copy the code

The _render method is called in the new Watcher to render the DOM. The _render method is used to render the DOM.

If $node does not exist after instantiating Watcher, initialize render and execute mounted hook.

BeforeUpdate (before updating data)

Vue.prototype._update = function (vnode, hydrating) {
    var vm = this;
    if (vm._isMounted) {
      callHook(vm, 'beforeUpdate'); }};Copy the code

Call the beforeUpdate hook directly if _isMounted is true for the current vue instance.

_isMounted is set to true before the mounted hook executes.

Execute the beforeUpdate hook;

Updated (data updated)

function callUpdatedHooks (queue) {
  var i = queue.length;
  while (i--) {
    var watcher = queue[i];
    var vm = watcher.vm;
    if (vm._watcher === watcher && vm._isMounted) {
      callHook(vm, 'updated'); }}}Copy the code

Because when there are multiple components, there will be many watcher, here, is to check the current watcher is which, is the current, directly execute the current updated hook.

BeforeDestroy (before uninstalling components)

Vue.prototype.$destroy = function () {
    var vm = this;
    if (vm._isBeingDestroyed) {
      return
    }
    callHook(vm, 'beforeDestroy');
};
Copy the code

Before uninstalling, check whether it has been uninstalled. If it has been uninstalled, return it directly.

Execute the beforeDestroy hook;

Destroyed (component uninstalled)

vm._isBeingDestroyed = true; Var parent = vm.$parent;
if(parent && ! parent._isBeingDestroyed && ! vm.$options.abstract) {
  remove(parent.$children, vm); } // Disassemble the observerif (vm._watcher) {
  vm._watcher.teardown();
}
var i = vm._watchers.length;
while(i--) { vm._watchers[i].teardown(); } // Remove references from the frozen object's data that may have no observers.if(vm._data.__ob__) { vm._data.__ob__.vmCount--; } // Prepare to execute the last hook vm._isDestroyed =true; // Call Destroyed hook vm.__patch__(v._vnode, null) on the currently rendered tree; callHook(vm,'destroyed');
Copy the code

In fact, here is to delete all traces of their own place;

Execute the Destroyed hook.

conclusion

At this point, we already have a pretty good idea of what each lifecycle hook does, so this large amount of code might not seem very convenient, so let’s make a summary list:

  • beforeCreate: Initializes some parameters, if there are the same parameters, do the parameter merge, executebeforeCreate
  • created: is initializedInjectProvidepropsmethodsdatacomputedwatch, the implementation ofcreated
  • beforeMount: Check whether it existselProperty to render if it existsdomTo performbeforeMount
  • mounted: instantiateWatcherTo renderdom, the implementation ofmounted
  • beforeUpdate: the renderingdomAfter the executionmountedAfter the hook, when the data is updated, executebeforeUpdate
  • updated: Checks the currentwatcherIn the list, whether there is a current to update datawatcher, if it existsupdated
  • beforeDestroy: Checks whether the system has been uninstalled. If yes, go toreturnGet out or executebeforeDestroy
  • destroyed: To delete all traces of their own place;

conclusion

Vue lifecycle implementation, first when it comes to here, in some places, not many details, because the articles and source code parsing direction and purpose is not the same as before, the source code to explain in detail the purpose is to let people step by step to understand, write, and the purpose of this article is to let everybody know to each life cycle stage, all done.

If you feel there is a problem, or write a bad place, please directly comment below to point out, thanks.