构造函数中调用的 this._init 是在哪里定义的呢?正如我们所看到的,构造函数内部并未对这个 ._init 方法进行定义。 快速进行全局搜索源码可以发现 ._init 方法是在名为 initMixin 的函数中添加到 Vue.prototype 上的。

initMixin

this._init 方法被定义在 initMixin 函数中。initMixin 函数在 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);
  }

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

我们来看一下 initMixin 函数的定义,特别简单,接收 Vue 构造函数作为形参,并且为构造函数原型添加了 _init 方法。

  function initMixin (Vue) {
    Vue.prototype._init = function (options) {
      [. . . .]
    };
  }

uid$3

在顶级作用域中, initMixin 上面定义了一个变量 uid$3,这个变量被当做一个计数器,每当创建一个 Vue 实例的时候,都会自增,然后添加为当次创建的 Vue 实例的属性。

  function initMixin (Vue) {
    Vue.prototype._init = function (options) {
      // a uid
      vm._uid = uid$3++;
      [. . . .]
    };
  }

vmthis

_init 方法内部设置了一个 this 的帮助变量。通常情况下,我们会将代表当前函数上下文对象的 this 关键字保存在其他变量中,方便以后使用,比如 self = this。 这里的做法是类似的,将 this 保存在了一个名为 vm 的变量中:

  function initMixin (Vue) {
    Vue.prototype._init = function (options) {
      var vm = this;
      [. . . .]
    };
  }

性能相关

接下来,._init方法中,设置了性能检查相关的内容。

function initMixin (Vue) {
  Vue.prototype._init = function (options) {
    ...
    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);
    }
    ...
  }
}

这里声明了两个变量 startTagendTag.


function initMixin (Vue) {
  Vue.prototype._init = function (options) {
    [. . . .]
    var startTag, endTag;
    [. . . .]
  }
}

然后你可能会注意到这个奇怪的注释:

/* istanbul ignore if */

Istanbul 其实是一个覆盖率测试工具,这里的注释是在告诉 Istanbul 忽略掉 if 语句。

if 语句首先检查的是当前环境是开发环境还是生产环境,然后判断 config.performence 属性是否有设置为 true

function initMixin (Vue) {
  Vue.prototype._init = function (options) {
    [. . . .]
    /* istanbul ignore if */
    if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
    }
    [. . . .]
  }
}

config 对象

这里我们不得不去关注一下 config 这个对象,这个对象声明在别的地方,并且默认的 performance 这个属性是 false

var config = ({
  [. . . .]
    /**
   * Whether to record perf
   */
  performance: false,
  [. . . .]
})

就像注释标记的那样,config.performance 这个属性用来决定 Vue 是否要记录性能。

我们继续回到 _init 方法里面来,if 语句中接下来又检查了一个名为 mark 的变量。

function initMixin (Vue) {
  Vue.prototype._init = function (options) {
    [. . . .]
    /* istanbul ignore if */
    if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
    }
    [. . . .]
  }
}

mark 函数

那我们又不得不去找找看 mark 到底是在哪儿定义的。

  var mark;
  var measure;

  {
    var perf = inBrowser && window.performance;
    /* istanbul ignore if */
    if (
      perf &&
      perf.mark &&
      perf.measure &&
      perf.clearMarks &&
      perf.clearMeasures
    ) {
      mark = function (tag) { return perf.mark(tag); };
      measure = function (name, startTag, endTag) {
        perf.measure(name, startTag, endTag);
        perf.clearMarks(startTag);
        perf.clearMarks(endTag);
        // perf.clearMeasures(name)
      };
    }
  }

查看代码我们会发现,这个mark变量,只会在特定的情况下被赋值。首先呢,他会检查,我们是否在浏览器环境中,然后检查 window.performance 是否存在。

  // Browser environment sniffing
  var inBrowser = typeof window !== 'undefined';
  [. . . .]
  {
    var perf = inBrowser && window.performance;
    [. . . .]
  }

要知道这里在干吗,我们需要去文档查看一下 window 对象的 performance 属性。 MDN中说:

window 对象的 performance 属性返回一个 Performance 对象,可用于收集当前文档的性能信息。 它作为 Performance Timeline APIHigh Resolution Time APINavigation Timing APIUser Timing APIResource Timing API的公开点。性能接口是High Resolution Time API的一部分,可以通过它来访问当前页面性能相关信息。

mark, measure, clearMarks, clearMeasures 都是 Performance 对象上的方法。

  • mark 方法用给定的名字在浏览器的性能输入缓冲区创建一个时间戳。

  • measure 方法在浏览器的性能输入缓冲区中两个指定标记(分别称为开始标记和结束标记)之间创建一个命名时间戳。

  • clearMarks 方法用来移除浏览器的性能输入缓冲区中指定的 mark

  • clearMeasures 方法用来移除浏览器的性能输入缓冲区中指定的 measure

就像Vue API中解释的那样,如果 performance 选项被设置为 true,将会在浏览器的 devtool peformance/timeline 面板中开启对组件初始化、编译、渲染以及组件更新的性能追踪。但是只能在development 模式下生效,并且受限于浏览器,只能在支持 performance.mark 接口的浏览器中使用。

所以,我们继续回头看一下 mark 变量的初始化代码:

{
  var perf = inBrowser && window.performance;
  /* istanbul ignore if */
  if (
    perf &&
    perf.mark &&
    perf.measure &&
    perf.clearMarks &&
    perf.clearMeasures
  ) {
    mark = function (tag) { return perf.mark(tag); };
    measure = function (name, startTag, endTag) {
      perf.measure(name, startTag, endTag);
      perf.clearMarks(startTag);
      perf.clearMarks(endTag);
      perf.clearMeasures(name);
    };
  }
}

如果 perf 对象存在,并且当 perf 对象中存在 markmeasureclearMarksclearMeasures 方法,那么 Vue 就会设置好 markmeasure 函数。

mark 函数接收一个 tag 作为形参,并且返回一个在浏览器性能入口缓存区中用 tag 作为名字的时间戳。

markmeasure 一起,就允许我们在浏览器 devtool perfomance/timeline 面板中跟踪性能。

继续回到_init方法

现在我们知道了 mark 函数是用来做什么的,我们终于可以继续回到 Vue.prototype._init 方法中继续了解代码所做的事情。

下面的代码,检查了是否是开发环境,确认了性能配置选项是否被设置为 true, 还确认了 mark 函数是否存在。如果上述三个检查都通过了,Vue 会设置两个变量 startTagendTag, 然后使用 startTag 作为形参调用 mark 函数。

function initMixin (Vue) {
  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);
    }
    [. . . .]
  }
}