Vue源码系列一 ———— 流程

338 阅读6分钟

前言

前段时间抽空研究了一下Vue源码,这是源码系列的第一篇,关于Vue源码的整体流程,接下来会不定期更新Vue源码系列。

从new Vue(options)开始说起 先来个最简单的例子

// 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>

开始执行new Vue(options)的_init函数

  Vue.prototype._init = function (options) {
    var vm = this;
    if (options && options._isComponent) { // 这里是子组件vueComonent的_init过程
      initInternalComponent(vm, options); // 这里就是生成子组件的vm.$options并添加属性
    } else { // 这里是new Vue过程,生成Vue实例vm.$options并进行修饰
      vm.$options = mergeOptions(
        resolveConstructorOptions(vm.constructor),
        options || {},
        vm
      );
    }
    // 接下来这里的init就是往vm或者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');
    // 这里是new Vue时执行vm.$mount实现挂载
    if (vm.$options.el) {
      vm.$mount(vm.$options.el);
    }
  };

这里有必要对initInternalComponent函数进行探究

function initInternalComponent (vm, options) {
  // 第一句很重要,vm.$options继承了Sub.options(当前组件vm的构造函数),
  // 所以当调用子组件的render函数的时候vm.$options.render就能访问到render函数了
  var opts = vm.$options = Object.create(vm.constructor.options);
  
  var parentVnode = options._parentVnode;
  opts.parent = options.parent;
  opts._parentVnode = parentVnode; // 这里生成vm.$options._parentVnode,
                                   // 提供给后面执行vm._render时的vm.$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;
  }
}
  // Sub.options
  beforeCreate: (3) [ƒ, ƒ, ƒ]
  beforeDestroy: [ƒ]
  components: {HelloWorld: {…}}
  data: ƒ data()
  destroyed: [ƒ]
  directives: {}
  filters: {}
  methods: {toogle: ƒ}
  mounted: [ƒ]
  name: "App"
  props: {hobby: {…}, aaa: {…}}
  render: ƒ () // 子组件会用到的render函数
  staticRenderFns: []
  watch: {hobby: ƒ}
  _Ctor: {}
  __file: "src/App.vue"
  _base: ƒ Vue(options)
  _compiled: true
  __proto__: Object

解释完initInternalComponent函数(这是子组件才会进入的逻辑),应该开始挂载的步骤,执行vm.mount(vm.mount(vm.options.el)

Vue.prototype.$mount = function (el) {
  el = el && query(el); // 将options.el转为真实的html节点
  var options = this.$options;
  // vm.$mount的这里是Vue实例,会看options是否有用户手写的render函数,没有进入if逻辑
  if (!options.render) {
    var template = options.template;
    if (template) {
      if (typeof template === 'string') {
        if (template.charAt(0) === '#') {
          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; //生成vm.$options.render,方便后续调用
      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');
      }
    }
  }
  // 这里的执行mount中用到的render会有三种情况:
  // 1.如果是vm.$mount的vm指的是Vue实例,进入上面的生成render函数逻辑
  // 2.Vue实例,用户手写的render函数
  // 3.vueComponent的实例,import子组件时获取到的render函数。
  return mount.call(this, el, hydrating) -> mountComponent(this, el, hydrating)
}

然后执行mountComponent函数

function mountComponent (
  vm,
  el,
  hydrating
) {
  vm.$el = el;
  callHook(vm, 'beforeMount');
  var updateComponent;
  updateComponent = function () {
    // new Watcher的时候执行,先执行作为参数的vm._render()函数。
    // 生成vnode作为参数传入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'); // 这个时候执行mounted生命周期函数,前面的new Watcher中已经把节点生成并插入页面中了,所以这个是在节点渲染完毕后执行的,顺序是先子后父。
  }
  return vm
}
  //vm._render()执行的就是这个函数,其实就是执行之前$mount中添加进去的vm.$options.render
  //render函数是什么具体要看当前的vm是谁的实例。
  Vue.prototype._render = function () {
    var vm = this;
    var ref = vm.$options;
    var render = ref.render;
    var _parentVnode = ref._parentVnode;
    // Vue实例中是没有_parentVnode的,所以为undefined,
    // 而VueComponent实例中,_parentVnode就是组件vnode,占位符vnode。
    // 在执行父组件的patch过程的时候,会执行cereateElm -> 
    // createComponent -> init -> createComponentInstanceForVnode生成子组件的vm实例时
    // 会将当前的组件vnode赋值给_parentVnode传入子组件vm._init(options)中
    vm.$vnode = _parentVnode; 
    // render self
    var vnode;

    currentRenderingInstance = vm;
    // new Vue时render函数是这样的
    // (function anonymous() {
    //   with(this){return _c('ul',[_c('app')],1)} // 会先执行参数_c('app'),再执行_c('ul')
    // })
    
    //new VueComponent时render函数是这样的
    //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
  };

执行render函数,会进入_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逻辑是当tag是html节点的时候,也就是普通的节点,直接调用new Vnode生成vnode。
    // else逻辑是tag是自定义节点,其实也就是一个引入的组件。先进入resolveAsset函数,
    // resolveAsset函数的作用就是获取components里面的组件的内容,例如本例中的App,
    // 返回的Ctor就是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);
    }
  }
}

VueComponent实例的render之后进入createComponent函数

