preface

Some time ago, I took the time to study the Vue source code, which is the first of the source code series, about the overall process of Vue source code, and then will update the Vue source code series irregularly.

Let’s start with new Vue(Options) as the simplest example

// main.js
import App from './App'
new Vue({
  el: '#app',
  router,
  components: { App },
  template: `<ul><app></app></ul>`
})

// App.vue
<template>
  <div id="app">
  </div>
</template>
Copy the code

Start executing _init function of new Vue(options)

  Vue.prototype._init = function (options) {
    var vm = this;
    if (options && options._isComponent) { // Here is the _init procedure for the vueComonent child
      initInternalComponent(vm, options); // Here is the vm.$options to generate the child component and add attributes
    } else { // Here is the new Vue procedure, which generates the Vue instance vm.$options and decorates it
      vm.$options = mergeOptions(
        resolveConstructorOptions(vm.constructor),
        options || {},
        vm
      );
    }
    // Init adds attributes to vm or vm.$options
    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');
    $mount = new Vue
    if(vm.$options.el) { vm.$mount(vm.$options.el); }};Copy the code

It’s worth exploring the initInternalComponent function here

function initInternalComponent (vm, options) {
  $options inherits sub. options (the constructor of the current component VM).
  // Vm. $options.render accesses the render function when the child component's render function is called
  var opts = vm.$options = Object.create(vm.constructor.options);
  
  var parentVnode = options._parentVnode;
  opts.parent = options.parent;
  opts._parentVnode = parentVnode; $options._parentVnode,
                                   $vnode = _parentVnode; $vnode = _parentVnode;

  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; }}Copy the code
  // Sub.options
  beforeCreate: (3) / ƒ, ƒ, ƒbeforeDestroy: [ƒ]
  components: {HelloWorld: {... }}data: ƒ data ()destroyed: [ƒ]
  directives: {}
  filters: {}
  methods: {toogle: ƒ}
  mounted: [ƒ]
  name: "App"
  props: {hobby: {... },aaa: {... }}render: ƒ () // The render function used by the child component
  staticRenderFns: []
  watch: {hobby: ƒ}
  _Ctor: {}
  __file: "src/App.vue"
  _base: ƒ Vue (options)_compiled: true
  __proto__: Object
Copy the code

