本文将结合例子进行一步步讲解,例子也会从简单到复杂逐步提高,这样理解的更深入html
<div id="app"></div>
复制代码
const app = new Vue({
template: '<div>child</div>',
})
app.$mount('#app');
复制代码
首先先调用new Vue
建立了一个实例,在core/instance/index
中定义了Vue
的构造函数node
function Vue(options) {
this._init(options)
}
initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)
复制代码
在该文件中定义了Vue
构造函数,而且经过下面几个Mixin
方法,在Vue
原型上也定义了一些方法,为何不用class
由于class
没有prototype
这么灵活。web
Mixin 方法 |
方法 | 属性 |
---|---|---|
initMixin |
_init |
- |
stateMixin |
$set 、$delete 、$watch |
$data 、$props |
eventsMixin |
$on 、$off 、$once 、$emit |
- |
lifecycleMixin |
_update 、$forceUpdate 、$detory |
- |
renderMixin |
_render 、$nextTick |
- |
_init
方法方法在core/instance/init.js
中,api
let uid = 0;
Vue.prototype._init = function (options) {
const vm = this;
vm._uid = uid++;
vm._isVue = true;
if (options && options._isComponent) {
initInternalComponent(vm, options)
} else {
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
}
vm._renderProxy = vm;
vm._self = vm;
initLifecycle(vm);
initEvents(vm);
initRender(vm);
callhook(vm, 'beforeCreate');
initInjections(vm);
initState(vm);
initProvide(vm),
callHook(vm, 'created')
}
复制代码
属性:app
Vue
的实例options
$children
中加入本身child
进行和父组件事件通讯的初始化,并在vm._events
对应的事件名称加入这个函数<div class="parent">
<child @change="changeToDo"></child>
</div>
复制代码
_c
、$createElement
。在Vue
原型上添加属性$attrs
、$listeners
,并让这些属性进行响应式监听inject
,inject
可以向子孙后代注入一个依赖,无论组件层次有多深props
、methods
、data
、computed
、watch
。让数据响应式就是这个阶段完成的,watch
和computed
都会生成对应的Watcher
provide
,用于接受inject
传入的数据$mount
建立实例后,会调用_init
进行一系列的初始化操做,而后调用$mount
,$mount
在不一样平台有不一样的定义,以web
为例ide
Vue.$prototype.$mount = function (el) {
el = el && query(el);
const options = this.$options;
if (!options.render) {
let template = options.template
}
if (template) {
const { render, staticRenderFns } = compileToFunction(template, {
// ...
})
options.render = render;
options.staticRenderFns = staticRenderFns
}
return mount.call(this, el);
}
复制代码
在不一样的平台调用不一样的编译方式最后把template
编译为render
函数。而后返回调用了mount
函数,最终调用的是mountComponent
函数
// cores/instance/lifecycle.js
function mountComponent(vm, el) {
vm.$el = el;
callHook(vm, 'beforeMount');
updateComponent = () => {
vm._update(vm._render())
}
new Watcher(vm, updateComponent, noop, {
before() {
if (vm._isMounted && !vm._isDestoryed) {
callHook(vm, 'beforeUpdate')
}
}
})
if (vm.$vnode == null) {
vm._isMounted = true;
callHook(vm, 'mounted')
}
}
复制代码
首先声明了回调函数upateComponent
,而后建立了渲染watcher
,渲染watcher
在初始化的时候就会执行回调函数updateComponent
,updateComponent
内部调用了_render
和_update
。这两个方法在文章开头的renderMixin
、lifecycleMixin
中定义了,_render
用于生成vnode
,_update
调用patch
:具体的path
可参照这篇文章Vue 源码patch过程详解,把vnode
中定义的内容渲染到真实DOM
中,最后调用mounted
钩子。oop
data
把上面的例子进行更改,当template
中data
发生了更改,再看看具体的变化。post
new Vue({
template: '<div class="parent" @click="change">{{visible}}</div>'
data: {
return {
visible: 'all'
}
},
methods: {
change() {
this.visible = 'change';
}
}
})
复制代码
当咱们点击元素的时候,就会触发change
事件更改data
中定义的值visible
学习
在initState
中会对data
中定义的值进行响应式设置
//core/instance/state.js
function initData(vm) {
let data = vm.$options.data;
data = vm._data = typeof data === 'function'
? getData(data, vm)
: data || {}
observe(data, true)
}
复制代码
这里在初始化data
的时候,首先调用了自己,获得返回的值,而后调用observe
进行数据响应式具体的可参照这篇文章深刻源码学习Vue响应式原理。回到mountComponent
中,在建立renderWatcher
的时候首先会执行一遍updateComponent
,进行依赖收集
当数据更新后,依赖该data
数据的watcher
就会更新,这里只有renderWatcher
有依赖,因此这个watcher
就会调用回调函数,从新执行一遍_render
和_update
。vm._render
根据template
生成的render
来生成vnode
// core/instance/render.js
Vue.prototype._render = function {
const vm = this;
const { render, _parentVnode } = vm.$options;
if (_parentVnode) {
vm.$scopedSlots = normalizeScopedSlots(
_parentVnode.data.scopedSlots,
vm.$slots,
vm.$scopedSlots,
)
}
vm.$vnode = _parentVnode;
let vm.$vnode = _parentVnode
let vnode
try {
currentRenderingInstance = vm;
vnode = render.call(vm._renderProxy, vm.$createElement)
} finally {
currentRenderingInstance = null
}
if (Array.isArray(vnode) && vnode.length === 1) {
vnode = vnode[0]
}
vnode.parent = _parentVnode
return vnode;
}
复制代码
能够看到经过调用render
函数最后生成了vnode
, $createElement
也在当前文件夹中定义过,最后生成vnode
而后调用_update
执行patch
操做,把修改后的数据反映到真实DOM
对上面的例子在进行扩展,建立一个子组件
Vue.component('child', {
template: '<div class="child">child</div>'
})
new Vue({
template: '<div class="parent"><child></child></div>'
})
复制代码
这里声明了一个子组件,而且父组件中调用了这个子组件,首先compileToFunctions
将其编译为对应的render
函数上面把new Vue
中声明的template
编译为以下的render
函数
ƒ anonymous(
) {
with(this){return _c('div',{staticClass:"parent"},[_c('child')],1)}
}
复制代码
当执行当前render
就会生成以下的vnode
当执行patch
的时候,当发现vnode
下面有children
就会对children
进行一系列操做。
Vue.component
回到Vue.component
声明子组件,当调用Vue.component
都发生了什么,方法定义在core/global-api/assets.js
中
Vue.component = function (id, definition) {
definition.name = definition.name || id;
definition = this.options._base.extend(definition)
this.options[type + 's'][id] = definition;
}
复制代码
this._options._base
就是Vue
构造函数,至关于调用的是Vue.extend
,而后生成的definition
挂载到this.options.components
上,属性名为child
。Vue.extend
的方法定义在
Vue.extend = function (extendOptions) {
const Super = this;
const Sub = function VueComponent(options) {
this._init(options)
}
Sub.prototype = Object.create(Super.prototype);
Sub.prototype.constructor = Sub;
// ...
return Sub;
}
复制代码
能够看到返回是一个继承Vue
的构造函数,而且建立实例的实例也会调用Vue
的_init
函数
patch
具体的逻辑能够参照Vue 源码patch过程详解 回到父组件的$mount
操做,当建立渲染watcher
的时候,会当即执行updateComponent
,而后内部会执行_update
函数,能够执行patch
操做,而后上面图片能够看到children
中存在值,就会走到createChildren
为children
中的元素调用createElm
。由于child
是子组件就会走到 createComponent
而且二返回true
,在内部调用钩子init
,init
钩子函数具体实现以下:
const componentVNodeHooks = {
init: (vnode) => {
const child = vnode.componentInstance = createComponentInstanceForVnode(vnode, activeInstance)
child.$mount(vnode.elm)
}
}
复制代码
这里就会调用createComponentInstanceForVnode
函数,这个函数实际调用的就是前面在Vue.extend
中返回的继承于Vue
的构造函数,最后在调用$mount
函数。因此父子组件在渲染的时候钩子执行的前后顺序就是 父beforeMounted
=> 子beforeMounted
=> 子mounted
=> 父mounted