我们今天来说一下new Vue()的这个过程做了些什么?
首先我们找到Vue这个构造函数,如下所示,可以发现options是用户传过来的配置,比如methods,data等等
Vue函数调用了_init()方法,_init方法来自'./init' ,initMixin(Vue)
可以发现initLifeCycle是用来初始化Vue实例的组件生命周期标志位
| init函数中的一些方法的作用 | |
|---|---|
| initLifeCycle(vm) | 初始化Vue实例的组件生命周期标志位 |
| initEvents(vm) | 初始化组件事件监听 |
| initRender(vm) | 初始化渲染方法 |
| callHook(vm,'beforeCreate') | |
| initInjections(vm) | 初始化依赖注入内容,在初始化data,props之前 |
| initState(vm) | 初始化props、data、methods、watch、computed |
| callHook(vm,'created') |
通过上面的代码可以知道,在调用beforeCreate之前,数据还没有初始化完成,data,props,这些属性是访问不到的。 等到了created的时候,数据已经初始化完成,所以这个时候能够访问data、props属性,但是这个时候只是完成了数据的初始化,并没有完成dom的挂载,所以也是无法访问到dom元素的。
这里的initstate方法主要是用来初始化数据,
下面的initData主要是用来初始化data的,可以发现data在定义的时候既可以是对象也可以是函数,然后数据的初始化顺序就是上面initState里面的执行顺序,也就是props、methods、data、computed、watch
下面的是$mount方法,
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
// 获取或查询元素
el = el && query(el)
/* istanbul ignore if */
// vue 不允许直接挂载到body或页面文档上
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
// resolve template/el and convert to render function
if (!options.render) {
let template = options.template
// 存在template模板,解析vue模板文件
if (template) {
if (typeof template === 'string') {
if (template.charAt(0) === '#') {
template = idToTemplate(template)
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && !template) {
warn(
`Template element not found or is empty: ${options.template}`,
this
)
}
}
} else if (template.nodeType) {
template = template.innerHTML
} else {
if (process.env.NODE_ENV !== 'production') {
warn('invalid template option:' + template, this)
}
return this
}
} else if (el) {
// 通过选择器获取元素内容
template = getOuterHTML(el)
}
if (template) {
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
mark('compile')
}
/**
* 1.将temmplate解析ast tree
* 2.将ast tree转换成render语法字符串
* 3.生成render方法
*/
const { render, staticRenderFns } = compileToFunctions(template, {
outputSourceRange: process.env.NODE_ENV !== 'production',
shouldDecodeNewlines,
shouldDecodeNewlinesForHref,
delimiters: options.delimiters,
comments: options.comments
}, this)
options.render = render
options.staticRenderFns = staticRenderFns
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
mark('compile end')
measure(`vue ${this._name} compile`, 'compile', 'compile end')
}
}
}
return mount.call(this, el, hydrating)
}
通过上面的代码可以看出来,
(1)根元素是不可以放在body或者html上的
(2)可以在对象中定义template/render或者直接使用template,el表示元素选择器
(3)最终都会解析成render函数,调用解析的函数,然后template会被解析成render函数
对template解析步骤大概分为以下几步:
(1)将html解析成抽象语法树
(2)将抽象语法树解析成字符串
(3) 生成render函数
在代码里可以看到,生成的render函数最终又会被添加到options配置项上面,然后options会被挂载到Vue实例上,然后会再次调用mount方法。如下图的代码所示
调用mountComponent方法来进行渲染组件,下面是对mountComponent方法的声明
由上图的代码可以看出来,如过没有render函数的话,也就是没有解析模板文件生成render函数,那么会报一个警告,
接着往下看代码
接着会执行beforeMount钩子函数,下面定义updateComponent方法来渲染页面视图,继续往下看,监听组件数据,一旦发生改变,触发beforeUpdate生命周期函数。所以我们看完了他的执行方法,得出结论,updateComponent方法主要执行在vue初始化时声明的render,update方法。由代码可以看出,render函数主要是生成vnode,
接下来看render和update函数 直接上代码
可以看出render函数是Vue实例的options选项里面的。
_update函数主要是调用patch函数,将vnode转化为真实的dom,并且更新到页面中
总结:
new Vue的时候先_init方法
在_init方法里面
(1)定义 $watch等等;
(2)定义 $emit等等事件;
(3)定义_update,$destory生命周期
·调用$mount进行页面的挂载
·挂载的时候主要是通过mountComponent方法
·定义updateComponent更新函数
·执行render函数生成虚拟dom
·_update将vnode生成真实的dom结构,并且渲染到页面上