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

initMixin

this._init 方法被定义在 initMixin 函数中。initMixin 函数在 Vue 构造函数定义之后,和其他一组函数一起立即就被调用了,而且这一组函数调用全部接收了 Vue 构造函数作为实参。

1
2
3
4
5
6
7
8
9
10
11
12
13
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 方法。

1
2
3
4
5
function initMixin (Vue) {
Vue.prototype._init = function (options) {
[. . . .]
};
}

uid$3

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

1
2
3
4
5
6
7
function initMixin (Vue) {
Vue.prototype._init = function (options) {
// a uid
vm._uid = uid$3++;
[. . . .]
};
}

vmthis

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

1
2
3
4
5
6
function initMixin (Vue) {
Vue.prototype._init = function (options) {
var vm = this;
[. . . .]
};
}

性能相关

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

1
2
3
4
5
6
7
8
9
10
11
12
13
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.

1
2
3
4
5
6
7
8

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

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

1
/* istanbul ignore if */

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

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

1
2
3
4
5
6
7
8
9
function initMixin (Vue) {
Vue.prototype._init = function (options) {
[. . . .]
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
}
[. . . .]
}
}

config 对象

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

1
2
3
4
5
6
7
8
var config = ({
[. . . .]
/**
* Whether to record perf
*/
performance: false,
[. . . .]
})

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

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

1
2
3
4
5
6
7
8
9
function initMixin (Vue) {
Vue.prototype._init = function (options) {
[. . . .]
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
}
[. . . .]
}
}

mark 函数

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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 是否存在。

1
2
3
4
5
6
7
// 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 变量的初始化代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
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 函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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);
}
[. . . .]
}
}