After explaining the initInternalComponent function (which is the logic only child components enter), you should begin the mount step by executing vm.mount(vm.mount(vm. mount(vm.options.el).

Vue.prototype.$mount = function (el) {
  el = el && query(el); // Convert options.el to a real HTML node
  var options = this.$options;
  // Vue.$mount = Vue; // Vue.$mount = Vue
  if(! options.render) {var template = options.template;
    if (template) {
      if (typeof template === 'string') {
        if (template.charAt(0) = = =The '#') {
          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) {
        template = template.innerHTML;
      } else {
        if(process.env.NODE_ENV ! = ='production') {
          warn('invalid template option:' + template, this);
        }
        return this}}else if (el) {
      template = getOuterHTML(el);
    }
    if (template) {
      /* istanbul ignore if */
      if(process.env.NODE_ENV ! = ='production' && config.performance && mark) {
        mark('compile');
      }
      var ref = compileToFunctions(template, {
        outputSourceRange: process.env.NODE_ENV ! = ='production'.shouldDecodeNewlines: shouldDecodeNewlines,
        shouldDecodeNewlinesForHref: shouldDecodeNewlinesForHref,
        delimiters: options.delimiters,
        comments: options.comments
      }, this);

      var render = ref.render;
      var staticRenderFns = ref.staticRenderFns;
      options.render = render; // Generate vm.$options.render for subsequent calls
      options.staticRenderFns = staticRenderFns;

      /* istanbul ignore if */
      if(process.env.NODE_ENV ! = ='production' && config.performance && mark) {
        mark('compile end');
        measure(("vue " + (this._name) + " compile"), 'compile'.'compile end'); }}}// There are three cases of render used in mount:
  // 1. If vm.$mount vm refers to Vue instance, go to render function logic above
  // 2.Vue instance, user handwritten render function
  VueComponent instance, import the render function obtained when the child component.
  return mount.call(this, el, hydrating) -> mountComponent(this, el, hydrating)
}
Copy the code

Then execute the mountComponent function

function mountComponent (vm, el, hydrating) {
  vm.$el = el;
  callHook(vm, 'beforeMount');
  var updateComponent;
  updateComponent = function () {
    // execute vm._render() as an argument.
    // Generate a vnode as a parameter to vm._update.
    vm._update(vm._render(), hydrating); 
  };

  new Watcher(vm, updateComponent, noop, {
    before: function 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'); // The mounted life cycle function is executed after the nodes are rendered. // The mounted life cycle function is executed after the nodes are rendered.
  }
  return vm
}
Copy the code
  //vm._render() executes this function, which is the vm.$options.render added to $mount before executing it
  // What the render function is depends on whose instance the current VM is.
  Vue.prototype._render = function () {
    var vm = this;
    var ref = vm.$options;
    var render = ref.render;
    var _parentVnode = ref._parentVnode;
    // The Vue instance does not have _parentVnode.
    // In VueComponent, _parentVnode is the component vnode, the placeholder vnode.
    // cereateElm ->
    / / the createComponent - > init - > createComponentInstanceForVnode generated subcomponents vm instances
    // Assign the current component vnode to _parentVnode and pass it into the child component vm._init(options)
    vm.$vnode = _parentVnode; 
    // render self
    var vnode;

    currentRenderingInstance = vm;
    // New Vue render function looks like this
    // (function anonymous() {
    / / with the (this) {return _c (' ul '[_c (' app')], 1)} / / will first perform parameter _c (' app '), to perform _c (' ul ')
    // })
    
    // New VueComponent render function looks like this
    //var render = function() {
    // var _vm = this
    // var _h = _vm.$createElement
    // var _c = _vm._self._c || _h
    // return _c(
    // "div",
    // { attrs: { id: "app1" } },
    / / /
    // _c("hello-world"),
    // _vm._v(" "),
    // _c("div", [_vm._v(_vm._s(_vm.hobby.ball))]),
    // _vm._v(" "),
    // _c("h1", { on: { click: _vm.toogle } }, [_vm._v("button")])
    / /,
    / / 1
    / /)
    / /}

    vnode = render.call(vm._renderProxy, vm.$createElement);

    // set parent
    vnode.parent = _parentVnode;
    return vnode
  };
Copy the code

Execute the render function to go to _c -> createElement -> _createElement

function _createElement (context, tag, data, children, normalizationType) {
  if (typeof tag === 'string') {
    var Ctor;
    ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag);
    // If logic is to call new Vnode to generate Vnode when tag is an HTML node, i.e. a normal node.
    // Else the tag is a custom node, which is essentially an imported component. Let's go to the resolveAsset function,
    // The resolveAsset function retrieves the contents of components, such as App in this example.
    // The Ctor returned is the contents of app.vue.
    if (config.isReservedTag(tag)) {
      vnode = new VNode(
        config.parsePlatformTagName(tag), data, children,
        undefined.undefined, context
      );
    } else if((! data || ! data.pre) && isDef(Ctor = resolveAsset(context.$options,'components', tag))) { vnode = createComponent(Ctor, data, context, children, tag); }}}Copy the code

The VueComponent instance’s render is followed by the createComponent function

function createComponent (Ctor, data, context, children, tag) {
  var baseCtor = context.$options._base;
  // The vueComponent constructor is generated
  if (isObject(Ctor)) {
    Ctor = baseCtor.extend(Ctor);
  }
  // Call Ctor = baseCtor. Extend (Ctor) to enter the following function
  // Vue.extend = function (extendOptions) {
  // extendOptions = extendOptions || {};
  // var Super = this;
  // var SuperId = Super.cid;
  // var cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {});
  // if (cachedCtors[SuperId]) {
  // return cachedCtors[SuperId]
  / /}
  // var Sub = function VueComponent (options) {
  // this._init(options);
  / /};
  // Sub.prototype = Object.create(Super.prototype);
  // Sub.prototype.constructor = Sub;
  // Sub.cid = cid++;
  // Add the contents of the subcomponent app. vue to the options property of the Sub constructor
  // vm.$options calls render because it adds the render function to the Sub constructor.
  // Sub.options = mergeOptions(
  // Super.options,
  // extendOptions
  / /);
  The props and computed of the // // child components are initialized here by init
  // if (Sub.options.props) {
  // initProps$1(Sub);
  / /}
  // if (Sub.options.computed) {
  // initComputed$1(Sub);
  / /}

  // // adds attributes to the Vue constructor
  // Sub.extend = Super.extend;
  // Sub.mixin = Super.mixin;
  // Sub.use = Super.use;
  // return Sub
  // }
  
  data = data || {};

  // Get props for the child component passed in
  var propsData = extractPropsFromVNodeData(data, Ctor, tag);


  // install component management hooks onto the placeholder node
  installComponentHooks(data);
  Init (I = vnode.data); // installComponentHooks (I = vnode.data);
  // hook:
  //   destroy: ƒ destroy(vnode)
  // init: ƒ init(vnode, hydrating)
  //   insert: ƒ insert(vnode)
  ƒ prepatch(oldVnode, VNode)
  // __proto__: Object
  // on: undefined
  // __proto__: Object

  // return a placeholder vnode
  var name = Ctor.options.name || tag;
  var vnode = new VNode(
    ("vue-component-" + (Ctor.cid) + (name ? ("-" + name) : ' ')),
    data, undefined.undefined.undefined, context,
    { Ctor: Ctor, propsData: propsData, listeners: listeners, tag: tag, children: children },
    asyncFactory
  );

  return vnode
}
Copy the code

