本文章为自己总结使用,仅供参考,同时本文是通过学习 黄轶 老师文章总结。如果问题欢迎指正
初始化流程
一.引入 vue 文件
会调用几个方法,初始化一些方法
initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)
二.new Vue 初始化准备挂载
new Vue 的时候会调用 initMixin 里面的_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);
}
1.init 的方法中会判断传入的是否是组件 由于第一次传入的不是组件所以会进入到 else 里面进行合并配置
if (options && options._isComponent) {
// 优化内部组件实例化 因为动态选项合并非常慢,而且没有内部组件选项需要特殊处理。
initInternalComponent(vm, options);
} else {
vm.$options = mergeOptions(resolveConstructorOptions(vm.constructor), options || {}, vm);
}
2.Vue 初始化
- 主要就干了几件事,合并配置,初始化生命周期,初始化事件中心,初始化渲染,初始化 props、methods、 data、computed、watcher 等
initLifecyle: // 初始化一些属性如$parent,$children。根实例没有 $parent,$children 开始是空数组,直到它的 子组件 实例进入到 initLifecycle 时,才会往父组件的 $children 里把自身放进去。所以 $children 里的一定是组件的实例。
initEvents // 初始事件$events,$on,$once,$off,$emit
initRender // 初始化渲染相关如 $createElement
callHook(vm, 'beforeCreate')
initInjections(vm) // resolve injections before data/props
initState(vm) //
初始化 state
·初始化 props
·初始化 methods
·初始化 data
·初始化 computed
·初始化 watch
所以在 data 中可以使用 props 上的值,反过来则不行。
initProvide(vm) // resolve provide after data/props
callHook(vm, 'created')
3.开始挂载
- 如果监测到有 el 属性。则调用$mount 方法挂在 vue 实例,把模板渲染成最终的 DOM。然后调用 mountcomponent 方法 开始渲染
如果在 runtime-with-compiler 版本则会把你传入的 template 选项,或者 html 文本,通过一系列的编译生成 render 函数。在 Vue 2.0 版本中,所有 Vue 的组件的渲染最终都需要 render 方法
if (vm.$options.el) {
vm.$mount(vm.$options.el);
}
三、开始挂载
1.beforeMount 钩子函数
在 mountcomponent 里面开始调用 beforeMount 钩子函数,然后实例化一个渲染的 watcher,里面调用一个 updateComponent 回调函数,在这个方法中调用_render 方法生成 Vnode,最后调用_update 方法更新生成 DOM
updateComponent = () => {
vm._update(vm._render(), hydrating);
};
// 这个是个渲染的的watcher
new Watcher(
vm,
updateComponent,
noop,
{
before() {
if (vm._isMounted) {
callHook(vm, 'beforeUpdate');
}
}
},
true /* isRenderWatcher */
);
//
// 除了初始化 页面更新的时候也会调用这个方法渲染页面
1.new Watcher 会观察初始化里面有哪些响应式数据,如果数据发生了变化,会重新执行 updateComponent 函数重新更新渲染。
2.在调用_render 方法时,会通过执行 createElement 创建 VNode,VNode 里面有它的 children,而 children 下的元素也是 VNode,这样就生成了一个 VNode 的 tree。
3.在 render 的时候遇到了子组件,则会调用 createComponent 函数。 createComponent 函数内部,会通过 extend 方法为子组件生成一个属于自己的构造函数,这个 Ctor 继承了全局的一些属性和方法:同时对于这个 Sub 构造函数做了缓存,避免多次执行 Vue.extend 的时候对同一个子组件重复构造.
4.vm._update 方法则会对这个 vnode 进行 patch 操作,,帮我们把 vnode 通过 createElm 函数创建新节点并且渲染到 dom 节点中
// 在这个 patch 的时候会初始化 VNode 的生命周期函数,挂载在 VNode 的 data.hook 上,同时 patch 的时候也有 createComponent 函数会调用 VNode 的 init 方法
// createElm 中有 createChildren(vnode, children, insertedVnodeQueue)方法。createChildren 实际上是遍历子虚拟节点,递归调用 createElm,这是一种常用的深度优先的遍历算法,这里要注意的一点是在遍历过程中会把 vnode.elm 作为父容器的 DOM 节点占位符传入。
// 创建子组件
const child = createComponentInstanceForVnode(vnode);
// 挂载到 dom 上
child.$mount(vnode.elm);
createComponentInstanceForVnode 里面会调用子组件的构造函数
// baseCtor指向Vue
Ctor = baseCtor.extend(Ctor);
5.在 sub 实例化的时候会调用_init 方法又再次重复了 vue 实例化的逻辑,又会走到_render 方法
const Sub = function VueComponent(options) {
this._init(options);
};
然后,mounted 生命周期被触发。
2.mounted 被调用完成
到此为止,组件的挂载就完成了,初始化的生命周期结束。
更新流程
当一个响应式属性被修改以后,会执行 渲染 watcher 的回调函数,updateComponent,也就是里面的 vm._update(vm._render);
在更新前会调用 before 里面的方法,也就是 callHook(vm,'beforeUpdate');
beforeUpdate 被调用完成
然后经历了一系列的 patch、diff 流程后,组件重新渲染完毕,调用 updated 钩子
这里是对 watcher 倒序 updated 调用的
function callUpdatedHooks(queue) {
let i = queue.length;
while (i--) {
const watcher = queue[i];
const vm = watcher.vm;
if (vm._watcher === watcher && vm._isMounted) {
callHook(vm, 'updated');
}
}
}
updated 被调用完成
至此,渲染更新流程完毕。
其它知识
initData 的时候做了什么
会把 vm._data 等于 data。
- 首先会判断 data 是否是个 function。
- 然后再判断 props 和 mehtods 上面是否会有和 data 里面的同样的变量名称
- 通过 proxy 方法把 _data 里面的属性代理到 vm 实例上
- 最后通过 observe 把属性变成双向绑定。
data = vm._data = typeof data === 'function' ? getData(data, vm) : data || {};
proxy(vm, `_data`, key);
observe(data, true /* asRootData */);
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);
}
全局组件注册
Vue.component('my-component', {
// 选项
});
const ASSET_TYPES = [
'component',
'directive',
'filter
]
Vue 初始化的时候会初始化这三个全局函数。在这个函数中
,局部注册和全局注册不同的是,只有该类型的组件才可以访问局部注册的子组件,而全局注册是扩展到 Vue.options 下,所以在所有组件创建的过程中,都会从全局的 Vue.options.components 扩展到当前组件的 vm.$options.components 下,这就是全局注册的组件能被任意使用的原因