new Vue() 之后的过程。
Vue.component('comp1', {template: '<div>com1</div>',
created () {console.log('子组件创建')},
mounted () { console.log('子组件挂载')}
})
vm = new Vue({
el: '#app',
data: {abc: 123},
created () {console.log('父组件创建')},
mounted () { console.log('父组件挂载')}
})
console.log(vm)
// 创建时,从上而下
// 挂载时,从下而上。
</script>
执行构造器函数
src\core\ins
tance\index.js 中定义了构造器。
执行它的_init方法,并传入options。
_init
这个方法是在 src\core\instance\init.js 中的initMixin函数中添加到Vue的原型对象上的。而initMixin()已经在src\core\instance\index.js 中调用过了。所以要看这个方法,要去到initMixin中去找。
主要工作:
- 一系列初始化
- 执行钩子created
- 如果在配置项中传入了el,就去执行$mount
// 如果有el,会自动去执行$mount
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
$mount
$mount方法在不同的平台下各自不同,在web平台,它先会在
src\platforms\web\entry-runtime-with-compiler.js 中得到扩展
// 扩展 $mount方法
// 在web平台下,$mount要额外做一些工作(在.html中经常需要直接写模板)
// 把 template / el 转成render函数,并保存到options.render
const mount = Vue.prototype.$mount
Vue.prototype.$mount = function (el,hydrating){
// 1. 把 template / el 转成render函数,并保存到options.render
if(!options.render) {
// 优先级: render > template > el
const { render, staticRenderFns } =compileToFunctions(template)
options.render = render
}
// 2. 调用原$mount
return mount.call(this, el, hydrating)
}
那么这里的原$mount 是在哪定义的呢?src\platforms\web\runtime\index.js
在这个文件中,主要是做两件事
// 1. 声明__patche__. diff的过程是由__patch__来完成的
// import { patch } from './patch'
Vue.prototype.__patch__ = inBrowser ? patch : noop
// public mount method
// 2. 声明 $mount方法
Vue.prototype.$mount = function (){
// 挂载的执行
return mountComponent(this, el, hydrating)
}
mountComponent()
它定义在 src\core\instance\lifecycle.js
中,被$mount()调用。
内容:
- 执行钩子函数 beforeMount
- 定义updateComponent函数
updateComponent = () => { vm._update(vm._render(), hydrating) }
- 创建watcher并传入updateComponent函数
在watcher内部,会执行一次updateComponent, 依次执行vm_render(), vm_update() - 执行钩子函数 mounted
_render()
接下来执行_render()
它定义在src/core/instance/render.js中。
_render使用在$options.render
来生成虚拟dom。(在$mount时,已经附加了render函数)
function anonymous() {
with(this){
return _c('div',{attrs:{"id":"app"}},[_c('comp1')],1
}
}
注意到这里的render函数中有_c 。这里的_c是在 initRender 函数(这个函数是在src/core/instance/render.js文件中,initRender是在_init函数中被调用的)中定义的。
vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)
_c的作用是创建虚拟dom。它其实是通过定义在src\core\vdom\create-element.js文件中的createElement来具体执行。
createElement
createElement定义在src\core\vdom\create-element.js中,在它内部,它会调用_createElement来做具体的工作。
所以,执行渲染函数的过程中,_c其实就是 _createElement.
在执行的过程中, _c(‘div’) 是一个普通的标签,直接处理vnode = new VNode()
,而_c(‘dom1’)中的dom1将会被当作自定义组件来处理,进而去调用createComponent()
createComponent
createComponent这个函数定义在src\core\vdom\create-component.js文件中。
在createElement调用createComponent之前,先通过resolveAsset取到这个组件的构造器,然后把构造器传给createComponent函数。。
然后,在这个函数内部,它只通过定义在
src\core\instance\init.js中的
resolveConstructorOptions函数去解析出com1这个组件的构造器选项。
这个函数内部,执行
installComponentHooks,给组件安装钩子:向data上补充hook:{destory,init,insert,prepatch}
最后:
const vnode = new VNode(
`vue-component-${Ctor.cid}${name ? `-${name}` : ''}`,
data, undefined, undefined, undefined, context,
{ Ctor, propsData, listeners, tag, children },
asyncFactory
)
这样就创建出来这个组件的虚拟DOM。
至此,整个的_rend()过程结束,下面开始进入到_update()。
patch
_update()会进入到patch中去。在patch时,由于这里是初始化,会进入到 createElm,在createElm中,调用createComponent去检查是否是自定义组件。
createComponent ()
在这个函数当中,会取当前vnode.data中的hook,并执行。
if (isDef(i = i.hook) && isDef(i = i.init)) {
i(vnode, false /* hydrating */)
}
如果它是一个自定义组件的话,它就会去执行hook中的init。init定义在create-component.js中的componentVNodeHooks.init()
const child = vnode.componentInstance = createComponentInstanceForVnode(
vnode,activeInstance
)
child.$mount(hydrating ? vnode.elm : undefined, hydrating)
它通过createComponentInstanceForVnode来创建组件的实例(此时会调用this_init,由于组件上没有el选项,需手动调用$mount, 至此,进入递归模式。