解密VUE框架源码(1)

VUE 其实就是一个 函数式的类

function Vue (options) {   //new vue的时候  把options传入_init方法   _init方法在initMixin函数中 被挂载到了Vue的原型中
  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);
}

定义之后 就会有一系列的初始化的函数 进行init 上面的_init 就是初始化函数 把_init方法定义到了vue 的原型上
初始化函数如下

initMixin(Vue);
stateMixin(Vue);
eventsMixin(Vue);
lifecycleMixin(Vue);
renderMixin(Vue);

首先看initMixin 我们跳到initMixin 看到这里可以看到 往Vue的原型上定义了一个_init方法 这个_init方法在new Vue的时候就会被调用

function initMixin (Vue) {
  Vue.prototype._init = function (options) {
 
    var vm = this; 
  
    // a uid
    vm._uid = uid$3++;  //给这个实例一个uid属性  每new一次 就++

    var startTag, endTag;
    /* istanbul ignore if */
    if (process.env.NODE_ENV !== 'production' && 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 {
      vm.$options = mergeOptions(
        resolveConstructorOptions(vm.constructor),
        options || {},
        vm
      );
    }
    /* istanbul ignore else */
    if (process.env.NODE_ENV !== 'production') {  //生产环境
      initProxy(vm);
    } else {
      vm._renderProxy = vm;
    }
    // expose real self
    vm._self = vm;
    initLifecycle(vm);  //设置生命周期
    initEvents(vm);    //设置事件
    initRender(vm);      //设置render
    callHook(vm, 'beforeCreate');
    initInjections(vm); // resolve injections before data/props
    initState(vm);  //初始化状态 
    initProvide(vm); // resolve provide after data/props
    callHook(vm, 'created');

    /* istanbul ignore if */
    if (process.env.NODE_ENV !== 'production' && 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);   //将options中的 el 传入$mount中  执行完这个$mount函数之后 页面的插值表达式就会被渲染出来
    }
  };
}

先来看这个 initState 函数 这个函数的样子如下

function initState (vm) {
  vm._watchers = []; //在vue的构造函数中设置_watchers
  var opts = vm.$options;
  if (opts.props) { initProps(vm, opts.props); }
  if (opts.methods) { initMethods(vm, opts.methods); }
  if (opts.data) { //初始化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);
  }
}

先来看这个 initData 做了哪些事 从字面的意思来讲就是 初始化我们的data

function initData (vm) { //初始化data
  var data = vm.$options.data;
  console.log("vm.$options.data",vm.$options.data) //这里data是一个函数 返回一个对象 

  data = vm._data = typeof data === 'function'  //判断data是不是函数
    ? getData(data, vm)
    : data || {};
  
  if (!isPlainObject(data)) {
    data = {};
    process.env.NODE_ENV !== 'production' && warn(
      'data functions should return an object:\n' +
      'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
      vm
    );
  }
  // proxy data on instance

  var keys = Object.keys(data);

  var props = vm.$options.props;    
  var methods = vm.$options.methods;
  var i = keys.length;
  while (i--) {
    var key = keys[i];
    if (process.env.NODE_ENV !== 'production') {
      if (methods && hasOwn(methods, key)) {
        warn(
          ("Method \"" + key + "\" has already been defined as a data property."),
          vm
        );
      }
    }
    if (props && hasOwn(props, key)) {
      process.env.NODE_ENV !== 'production' && warn(
        "The data property \"" + key + "\" is already declared as a prop. " +
        "Use prop default value instead.",
        vm
      );
    } else if (!isReserved(key)) {
      proxy(vm, "_data", key);   //代理 原函数在4648行  将每一个key 代理到_data中
      console.log(vm)
    }
  }
  // observe data
  observe(data, true /* asRootData */);
}

上面这个函数中 首先判断了 data是不是函数 如果是函数就调用getData函数 把data函数的返回值赋值给 vm.$options.data 和vm._data
重点看最后的这个 proxy(vm, "_data", key); 这个函数的意思是 把data的每一个key 代理到 vm._data中 代理函数如下

