new Vue 发生了什么
我们从入口开始分析,new Vue 背后发生了什么。在 上一篇 中我们看到了 Vue 的代码在
src/core/instance/index.js中。
import { initMixin } from "./init";
import { stateMixin } from "./state";
import { renderMixin } from "./render";
import { eventsMixin } from "./events";
import { lifecycleMixin } from "./lifecycle";
import { warn } from "../util/index";
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);
}
initMixin(Vue);
stateMixin(Vue);
eventsMixin(Vue);
lifecycleMixin(Vue);
renderMixin(Vue);
export default Vue;
可以看到 Vue 只能通过 new 关键字初始化,然后调用 Vue 原型上的 _init 方法,_init方法是在 initMixin 方法执行的过程中绑定上的。
/* @flow */
import config from "../config";
import { initProxy } from "./proxy";
import { initState } from "./state";
import { initRender } from "./render";
import { initEvents } from "./events";
import { mark, measure } from "../util/perf";
import { initLifecycle, callHook } from "./lifecycle";
import { initProvide, initInjections } from "./inject";
import { extend, mergeOptions, formatComponentName } from "../util/index";
let uid = 0;
export function initMixin(Vue: Class<Component>) {
// 给Vue的原型prototype添加 _init方法,在new Vue初始化实例时调用
Vue.prototype._init = function (options?: Object) {
// this指实例本身
const vm: Component = this;
// a uid 防止多个Vue实例冲突
vm._uid = uid++;
let 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;
// 合并配置
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;
// 初始化生命周期
initLifecycle(vm);
// 初始化事件中心
initEvents(vm);
// 初始化渲染
initRender(vm);
callHook(vm, "beforeCreate");
// 初始化注入
initInjections(vm); // resolve injections before data/props
// 初始化data、props、methods、computed、watch
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);
}
// 执行到如果,如果有
// new Vue({
// el: "#app",
// });
// 则调用$mount 挂载vm,挂载的目标就是把模板渲染成最终的DOM
if (vm.$options.el) {
vm.$mount(vm.$options.el);
}
};
}
export function initInternalComponent(
vm: Component,
options: InternalComponentOptions
) {
const opts = (vm.$options = Object.create(vm.constructor.options));
// doing this because it's faster than dynamic enumeration.
const parentVnode = options._parentVnode;
opts.parent = options.parent;
opts._parentVnode = parentVnode;
const vnodeComponentOptions = parentVnode.componentOptions;
opts.propsData = vnodeComponentOptions.propsData;
opts._parentListeners = vnodeComponentOptions.listeners;
opts._renderChildren = vnodeComponentOptions.children;
opts._componentTag = vnodeComponentOptions.tag;
if (options.render) {
opts.render = options.render;
opts.staticRenderFns = options.staticRenderFns;
}
}
export function resolveConstructorOptions(Ctor: Class<Component>) {
let options = Ctor.options;
if (Ctor.super) {
const superOptions = resolveConstructorOptions(Ctor.super);
const cachedSuperOptions = Ctor.superOptions;
if (superOptions !== cachedSuperOptions) {
// super option changed,
// need to resolve new options.
Ctor.superOptions = superOptions;
// check if there are any late-modified/attached options (#4976)
const modifiedOptions = resolveModifiedOptions(Ctor);
// update base extend options
if (modifiedOptions) {
extend(Ctor.extendOptions, modifiedOptions);
}
options = Ctor.options = mergeOptions(superOptions, Ctor.extendOptions);
if (options.name) {
options.components[options.name] = Ctor;
}
}
}
return options;
}
function resolveModifiedOptions(Ctor: Class<Component>): ?Object {
let modified;
const latest = Ctor.options;
const extended = Ctor.extendOptions;
const sealed = Ctor.sealedOptions;
for (const key in latest) {
if (latest[key] !== sealed[key]) {
if (!modified) modified = {};
modified[key] = dedupe(latest[key], extended[key], sealed[key]);
}
}
return modified;
}
function dedupe(latest, extended, sealed) {
// compare latest and sealed to ensure lifecycle hooks won't be duplicated
// between merges
if (Array.isArray(latest)) {
const res = [];
sealed = Array.isArray(sealed) ? sealed : [sealed];
extended = Array.isArray(extended) ? extended : [extended];
for (let i = 0; i < latest.length; i++) {
// push original options and not sealed options to exclude duplicated options
if (extended.indexOf(latest[i]) >= 0 || sealed.indexOf(latest[i]) < 0) {
res.push(latest[i]);
}
}
return res;
} else {
return latest;
}
}
在 _init 方法中,Vue 主要做了:合并配置、初始化生命周期、初始化事件中心,初始化渲染,初始化 data、props、computed、watcher 等。
总结
可以看到这块逻辑的书写非常明确,把一些不同的功能拆分成不同的函数,在主线的基础上增加不同的功能。
在本篇中我们只看模板和数据是如何渲染成最终的 DOM,所以其他的小逻辑暂时不看。在初始化的最后,如果有 el,则调用 vm.$mount 方法挂载 vm,接下来就是渲染成最终的 DOM,接下来会来分析 Vue 的挂载过程。