实例学习Vue源码第二篇-浅析初始化到挂载过程

实例学习VUE源码(二)

  • Vue实例初始化过程
    • Vue的构造函数
    • Vue原型链中的_init函数
      • 初始过程重点关注函数:
        • 1. vm.$options的mergeOptions
        • 2. initProxy
        • 3. initState函数
        • 4. Vue原型链中的$mount函数
          • mountComponent
            • Vue原型链中的_render 函数
            • Vue原型链中的_update函数

想了解Vue实例初始化过程,可以先学习 VUE的所有初始化过程的思维导图

Vue实例初始化过程

Vue的构造函数

创建Vue实例前,先了解下Vue的构造函数:

function Vue (options) {
     
  if (!(this instanceof Vue)
  ) {
     
    warn('Vue is a constructor and should be called with the `new` keyword');
  }
  this._init(options);
}

Vue原型链中的_init函数

当通过new关键字创建Vue实例时,会调用_init方法,此方法是initMixin函数内部定义在Vue原型链上的方法;

function initMixin (Vue) {
     
    Vue.prototype._init = function (options) {
     
      var vm = this;
      // a uid  Vue实例的一个唯一性标识
      vm._uid = uid$3++;

      var startTag, endTag;
      /* istanbul ignore if */
      if (config.performance && mark) {
     
        startTag = "vue-perf-start:" + (vm._uid);
        endTag = "vue-perf-end:" + (vm._uid);
        mark(startTag);
      }

      // 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 {
     
      	//合并创建Vue实例的option和Vue构造函数选项
        vm.$options = mergeOptions(
          resolveConstructorOptions(vm.constructor),
          options || {
     },
          vm
        );
      }
      /* istanbul ignore else */
      {
     
        initProxy(vm);
      }
      // expose real self
      vm._self = vm;
      initLifecycle(vm);
      initEvents(vm);
      initRender(vm);
      //Vue的beforeCreate的钩子函数,此时访问数据数据和计算数据,不会产生监听;给属性赋新值不会生效;
      callHook(vm, 'beforeCreate');
      initInjections(vm); // resolve injections before data/props
      initState(vm);
      initProvide(vm); // resolve provide after data/props
      //Vue的beforeCreate的钩子函数,此时访问数据数据和计算数据,不会产生监听;但是,可以给属性赋新值
      callHook(vm, 'created');

      /* istanbul ignore if */
      if (config.performance && mark) {
     
        vm._name = formatComponentName(vm, false);
        mark(endTag);
        measure(("vue " + (vm._name) + " init"), startTag, endTag);
      }

      if (vm.$options.el) {
     
      	//挂载实例
        vm.$mount(vm.$options.el);
      }
    };
  }

初始过程重点关注函数:

对performance感兴趣的同学,可以查看文章:window.performance(监控网页与程序性能)

1. vm.$options的mergeOptions

vm.$options值为Vue构造函数选项和Vue实例选项的合并值,实例选项的值会覆盖构造函数选项的值;想详细了解可以直接查看mergeOptions函数;

2. initProxy

initProxy是初始化Vue实例的一个代理(Proxy);
Proxy链接的文章需要注意hander.has会拦截的操作:
1 with 检查: with(proxy) { (foo); }
2 属性查询: foo in proxy
3 继承属性查询: foo in Object.create(proxy)
4 Reflect.has()
hander.get会拦截的操作:
1 访问属性: proxy[foo]和 proxy.bar;
2 访问原型链上的属性: Object.create(proxy)[foo]
3 Reflect.get();

3. initState函数

initState函数中会调用如下初始化函数:
1 initProps 初始化子组件访问的父组件属性;
2 initMethods 初始化函数方法;
3 initData 初始化数据属性;
4 initComputed 初始化计算属性;
5 initWatch 初始化监听属性;
函数中定义了两个钩子函数:

  1. beforeCreate;此时访问属性和属性的设值操作都不生效;
  2. created;此时访问属性操作和属性的设值操作生效;
    挂载操作没完成,所以数据和计算属性的监听器没绑定;

4. Vue原型链中的$mount函数

var mount = Vue.prototype.$mount;
Vue.prototype.$mount = function (
    el,
    hydrating
  ) {
     
    el = el && query(el);

    /* istanbul ignore if */
    if (el === document.body || el === document.documentElement) {
     
      warn(
        "Do not mount Vue to  or  - mount to normal elements instead."
      );
      return this
    }

    var options = this.$options;
    // resolve template/el and convert to render function
    if (!options.render) {
     
      var template = options.template;
      if (template) {
     
        if (typeof template === 'string') {
     
          if (template.charAt(0) === '#') {
     
            template = idToTemplate(template);
            /* istanbul ignore if */
            if (!template) {
     
              warn(
                ("Template element not found or is empty: " + (options.template)),
                this
              );
            }
          }
        } else if (template.nodeType) {
     
          template = template.innerHTML;
        } else {
     
          {
     
            warn('invalid template option:' + template, this);
          }
          return this
        }
      } else if (el) {
     
        template = getOuterHTML(el);
      }
      if (template) {
     
        /* istanbul ignore if */
        if (config.performance && mark) {
     
          mark('compile');
        }

        var ref = compileToFunctions(template, {
     
          outputSourceRange: "development" !== 'production',
          shouldDecodeNewlines: shouldDecodeNewlines,
          shouldDecodeNewlinesForHref: shouldDecodeNewlinesForHref,
          delimiters: options.delimiters,
          comments: options.comments
        }, this);
        var render = ref.render;
        var staticRenderFns = ref.staticRenderFns;
        options.render = render;
        options.staticRenderFns = staticRenderFns;

        /* istanbul ignore if */
        if (config.performance && mark) {
     
          mark('compile end');
          measure(("vue " + (this._name) + " compile"), 'compile', 'compile end');
        }
      }
    }
    return mount.call(this, el, hydrating)
  };

