8

代码:

<template>
  <div id="app">
    {{message}}
  </div>
</template>

<script>

export default {
  name: 'app',
  data: function() {
    return {
      message: "Hello World"
    }
  }
}
</script>

<style>
#app {
  text-align: center;
  color: #2c3e50;
  font-family: 'Courier New', Courier, monospace;
  font-size: 18px;
}
</style>

结果:
图片描述

问题:背后发生了什么
阅读源码的第一步是知道如何调试,不会调试就不可能分析出代码的执行逻辑。首先,在项目中我们引入了Vue:import Vue from 'vue'。问题是vue到底从哪里来的。从node_modules中来。在node_modules路径下存在vue文件夹,vue文件夹中存在一个package.json文件,这个文件的作用是描述整个项目的。在这个文件中存在两个配置字段,它们都是程序的主入口文件。

  "main": "dist/vue.runtime.common.js", 
  "module": "dist/vue.runtime.esm.js",

在module的优先级大于main的优先级。在module不存在时,main对应的配置项就是主入口文件。可以看到
dist/vue.runtime.esm.js才是主入口文件。我们是通过

new Vue({
  render: h => h(App),
}).$mount('#app')

创建Vue实例的,因此首先搜索Vue的定义

function Vue (options) {
  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);
}

可以看到Vue构造函数的核心代码只有一行:this._init(options);因此搜索私有化_init方法。由于_init是作为this的一个方法,注意此处的this就是Vue。经过查找_init方法的定义如下:

 Vue.prototype._init = function (options) {
    var vm = this;
    // a uid
    vm._uid = uid$3++;

    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;
    debugger
    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');

    /* 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);
    }
  };
}

读源码需要注意的一点就是不相关的一定要忽略,一旦遵循深度遍历法则读下去,你是一定会失败的。如果方法不对,那还不如不读,睡会觉。可以将上面的代码简化为:

Vue.prototype._init = function (options) {
    var vm = this;
    ...
    vm.$options = mergeOptions(options || {}, vm);
    ...
    initState(vm);
    ...
    if (vm.$options.el) {
      vm.$mount(vm.$options.el);
    }
    ...
  };
}

_init方法总体上做的事情其实并不多,第一项就是合并配置项。比如路由,状态管理,渲染函数。

new Vue({
  store: store,
  router: router,
  render: h => h(App),
}).$mount('#app')

然后初始化状态,initState的方法定义如下:

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);
  }
}

从源码可以看出,initState就是将vue实例中的data,method,computed,watch等数据项做进一步得处理,其实就是做代理以及转化成可观测对象。
数据处理完成之后就将数据挂载到指定的钩子上:vm.$mount(vm.$options.el);

另外需要注意的是,_init方法中有一下一段代码,在上面我为来突出主线而省略了,这就是

    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');

可以看到在initState(vm)执行之前,我们执行了beforeCreate方法,在initState(vm)执行之后,我们执行了created方法。因此在beforeCreate方法中,我们无法直接引用data,method,computed,watch等在initState(vm)中才开始存在的属性。


洛神赋
54 声望8 粉丝

为往圣继绝学,为万世开太平。