主题
new Vue的初始化过程做了什么?
1. Vue 构造函数
首先,我们根据入口文件,找到了 vue 构造函数,位于src/core/instance/index.js
文件,内容如下:
js
function Vue(options) {
// 非生产环境下,没有new 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); // new Vue时候会调用
}
// 下面每一个mixin,都是往Vue.prototype上添加一些方法
initMixin(Vue);
stateMixin(Vue);
eventsMixin(Vue);
lifecycleMixin(Vue);
renderMixin(Vue);
export default Vue; // 最终导出Vue,但是这里的Vue并非用户拿到的Vue,后面还会基于aop切片编程,对Vue进一步处理
可以看到,new Vue({})
时候,会执行_init
函数,并将用户传入的options
,也就是包含data
、methods
、computed
、watch
等属性方法的对象,那么问题来了_init()
函数在哪儿定义的呢?答案就是initMixin(Vue)
,这里initMixin
方法执行后, 会在Vue
原型上挂上一个_inti
方法。Vue.prototype._init
。
2. Vue.prototype._init(options)
initMixin()
方法的位置在src/core/instance/init.js
文件。内容如下
js
let uid = 0;
export function initMixin(Vue: Class<Component>) {
Vue.prototype._init = function (options?: Object) {
const vm: Component = this; // _init在Vue里面是this调用的,所以这里的this就是Vue实例
// 当前Vue的唯一标志
vm._uid = uid++;
/****************不是生产环境下进行的性能监控 监控开始位置,可以忽略********************/
let startTag, endTag;
if (process.env.NODE_ENV !== "production" && config.performance && mark) {
startTag = `vue-perf-start:${vm._uid}`;
endTag = `vue-perf-end:${vm._uid}`;
mark(startTag);
}
vm._isVue = true; // 一个标志,避免该对象被响应系统观测
/********** 对 Vue 提供的 props、data、methods等选项进行合并处理 ************/
if (options && options._isComponent) {
// 优化内部组件实例化,因为动态选项合并速度非常慢,内部组件选项需要特殊处理。
initInternalComponent(vm, options); // 其实就是将父组件挂到vm.$options上,减少查找次数,从而优化性能
} else {
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
); // 将子组件的options合并到vm.$options上
}
// 设置渲染函数的作用域代理,其目的是提供更好的提示信息(如:在模板内访问实例不存在的属性,则会在非生产环境下提供准确的报错信息)
if (process.env.NODE_ENV !== "production") {
initProxy(vm);
} else {
vm._renderProxy = vm;
}
// 暴露真实的实例本身
vm._self = vm;
/* ***********执行相关初始化程序及调用初期生命周期函数*********** */
initLifecycle(vm); // 初始化生命周期
initEvents(vm); // 初始化事件
initRender(vm); // 初始化渲染
callHook(vm, "beforeCreate"); // 调用 beforeCreated 钩子
initInjections(vm); // resolve injections before data/props
initState(vm); // 初始化 initProps、initMethods、initData、initComputed、initWatch
initProvide(vm); // resolve provide after data/props
callHook(vm, "created"); // 调用 created 钩子
/****************不是生产环境下进行的性能监控 监控结束位置,可以忽略********************/
if (process.env.NODE_ENV !== "production" && config.performance && mark) {
vm._name = formatComponentName(vm, false);
mark(endTag);
measure(`vue ${vm._name} init`, startTag, endTag);
}
// 根据判断是否有el挂载点,挂载实例
if (vm.$options.el) {
vm.$mount(vm.$options.el);
}
};
}
总结Vue.prototype._init
做的事情:
从Vue.prototype._init
方法可以总结出,new Vue
的时候,主要做了一下事情:
- 用
mergeOptions
方法合并选项 props、data、methods 等(包括组件的查找优化,递归合并子组件选项)都会合并到 vm.$options 上面 - 设置渲染函数的作用域代理,其目的是提供更好的提示信息
- 一些初始化工作和生命周期函数调用
- 初始化生命周期
- 初始化事件
- 初始化渲染
- 调用生命周期钩子函数-
beforeCreate
- 初始化
Inject
- 初始化 state,包括 props、methods、data、computed、watch
- 初始化
provide
- 调用生命周期钩子函数-
created
- 若
el
存在 new Vue 时传入的对象中,则调用实例上面的$mont
方法,并传入el
。
下面来依次分析Vue.prototype._init
内的每一个方法, 主要是这一部分
初始化内置组件
initInternalComponent
这部分暂时跳过
js
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 as any)
} else {
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor as any),
options || {},
vm
)
}
initLifecycle(vm); // 初始化生命周期
initEvents(vm); // 初始化事件
initRender(vm); // 初始化渲染
callHook(vm, "beforeCreate"); // 调用 beforeCreated 钩子
initInjections(vm); // resolve injections before data/props
initState(vm); // 初始化 initProps、initMethods、initData、initComputed、initWatch
initProvide(vm); // resolve provide after data/props
callHook(vm, "created"); // 调用 created 钩子