1 $mount函数中compileToFunctions函数编译模板为AST的模型树;
2 调用mount.call(this, el, hydrating);源码如下:

// public mount method,vue实例和vue组件的公用挂载函数
Vue.prototype.$mount = function (
    el,
    hydrating
  ) {
     
    el = el && inBrowser ? query(el) : undefined;
    return mountComponent(this, el, hydrating)
  };

注意点:
1 var mount = Vue.prototype.$mount;这行代码保存了Vue原型链中之前保存的函数;
2 compileToFunctions函数把template编译成AST的模型树,并生成了render的函数表达式;

mountComponent

里面定义了updateComponent函数:

updateComponent = function () {
     
 vm._update(vm._render(), hydrating);
};
Vue原型链中的_render 函数

_render是在renderMixin 函数调用时定义的Vue原型链中的函数

function renderMixin (Vue) {
     
    // install runtime convenience helpers
	//渲染的一些函数放入Vue.prototype
    installRenderHelpers(Vue.prototype);

    Vue.prototype.$nextTick = function (fn) {
     
      return nextTick(fn, this)
    };

    Vue.prototype._render = function () {
     
      var vm = this;
      var ref = vm.$options;
      var render = ref.render;
      var _parentVnode = ref._parentVnode;

      if (_parentVnode) {
     
        vm.$scopedSlots = normalizeScopedSlots(
          _parentVnode.data.scopedSlots,
          vm.$slots,
          vm.$scopedSlots
        );
      }

      // set parent vnode. this allows render functions to have access
      // to the data on the placeholder node.
      vm.$vnode = _parentVnode;
      // render self
      var vnode;
      try {
     
        // There's no need to maintain a stack becaues all render fns are called
        // separately from one another. Nested component's render fns are called
        // when parent component is patched.
        currentRenderingInstance = vm;
		//渲染视图的入口;参考关键字:installRenderHelpers
        vnode = render.call(vm._renderProxy, vm.$createElement);
      } catch (e) {
     
        handleError(e, vm, "render");
        // return error render result,
        // or previous vnode to prevent render error causing blank component
        /* istanbul ignore else */
        if (vm.$options.renderError) {
     
          try {
     
            vnode = vm.$options.renderError.call(vm._renderProxy, vm.$createElement, e);
          } catch (e) {
     
            handleError(e, vm, "renderError");
            vnode = vm._vnode;
          }
        } else {
     
          vnode = vm._vnode;
        }
      } finally {
     
        currentRenderingInstance = null;
      }
      // if the returned array contains only a single node, allow it
      if (Array.isArray(vnode) && vnode.length === 1) {
     
        vnode = vnode[0];
      }
      // return empty vnode in case the render function errored out
      if (!(vnode instanceof VNode)) {
     
        if (Array.isArray(vnode)) {
     
          warn(
            'Multiple root nodes returned from render function. Render function ' +
            'should return a single root node.',
            vm
          );
        }
        vnode = createEmptyVNode();
      }
      // set parent
      vnode.parent = _parentVnode;
      return vnode
    };
  }

_render 函数会执行render的函数表达式,创建出vnode节点;

Vue原型链中的_update函数

_update是在lifecycleMixin 函数中定义的Vue原型链中的函数

function lifecycleMixin (Vue) {
     
	  //更新DOM进行展示
    Vue.prototype._update = function (vnode, hydrating) {
     
      var vm = this;
      var prevEl = vm.$el;
      var prevVnode = vm._vnode;
      var restoreActiveInstance = setActiveInstance(vm);
      vm._vnode = vnode;
      // Vue.prototype.__patch__ is injected in entry points
      // based on the rendering backend used.
      if (!prevVnode) {
     
        // initial render
        vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */);
      } else {
     
        // updates
        vm.$el = vm.__patch__(prevVnode, vnode);
      }
      restoreActiveInstance();
      // update __vue__ reference
      if (prevEl) {
     
        prevEl.__vue__ = null;
      }
      if (vm.$el) {
     
        vm.$el.__vue__ = vm;
      }
      // if parent is an HOC, update its $el as well
      if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {
     
        vm.$parent.$el = vm.$el;
      }
      // updated hook is called by the scheduler to ensure that children are
      // updated in a parent's updated hook.
    };

_update会根据patch(diff原理)函数,对比渲染前后的vnode的差异后,更新具体的视图(View);
函数中定义了两个钩子函数:

  1. beforeMount;此时访问属性操作和属性的设值操作生效;
  2. mounted;此时访问属性操作和属性的设值操作生效;属性的设置操作会重新触发View渲染;

下一篇:实例学习Vue源码第三篇-Vue的响应式原理

你可能感兴趣的:(VUE,vue)