Vue如何渲染DOM (第二章)
当我们new Vue时发生了什么?
接下来我们就来探究Vue
是如何将该页面进行渲染的。在研究之前,我们必须了解Vue
构造函数。但是在执行Vue
函数前,当我们import Vue from 'vue'
的时候,该框架做了这样的几个初始化:
initMixin(Vue)
//传入Vue,向其原型上添加_init函数
stateMixin(Vue)
//传入Vue,向其原型上添加 $set,$delete,$watch函数 并做其他初始化定义
eventsMixin(Vue)
//传入Vue,向其原型上添加 $on,$once,$off,$emit函数,并做其他初始化定义
lifecycleMixin(Vue)
//传入Vue,向其原型上添加 _update,$forceUpdate,$destroy函数,并做其他初始化定义
renderMixin(Vue)
//renderMixin函数向Vue原型添加 $nextTick,_render,在此之前执行了installRenderHelpers(Vue.prototype) 这个函数的执行也向Vue,.prototype上添加了很多内置的函数
//以上的5个函数的作用是向Vue.prototype上添加属性和方法
//对Vue进行包装
export default Vue
//当我们new Vue({})时就进入了上面定义的Vue函数了
其实上面的函数是在我们导入Vue
时,该框架本身做的一些初始化,这和我们执行Vue
构造函数时内部的初始化不一样。这些函数主要是在我们使用Vue
前,向Vue
构造函数的原型上添加一些函数。而当我们执行Vue
构造函数时,也会在内部进行一些初始化,例如响应式的完成和数据代理。所以我们需要区分这两种初始化。
在我们import Vue from 'vue’的时候,主要做的是向
Vue
构造函数的原型上添加一些函数或属性。
然后我们就可以执行Vue
构造函数了。当我们执行Vue
构造函数的时候,本质上其实就是执行Vue.prototype._init()
方法。
function Vue (options) {
//options是我们传入的配置项
//这个就是大名鼎鼎的vue构造函数,所有的vue项目的开始的地方
if (process.env.NODE_ENV !== 'production' &&
!(this instanceof Vue)
//这里的this 其实使 vm实例,看是否用了new Vue
) {
warn('Vue is a constructor and should be called with the `new` keyword')
}
//这里的this是我们执行new Vue 时构造函数内部生成的实例对象,也可以理解为vm也就是组件的实例对象,这里是根组件的实例对象
this._init(options)
//从这个函数进入我们用initMixin(Vue)初始化添加的_init函数
}
这个this._init()
是在initMixin()
函数中声明的。this._init()
函数中做了很多事情,比如合并options
,执行一些函数:
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')
不过这些暂时不是我们关注的重点,重点是后面的代码,即关于渲染的代码。不过需要注意的是,这里的渲染指的是Vue
已经完成了绝大部分事情,只剩下将浏览器上的视图重新渲染。
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
如果我们在vue
构造函数的配置项中传入了el
参数,那么将会执行vm.$mount
函数,而传入的参数是vm,$options.el
。也即是我们的#app
字符串。
vm.$mount
vm.$mount
的挂载其实是在./src/platforms/web/entry-runtime-with-complier.js
当中,如果我们只在./src/core/
的文件中其实是找不到的。vue
的编译一共有两个版本:第一是runtime-only
版本,另一个就是runtime-complier
。在这里我们需要说明的是,什么是运行时+编译,其实当我们写如下代码:
var vm = new Vue({
el:"#app",
data(){
return{
message:123
}
}
})
Vue
在进行模板渲染的时候其实是有一个对#app
进行编译的一个过程,会将模板编译成一个render函数。从某种意义上来讲其实是对性能的一种消耗。假如我们写这样的一段代码:
var vm = new Vue({
el:'#app',
data(){
return{
message:123
}
},
render:h(createElement)=>{
return createElement({
tag:'div'
})
}
})
此时我们添加了一项render
。这个函数的作用是返回一个由createElement
函数生成的一个vdom
。用来代替我们的el
。这样有什么好处呢。好处就是Vue
不需要把模板编译成render函数
。因为我们直接就提供给Vue
其render函数
。这样就省去了模板 ---> render函数
的编译时间。对于runtime + complier
版本的根组件我们不论写的是render/template
,我们必须有一个前提,那就是我们需要写el。原因在于Vue需知道要我们最后的模板渲染到文档的那个位置,所以必须有el,但是对于组件来说,可能就不需要了。
接下来我们来详细的讲解vm.$mount
这个方法。该方法代码如下:
Vue.prototype.$mount = function ( el?: string | Element,//我们传入的el类型有两种,一种是字符串'#app',另一种是一个元素对象,例如document.getElementById('app')
hydrating?: boolean ): Component {
el = el && query(el)//query函数主要是返回一个元素对象,如果我们传入的el存在,那么就返回该元素的对象形式(也就是真正的元素节点),如果不存在,那么就会默认是一个div元素对象
/* istanbul ignore if */
if (el === document.body || el === document.documentElement) {
//这里告诉我们el不能是body和html。原因是它会发生覆盖,这样就会将原来的模板完全覆盖掉。
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//这里的this指向的是vm实例对象
// resolve template/el and convert to render function
if (!options.render) {
//如果我们没有render函数。那么会进入该区域代码。
let template = options.template //获取模板
if (template) {
//如果存在template配置项,
if (typeof template === 'string') {//如果配置项的类型为字符串。
if (template.charAt(0) === '#') {//这里我们只处理template为#xxx的格式的模板,也就是类似于template:'#app'这种
template = idToTemplate(template)//该函数返回的是template模板内部的节点的字符串形式。
/* istanbul ignore if */
//这里是template的错误处理
if (process.env.NODE_ENV !== 'production' && !template) {
warn(
`Template element not found or is empty: ${options.template}`,
this
)
}
}
} else if (template.nodeType) {
//如果我们传入的template是一个节点对象,那么获取该节点对象中的innerHTML,然会的也是字符串形式
template = template.innerHTML
} else {
//不是以上两种格式,那么抛出错误
if (process.env.NODE_ENV !== 'production') {
warn('invalid template option:' + template, this)
}
return this
}
} else if (el) {
//如果template配置项不存在,那么获取el.outerHTML当作我们的template。返回的也是字符串类型
template = getOuterHTML(e