0.从new一个Vue对象开始
let vm = new Vue({
el: '#app',
/*some options*/
});
在new一个Vue对象的时候,内部究竟发生了什么?
1.Vue构造函数
Vue的构造类只做了一件事情,就是调用_init函数进行初始化。
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)
}
2._init,初始化
_init主要做了这两件事:
- 初始化(包括生命周期、事件、render 函数、state 等)。
- $mount 组件。
Vue.prototype._init = function (options?: Object) {
const vm: Component = this
vm._isVue = true
vm._self = vm
initLifecycle(vm)
initEvents(vm)
initRender(vm)
callHook(vm, 'beforeCreate')
initInjections(vm)
initState(vm)
initProvide(vm)
callHook(vm, 'created')
if (vm.$options.el) {
/*把组件挂载到页面成为真实DOM*/
vm.$mount(vm.$options.el)
}
}
initData
- 首先判断data是不是函数,如果是函数的话执行getData。getData做的事情很简单:
return data.call(vm, vm);
,因为vue推荐我们把data定义为一个函数然后返回具体的data对象,因此getData只需执行一次data拿到返回值即可。 - 判断是否与props属性名冲突,然后将data挂载到vue实例中。这里是通过proxy对vue实例的getter,setter做一层代理转发,比如this.message访问data时实际上转发到this.data.message。
- 对数据做响应式化。
function initData(vm: Component) {
let data = vm.$options.data;
data = vm._data = typeof data === "function" ? getData(data, vm) : data || {};
const keys = Object.keys(data);
const props = vm.$options.props;
const methods = vm.$options.methods;
let i = keys.length;
while (i--) {
const key = keys[i];
if (props && hasOwn(props, key)) {
process.env.NODE_ENV !== "production" &&
warn(
`The data property "${key}" is already declared as a prop. ` +
`Use prop default value instead.`,
vm
);
} else if (!isReserved(key)) {
// 将data挂载到vue实例中
proxy(vm, `_data`, key);
}
}
observe(data, true /* asRootData */);
}
export function proxy(target: Object, sourceKey: string, key: string) {
sharedPropertyDefinition.get = function proxyGetter() {
return this[sourceKey][key];
};
sharedPropertyDefinition.set = function proxySetter(val) {
this[sourceKey][key] = val;
};
Object.defineProperty(target, key, sharedPropertyDefinition);
}
3.mount,将vue实例挂载到页面
-
首先将原型上的$mount方法缓存下来。
-
然后重新定义$mount方法。
之所以这么设计完全是为了复用,因为原生的$mount方法是可以被runtime only版本的 Vue 直接使用的。而在runtime-compiler版本中需要添加额外的逻辑。
-
接下来的是很关键的逻辑 —— 如果没有定义 render 方法,则会把 template 字符串或者是 el 编译成 render 函数。 即调用compileToFunctions处理模板templete。如果存在render方法则优先使用render。
const mount = Vue.prototype.$mount
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
el = el && query(el)
/* 对 el 做了限制,Vue 不能挂载在 body、html 这样的根节点上 */
if (el === document.body || el === document.documentElement) {
process.env.NODE_ENV !== 'production' && warn(
`Do not mount Vue to <html> or <body> - mount to normal elements instead.`
)
return this
}
const options = this.$options
if (!options.render) {
let template = options.template
/*template存在的时候取template,不存在的时候取el的outerHTML*/
if (template) {
/*当template是字符串的时候*/
if (typeof template === 'string') {
if (template.charAt(0) === '#') {
template = idToTemplate(template)
if (process.env.NODE_ENV !== 'production' && !template) {
warn(
`Template element not found or is empty: ${options.template}`,
this
)
}
}
} else if (template.nodeType) {
/*当template为DOM节点的时候*/
template &#