function createComponent (
  Ctor,
  data,
  context,
  children,
  tag
) {
  var baseCtor = context.$options._base;
  // 这一句很关键,生成vueComponent构造函数
  if (isObject(Ctor)) {
    Ctor = baseCtor.extend(Ctor);
  }
  // 执行Ctor = baseCtor.extend(Ctor)进入如下函数
  // 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++;
  //   将子组件App.vue里面的内容加入Sub构造函数的options属性里面,子组件的vm.$mount为什么能通过
  //   vm.$options访问到render,其实就是因为这里将render函数添加到Sub构造函数中。
  //   Sub.options = mergeOptions(
  //     Super.options,
  //     extendOptions
  //   );
  //   // 子组件的props和computed是在这里去init初始化的
  //   if (Sub.options.props) {
  //     initProps$1(Sub);
  //   }
  //   if (Sub.options.computed) {
  //     initComputed$1(Sub);
  //   }

  //   // 加入Vue构造函数中的属性
  //   Sub.extend = Super.extend;
  //   Sub.mixin = Super.mixin;
  //   Sub.use = Super.use;
  //   return Sub
  // }
  
  data = data || {};

  // 获取传入子组件的props
  var propsData = extractPropsFromVNodeData(data, Ctor, tag);


  // install component management hooks onto the placeholder node
  installComponentHooks(data);
  // installComponentHooks之后data的值如下,之后执行patch的时候会调用i.init就会调用这里的hook.init(i = vnode.data)
  // hook:
  //   destroy: ƒ destroy(vnode)
  //   init: ƒ init(vnode, hydrating)
  //   insert: ƒ insert(vnode)
  //   prepatch: ƒ 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
}

像这个例子中template = <ul><app></app></ul>,render函数是_c('ul',_c('app')),生成的组件vnode就会被当做children参数传入ul的vnode中。

执行完_render函数之后生成vnode,作为参数开始执行_update函数。

  Vue.prototype._update = function (vnode, hydrating) {
    var vm = this;
    var prevEl = vm.$el; // Vue实例vm.$el有值,就是new Vue(options)传入的options: {el: '#app'},
                         //vueComponent时,这里还没有值,会在下面patch时为vm.$el的赋值
    var prevVnode = vm._vnode;
    //  function setActiveInstance(vm) {
    //    将当前的vm与子组件vm构建父子关系,子组件vm.$options.parent = 当前vm(父)
    //    var prevActiveInstance = activeInstance;
    //    activeInstance = vm;
    
    //    执行完patch执行此函数,将activeInstance还原回当前vm的父级vm,其实意思就是在进入patch
    //    过程的时候需要当前的vm与子组件vm构建父子关系,patch结束后跳出回到当前vm的循环,
    //    activeInstance当然就是当前vm的父级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的,所以prevVnode存在应该发生在更新过程
    if (!prevVnode) {
      // initial render
      vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */);
    } else {
      // updates
      vm.$el = vm.__patch__(prevVnode, vnode);
    }
    restoreActiveInstance(); // 执行完patch调用
    vm._vnode = vnode; // vm = new Vue()时,vnode是组件vnode,如果vm = new Sub()时,vnode是
                      //  App.vue文件里面的template的内容
    if (!prevVnode) {
      // 初始化进入
      vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */);
    } else {
      // 更新时进入
      vm.$el = vm.__patch__(prevVnode, vnode);
    }
  };
function patch (oldVnode, vnode, hydrating, removeOnly) {
    var insertedVnodeQueue = [];
    if (isUndef(oldVnode)) { 
      // 没有oldVnode,说明此时是第一次执行渲染组件vnode时,更新的话也会有oldVnode。
      createElm(vnode, insertedVnodeQueue);
    } else {
      // oldVnode是否是真实的dom
      var isRealElement = isDef(oldVnode.nodeType);
      if (!isRealElement && sameVnode(oldVnode, vnode)) {
        // 此时是执行重新渲染,进入patchVnode,prepatch和diff算法都是在此逻辑之下。
        patchVnode(oldVnode, vnode, insertedVnodeQueue, null, null, removeOnly);
      } else {
        // replacing existing element
        var oldElm = oldVnode.elm; // div#app
        var parentElm = nodeOps.parentNode(oldElm); //body,后续插入页面用
        // create new node
        createElm(
          vnode,
          insertedVnodeQueue
        );
        // 这里的作用是子组件vnode(占位符vnode)去寻找它下面的不是占位符vnode的elm
        if (isDef(vnode.parent)) {
          var ancestor = vnode.parent;
          var patchable = isPatchable(vnode);
          while (ancestor) {
            ancestor.elm = vnode.elm;
            ancestor = ancestor.parent;
          }
        }

        // 移除最外层的父节点
        if (isDef(parentElm)) {
          removeVnodes([oldVnode], 0, 0);
        } else if (isDef(oldVnode.tag)) {
          invokeDestroyHook(oldVnode);
        }
      }
    }

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

都会进入createElm函数

  function createElm (
    vnode,
    insertedVnodeQueue,
    parentElm,
    refElm,
    nested,
    ownerArray,
    index
  ) {
    // 如果不是组件vnode,进来后啥也不做
    // function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {
    //   var i = vnode.data; // vnode.data是只有是组件vnode时才会有的
    //   if (isDef(i)) {
    //     var isReactivated = isDef(vnode.componentInstance) && i.keepAlive;
    //     if (isDef(i = i.hook) && isDef(i = i.init)) {
    //       这里就会执行上面定义的hook.init。
    //       i(vnode, false /* hydrating */); 
    //     }
    //     可以看出组件vnode的插入是在这里进行的
    //     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);
      // 如果存在children,那么将递归调用createElm函数,parentElm是调用createElm时传递进来的参
      // 数,也就是说如果递归调用createElm函数的话,当前的vnode.elm就会被作为children的
      // 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);
    }
  }
  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);
    }
  }
function createComponentInstanceForVnode (
  vnode, // we know it's MountedComponentVNode but flow doesn't
  parent // activeInstance in lifecycle state
) {
  //这是传入vm.init(options)中的参数,所以前面Vue.prototype._init中获取到的
  // options._isComponent和options._parentVnode都是这里传过去的。
  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)
}