Template =

, render function is _c(‘ul’,_c(‘app’)), the generated component vnode will be passed to ul’s vnode as children.

After the _render function is executed, a VNode is generated as an argument to start the _update function.

  Vue.prototype._update = function (vnode, hydrating) {
    var vm = this;
    var prevEl = vm.$el; $el = new Vue(options); options: {el: '#app'};
                         //vueComponent, where there is no value yet, will be assigned to vm.$el when patch below
    var prevVnode = vm._vnode;
    // function setActiveInstance(vm) {
    $options.parent = current VM (parent);
    // var prevActiveInstance = activeInstance;
    // activeInstance = vm;
    
    ActiveInstance is restored to the parent VM of the current VM
    // During the process, the current VM and the sub-component VM are required to build the parent-child relationship. After the patch is finished, the loop jumps back to the current VM.
    // activeInstance is of course the parent VM of the current VM
    
    // return function () {
    // activeInstance = prevActiveInstance;
    / /}
    / /}
    var restoreActiveInstance = setActiveInstance(vm);
    vm._vnode = vnode;
    // Vue.prototype.__patch__ is injected in entry points
    // based on the rendering backend used.
    PrevVnode is not present in the first render, so the prevVnode should be present in the update process
    if(! prevVnode) {// initial render
      vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */);
    } else {
      // updates
      vm.$el = vm.__patch__(prevVnode, vnode);
    }
    restoreActiveInstance(); // Complete the patch call
    vm._vnode = vnode; // vnode is a component vnode when vm = new Vue(), and vnode is a component vnode when VM = new Sub()
                      // the contents of template in the app. vue file
    if(! prevVnode) {// Initialize entry
      vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */);
    } else {
      // Enter when updatingvm.$el = vm.__patch__(prevVnode, vnode); }};Copy the code
function patch (oldVnode, vnode, hydrating, removeOnly) {
    var insertedVnodeQueue = [];
    if (isUndef(oldVnode)) { 
      // There is no oldVnode, indicating that this is the first time to execute the rendering component vnode, and the update will also have oldVnode.
      createElm(vnode, insertedVnodeQueue);
    } else {
      // Whether oldVnode is a real DOM
      var isRealElement = isDef(oldVnode.nodeType);
      if(! isRealElement && sameVnode(oldVnode, vnode)) {// It is time to re-render and enter patchVnode. Prepatch and diff algorithms are all under this logic.
        patchVnode(oldVnode, vnode, insertedVnodeQueue, null.null, removeOnly);
      } else {
        // replacing existing element
        var oldElm = oldVnode.elm; // div#app
        var parentElm = nodeOps.parentNode(oldElm); //body is used to insert pages later
        // create new node
        createElm(
          vnode,
          insertedVnodeQueue
        );
        // The function here is that the child vnode (placeholder vnode) looks for elm that is not placeholder vnode
        if (isDef(vnode.parent)) {
          var ancestor = vnode.parent;
          var patchable = isPatchable(vnode);
          while(ancestor) { ancestor.elm = vnode.elm; ancestor = ancestor.parent; }}// Remove the outermost parent node
        if (isDef(parentElm)) {
          removeVnodes([oldVnode], 0.0);
        } else if (isDef(oldVnode.tag)) {
          invokeDestroyHook(oldVnode);
        }
      }
    }

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

All enter the createElm function

  function createElm (vnode, insertedVnodeQueue, parentElm, refElm, nested, ownerArray, index) {
    // If it is not a component vNode, do nothing after coming in
    // function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {
    // var i = vnode.data; // vnode.data is available only for component vNodes
    // if (isDef(i)) {
    // var isReactivated = isDef(vnode.componentInstance) && i.keepAlive;
    // if (isDef(i = i.hook) && isDef(i = i.init)) {
    // This will execute the hook. Init defined above.
    // i(vnode, false /* hydrating */);
    / /}
    // We can see that the component vNode is inserted here
    // if (isDef(vnode.componentInstance)) {
    // initComponent(vnode, insertedVnodeQueue);
    // insert(parentElm, vnode.elm, refElm);
    // if (isTrue(isReactivated)) {
    // reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm);
    / /}
    // return true
    / /}
    / /}
    // }
    if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {
      return
    }

    var data = vnode.data;
    var children = vnode.children;
    var tag = vnode.tag;
    if (isDef(tag)) {
      vnode.elm = vnode.ns
        ? nodeOps.createElementNS(vnode.ns, tag)
        : nodeOps.createElement(tag, vnode);
      setScope(vnode);
      // If children exist, the createElm function is recursively called. ParentElm is the argument passed in when createElm is called
      The current vnode. Elm will be called as children's
      // parentElm
      {
        createChildren(vnode, children, insertedVnodeQueue);
        insert(parentElm, vnode.elm, refElm);
      }

      if(process.env.NODE_ENV ! = ='production'&& data && data.pre) { creatingElmInVPre--; }}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
  hook.init = function init (vnode, hydrating) {
    if( vnode.componentInstance && ! vnode.componentInstance._isDestroyed && vnode.data.keepAlive ) {// kept-alive components, treat as a patch
      var mountedNode = vnode; // work around flow
      componentVNodeHooks.prepatch(mountedNode, mountedNode);
    } else {
      var child = vnode.componentInstance = createComponentInstanceForVnode(
        vnode,
        activeInstance
      );
      child.$mount(hydrating ? vnode.elm : undefined, hydrating); }}Copy the code
function createComponentInstanceForVnode (
  vnode, // we know it's MountedComponentVNode but flow doesn't
  parent // activeInstance in lifecycle state
) {
  // This is the parameter passed in vm.init(options), so vue.prototype. _init was retrieved earlier
  // Options. _isComponent and options._parentVnode are passed here.
  var options = {
    _isComponent: true._parentVnode: vnode,
    parent: parent
  };
  // check inline-template render functions
  var inlineTemplate = vnode.data.inlineTemplate;
  if (isDef(inlineTemplate)) {
    options.render = inlineTemplate.render;
    options.staticRenderFns = inlineTemplate.staticRenderFns;
  }
  return new vnode.componentOptions.Ctor(options)
}
Copy the code