var sharedPropertyDefinition = {
  enumerable: true,
  configurable: true,
  get: noop,
  set: noop
};

function proxy (target, sourceKey, key) {
  sharedPropertyDefinition.get = function proxyGetter () {
    return this[sourceKey][key]
  };
  sharedPropertyDefinition.set = function proxySetter (val) {
    this[sourceKey][key] = val;
  };
  Object.defineProperty(target, key, sharedPropertyDefinition);
}

//意思就是把 vm.key和 vm._data.key进行了一个关联 当你改变vm.key的值的时候 vm._data.key也会跟着改变 你访问例如this.message 实际上访问的是this._data.message 至此 初始化data完毕
我们再回到最初的 initMixin函数中 在最后 有一个 vm.options.el) 在这里 会把组件转为DOM对象
看上去就是把我们定义的el传入进了 $mount方法

而实际上 $mount方法会被先缓存 然后重写 我们刚开始调用的是这个重写后的方法

var mount = Vue.prototype.$mount;   这里是缓存的mount
Vue.prototype.$mount = function (
  el,
  hydrating
) { 
  el = el && query(el);

  /* istanbul ignore if */
  if (el === document.body || el === document.documentElement) {  //$options.el 不允许是html 或者是body
    process.env.NODE_ENV !== 'production' && 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) {  //如果没有定义render
 
    var template = options.template;
    if (template) {
      
      if (typeof template === 'string') {
       
        if (template.charAt(0) === '#') { //如果template:"#first" 类似这种情况
       
          template = idToTemplate(template); //找到first 返回first的所有innerHTML
      
          /* 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) {
      console.log("没有定义template")
      template = getOuterHTML(el);
      console.log(template)
    }
    if (template) {
      console.log("如果定义了template")
      /* istanbul ignore if */  //以下是编译相关  编译为render函数
      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;
      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');
      }
    }
  }
  return mount.call(this, el, hydrating)  //var mount = Vue.prototype.$mount; 
};

可以看出 $mount首先会判断有没有定义 render 如果没有定义 render 但是定义了template 如果这个template是以#开头 就返回这个元素的innerHTML字符串 ,如果没有定义template 就找el的outerHTML
总之不管开始template有没有 最后都会生成template 函数的最后是如果template是有的 就把template编译为rander函数 最后再调用之前缓存起来的mount方法 之前缓存的mount方法是

Vue.prototype.$mount = function (
  el,
  hydrating
) {
  el = el && inBrowser ? query(el) : undefined;
  return mountComponent(this, el, hydrating)
};

这里的重点是mountComponent函数

function mountComponent (
  vm,
  el,
  hydrating
) {
  vm.$el = el;
  
  if (!vm.$options.render) { 
    vm.$options.render = createEmptyVNode;  //创造一个vnode
    if (process.env.NODE_ENV !== 'production') {
      /* istanbul ignore if */
      if ((vm.$options.template && vm.$options.template.charAt(0) !== '#') ||
        vm.$options.el || el) {
        warn(
          'You are using the runtime-only build of Vue where the template ' +
          'compiler is not available. Either pre-compile the templates into ' +
          'render functions, or use the compiler-included build.',
          vm
        );
      } else {
        warn(
          'Failed to mount component: template or render function not defined.',
          vm
        );
      }
    }
  }
  // debugger
  callHook(vm, 'beforeMount');

  var updateComponent;
  /* istanbul ignore if */
  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 () { //updateComponent方法  只要数据一变就会执行
      vm._update(vm._render(), hydrating);  //把数据挂载到dom的函数   render()方法会生成一个VNODE
    };
  }

  // we set this to vm._watcher inside the watcher's constructor
  // since the watcher's initial patch may call $forceUpdate (e.g. inside child
  // component's mounted hook), which relies on vm._watcher being already defined
  new Watcher(vm, updateComponent, noop, {  //渲染 watcher
    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');
  }
  return vm
}

这里面的重点是updateComponent函数 只要数据一变就会执行 render()函数会生成一个vnode 然后_updata会把数据和vnode关联 渲染到页面上

你可能感兴趣的:(解密VUE框架源码(1))