虽然我们引入了 vue 但是却还没使用(初始化过程),使用的方式就是通过 new Vue 实例化。所以我们要知道 new Vue 的时候都做了哪些事情(首次渲染过程)?
在源码 src/core/instance/index.js 中,Vue 是通过 Function 的方式定义了一个构造函数,而不是直接使用 ES6 的 Class 定义,主要是为了方便在原型上挂载一些属性和方法。
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 只能通过 new 关键字初始化,然后会调用 this._init 方法(主要用来在其原型上挂载一些属性和方法),该方法在 src/core/instance/init.js 中定义。
Vue.prototype._init = function (options?: Object) {
const vm: Component = this;
// merge options
if (options && options._isComponent) {
initInternalComponent(vm, options);
} else {
vm.$options = mergeOptions(resolveConstructorOptions(vm.constructor), options || {}, vm);
}
// expose real self
vm._self = vm;
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');
if (vm.$options.el) {
vm.$mount(vm.$options.el);
}
};
这里主要列举一些关键的代码,其他的就不一一列举了。Vue 首次渲染过程( Vue.prototype._init )主要包含以下操作。
将 vue 内置的 options 和用户传入的 options 进行合并
vm.$options = mergeOptions(resolveConstructorOptions(vm.constructor), options || {}, vm);
初始化生命周期,给 Vue 实例添加静态属性,$parent、$root、$children、$refs
initLifecycle(vm);
初始化事件中心
initEvents(vm);
初始化 render 函数,给 Vue 实例添加静态属性,$vnode、$slots、$scopedSlots、_c、$createElement。添加响应式属性 $attrs、$listeners
initRender(vm);
调用生命周期钩子函数 beforeCreate
callHook(vm, 'beforeCreate');
在 data、props 初始化之前接收 inject
initInjections(vm); // resolve injections before data/props
初始化 props、methods、data、computed、watch
initState(vm);
在 data、props 之后定义 provide
initProvide(vm); // resolve provide after data/props
调用生命周期钩子函数 created
callHook(vm, 'created');
vue 实例挂载
if (vm.$options.el) {
vm.$mount(vm.$options.el);
}
Vue 的初始化逻辑写的非常清楚,把不同的功能逻辑拆成一些单独的函数执行。
我们的目标是弄清楚模板和数据如何渲染成最终的 DOM,所以各种初始化逻辑我们先不看。在初始化的最后,检测到如果有 el 属性,则调用 vm.$mount 方法挂载 vm(调用完后页面就会渲染出来 Hello Vue !),挂载的目标就是把模板渲染成最终的 DOM。