参考博客:https://blog.csdn.net/qq_41694291/article/details/108435096
Vue自身的初始化
在引入 import Vue from 'vue'
时,对自身执行了一次初始化。
调用了5个 mixin 方法,给 Vue 混入了大量的原型方法。
-
initMixin
提供_init
,根据传入的options
初始化 Vue 实例。 -
stateMixin
提供$data
$props
$set
$delete
$watch
,$data
和$props
是提供的_data
和_props
的只读;$set
和$delete
是提供的全局响应式方法,因为直接增删属性不会被检测到;$watch
同 watch -
eventsMixin
提供$on
$once
$off
$emit
,$on
用于向实例注册事件监听;$once
则是注册一个只会被调用一次的事件监听;$off
用于取消某个或某类事件监听;$emit
用于触发某个事件。 -
lifecycleMixin
提供_update
$forceUpdate
$destroy
,_update
负责组件的更新;$forceUpdate
用于强制更新组件;$destroy
用于销毁组件 -
renderMixin
提供$nextTick
和_render
,$nextTick
用于将一段代码逻辑推入微任务队列,以保证视图更新后才会执行;_render
负责渲染组件,它的主要实现逻辑是调用组件的 render 函数生成 DOM,然后挂载到页面上。
执行 new Vue 的生命周期
由 this._init(options)
开始实现初始化组件实例。
创建过程
- 创建一个
$options
用于其他的初始化使用 -
initProxy
初始化 proxy 代理。如果浏览器支持 proxy 就生成一个代理对象作为 render 函数的调用者,提高性能,如果不支持,代理对象就是实例本身。 -
initLifecycle
初始化生命周期。初始化生命周期相关的实例属性。 -
initEvents
初始化组件事件。主要是定义_events
属性,该属性后面将用于存储与当前组件有关的事件监听。 -
initRender
初始化渲染相关的属性和方法。
a._vnode
在挂载阶段保存当前组件对应的虚拟节点
b.$slots
保存插槽内容
c._c
渲染真实 DOM 的方法
d.$attrs 和 $listeners
保存来自父组件属性和监听方法 - 与组件无关的配置初始化完成,开始调用
beforeCreate
钩子函数
a.initInjections
初始化依赖注入模式下从外部注入的变量。依赖注入模式指的是provide 和 inject
,可以作为一个跨层级的组件通信使用。
b.initState
初始化组件状态,分别调用了initProps, initMethods, initData, initComputed initWatch
来初始化对应的内容,这一步骤的主要作用是构建响应式系统。而响应式系统的核心则主要是在initData
中构建起来的,初始化 data 的时候会为其属性添加 Observer 订阅者。
c.initProvide
初始化 provide ,和 initInjections 是对应的 - 组件初始化完毕,执行
created
钩子函数
创建过程的最后会检测 el
属性是否存在,如果有就进行挂载阶段,如果没有就需要手动调用 $mount
进行挂载
响应式系统
响应式系统的核心是 data,在 initData
的最后一步,通过 observe(data, true)
将其构建起来的。
响应式包括三个核心内容: Observer
Dep
和 Watcher
Observer (观测者) 以 __ob__
的属性形式存在数据对象上,用于观测对象属性的变化。
Dep 以 dep
的属性形式存在 __ob__
内,负责帮助 Observer 收集和通知监听者。
Watcher(订阅者) 存在 dep
属性的 subs 数组属性内,负责在数据发生变化时执行某些操作。
// initData执行完毕后组件的_data属性
// 包含__ob__属性证明它已经是响应式的
this._data = {
__ob__: {
dep: {
subs: [watcher, ...]
}
}
}
具体流程:
调用 observe 观测 data 时,Vue 为其添加一个 Observer 类型的 __ob__
属性,在这个过程中通过 Object.defineProperty
递归修改 data 里的每个属性的 set 和 get 。同时 __ob__
还会初始化 dep 属性,用于管理相关依赖,这些依赖(即 watchers)被保存在 dep 的 subs 数组里。调用 new Watcher
生成一个订阅者时,它会自动进入该数据对象的订阅者队列,而当数据变化时,Observer 通知 Dep,Dep 便依次调用每个 Watcher 提供的 run 方法,执行对应的回调,以此实现响应式系统。
挂载、更新和销毁过程
- 检查是否有 render 函数,如果没有,则调用自身的模板编译器对 template 进行编译;
a._c
创建DOM,基于 document.createElement
b._l
解析列表,如 v-for
c._v
解析标签文本
d._s
解析变量的值
...... - 调用 mountComponent (挂载)
以下两个内容都是在 beforeMount 阶段
a、updateComponent
一个用于更新和渲染组建的函数,其内部主要是实现vm._update(vm._render())
。这里的 _render() 是将 template 编译的渲染函数,传入到 _update 里。而 _update 里又会进行一次判断,如果旧的 vnode 不存在,说明是首次渲染,调用__patch__
将 虚拟dom 生成 真实dom 并绘制到页面;如果 vnode 存在,则用__patch__
比较新旧 vnode
b、new Watcher(vm, updateComponent, noop, { before (){ callhook('beforeUpdate') } })
这段代码为当前组件实例构造了一个watcher,初始化watcher的过程中会触发data属性的get方法,因此这个watcher就会被Dep收集起来,传入的回调函数正是它的updateComponent方法。当数据变化时,Observer会通知Dep,Dep依次调用订阅者watcher的run方法,run里面会执行上述回调函数(即updateComponent),于是视图得到更新。这样就实现了修改数据之后自动更新视图。
(我直接复制了,个人理解是 mountComponent调用时会注册watcher,往watcher里注入 updateComponent 的方法,挂载之前这个watcher会被dep收集。那么视图更新时就根据响应式系统的流程走,而watcher里有个参数就是 updateComponent,依次来完成视图的更新。)
(更新)
而每当updateComponent被调用前,Vue都会调用callHook('beforeUpdate'),执行该生命周期钩子函数,因为视图即将被更新。当然,当updateComponent执行完毕后,Vue又会调用callHook('updated'),执行更新完毕的生命周期钩子函数。 - 销毁(
$destroy
)
当触发$destroy方法时,首先是调用beforeDestroy生命周期钩子函数。接着主要是清除组件的依赖关系,以及销毁watcher等。此时组件已经失去了响应能力,相当于它的状态被销毁了,因此Vue会调用destroyed生命周期钩子函数。最后注销组件的事件监听,清除一些附属参数,组件彻底被销毁(对于Vue组件来说,一旦状态被销毁,它就被认为是销毁了,所以destroyed是在事件被销毁前调用的)
全流程图.png
总结
vue的渲染分为两个部分:1. vue自身的初始化; 2. 生命周期钩子函数的过程
自身初始化时,通过五个 mixin 方法为 vue 自身注入相关属性和方法。
生命周期流程时:
首先提取出通过 _init
提取出 $options
以及 初始化 proxy
(浏览器不支持就初始化实例本身)、生命周期、事件和渲染方法;
接着进入 beforeCreate 阶段,初始化数据和注册依赖(provide 和 inject) ;
然后 beforeMount 阶段,查询 render 函数,执行 mountComponent ,主要注册 updateComponent 和 watcher,这个 watcher 被 dep 收录,用于更新的操作;
更新阶段就是响应式系统那一套,销毁前主要是清除组件的依赖关系,以及销毁watcher等,最后注销组件的事件监听,清除一些附属参数,组件彻底被销毁。
网友评论