您现在的位置是:首页 >学无止境 >vue2首次渲染过程网站首页学无止境
vue2首次渲染过程
简介vue2首次渲染过程
实例成员初始化
1.首先在 core/instance/index 中生命vue的构造函数后,经过一系列的操作初始化了一些实例成员
// 初始化 _init 方法,也就是初始化整个Vue的入口
initMixin(Vue)
// 初始化 $data 和 $props 实例成员,指向实例的_data和_props,并禁止修改,只能获取
// 初始化实例上的 $set 和 $delete 方法,和静态成功set、delete一样
// 初始化 $watch,用于创建userWatcher并返回取消监听的方法
stateMixin(Vue)
// 初始化$on、$off、$emit、$destory、$forceUpdate,
eventsMixin(Vue)
// 初始化_update、$destory、$forceUpdate,
// 其中 $forceUpdate 用于执行当前实例渲染$watcher 的update方法立即更新
lifecycleMixin(Vue)
// 挂载渲染相关辅助函数
// 挂载 $nextTick,和静态成员 nextTick 是一个方法
// 挂载 _render,用于调用用户传入的render方法,创建虚拟DOM
renderMixin(Vue)
静态成员初始化
2.通过initGlobalAPI声明vue构造函数的静态成员
export function initGlobalAPI(Vue: GlobalAPI) {
// config
const configDef: Record<string, any> = {}
configDef.get = () => config
// 初始化了Vue.config这个静态属性
Object.defineProperty(Vue, 'config', configDef)
// 定义了Vue.util,并挂载了一部分API(warn/extend/mergeOptions/defineReactive)
Vue.util = {
warn,
extend,
mergeOptions,
defineReactive
}
// 定义三个静态方法(set/delete/nextTick)
Vue.set = set
Vue.delete = del
Vue.nextTick = nextTick
// 定义observable方法,内部调用observe,
// 让入参对象变成一个响应式对象,并返回这个响应式对象
Vue.observable = <T>(obj: T): T => {
observe(obj)
return obj
}
// 初始化了Vue.options对象,其__proto__指向null
Vue.options = Object.create(null)
// 在其上扩展了三个方法(component/directive/filter)
ASSET_TYPES.forEach(type => {
Vue.options[type + 's'] = Object.create(null)
})
// 利用Vue.options._base暂存Vue构造函数
Vue.options._base = Vue
// 将内建组件下的全部组件都拷贝给Vue.options.components
// extend实现了两个对象属性的浅拷贝
extend(Vue.options.components, builtInComponents)
// 注册vue.use方法,用于插件注册
initUse(Vue)
// 注册Vue.mixin方法,用于全局混入
initMixin(Vue)
// 注册Vue.extend,基于传入的options返回一个组件的构造函数
initExtend(Vue)
// 注册Vue.directive,Vue.component和Vue.filter三个静态方法
initAssetRegisters(Vue)
}
3.在 web/runtime/index 中初始化了平台相关的内容,主要是实例成员 $mount 方法
4.如果是编译器版本,需要在 src/platform/web/runtime/entry-runtime-with-compiler 中重写$mount方法
执行vue构造函数
5.首次渲染时执行 new Vue(options),执行_init方法
export function initMixin(Vue: typeof Component) {
Vue.prototype._init = function (options?: Record<string, any>) {
// 使用vm存储vue实力
const vm: Component = this
// 设置自增长唯一键uin
vm._uid = uid++
let startTag, endTag
/* istanbul ignore if */
// 用于测试性能,可了解下window.performance
if (__DEV__ && config.performance && mark) {
startTag = `vue-perf-start:${vm._uid}`
endTag = `vue-perf-end:${vm._uid}`
mark(startTag)
}
// 后续设置响应式对象时通过该标记来忽略,不进行 observer 操作
vm._isVue = true
// avoid instances from being observed
vm.__v_skip = true
// effect scope
vm._scope = new EffectScope(true /* detached */)
vm._scope._vm = true
// merge options
if (options && options._isComponent) {
// optimize internal component instantiation
// since dynamic options merging is pretty slow, and none of the
// internal component options needs special treatment.
// 组件实例化 合并选项参数
initInternalComponent(vm, options as any)
} else {
// Vue实例化 合并选项参数
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor as any),
options || {},
vm
)
}
/* istanbul ignore else */
// 代理 vm._renderProxy 到 自身,主要用来检测 vm 上属性的读取操作
if (__DEV__) {
initProxy(vm)
} else {
vm._renderProxy = vm
}
// expose real self
vm._self = vm
// 添加一些实例变量,设置初始空值
initLifecycle(vm)
// 初始化设置 $on $off 自定义事件
initEvents(vm)
// 扩展 createElement 函数赋给 vm 为动态方法 $createElement、_c 等
initRender(vm)
// 调用钩子函数 beforeCreate
callHook(vm, 'beforeCreate', undefined, false /* setContext */)
// 解析 inject 选项
initInjections(vm) // resolve injections before data/props
// props,methods,data,computed,watch 做了一些列初始化操作
initState(vm)
// 解析 provide 选项,配合 inject 一起使用
initProvide(vm) // resolve provide after data/props
// 调用钩子函数 created
callHook(vm, 'created')
/* istanbul ignore if */
if (__DEV__ && config.performance && mark) {
vm._name = formatComponentName(vm, false)
mark(endTag)
measure(`vue ${vm._name} init`, startTag, endTag)
}
// 挂载渲染页面
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}
}
$mount
6.$mount会调用 mountComponent 方法
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
el = el && inBrowser ? query(el) : undefined
return mountComponent(this, el, hydrating)
}
export function mountComponent(
vm: Component,
el: Element | null | undefined,
hydrating?: boolean
): Component {
// 使用 $el存储el, 并挂载到实例上 vm.$el = el
vm.$el = el
if (!vm.$options.render) {
// ...
}
// 调用钩子函数 beforeMount
callHook(vm, 'beforeMount')
let updateComponent
/* istanbul ignore if */
if (__DEV__ && config.performance && mark) {
// ...
} else {
// 内部调用了_update方法,传入了vm_render()函数作为参数
// vm._render()函数内渲染了我们之前编译出来的render函数,并生成虚拟dom vnode
updateComponent = () => {
vm._update(vm._render(), hydrating)
}
}
const watcherOptions: WatcherOptions = {
before() {
// 如果mounted钩子已经执行过了,而当前组件还未销毁
// 此时,如果产生更新,说明并非首次渲染,那么执行beforeUpdate钩子
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'beforeUpdate')
}
}
}
// we set this to vm._watcher inside the watcher's constructor
// since the watcher's initial patch may call $forceUpdate (e.g. inside child
// component's mounted hook), which relies on vm._watcher being already defined
new Watcher(
vm,
updateComponent,
noop, // 空函数
watcherOptions, // 触发beforeUpdate
true /* isRenderWatcher */
)
hydrating = false
// manually mounted instance, call mounted on self
// mounted is called for render-created child components in its inserted hook
// 若当前vnode为null,即之前没有生成过虚拟dom,即首次渲染
if (vm.$vnode == null) {
vm._isMounted = true
// 调用钩子函数 mounted,首次渲染完成
callHook(vm, 'mounted')
}
return vm
}
7.进入 watcher 构造函数
export default class Watcher implements DepTarget {
constructor(
vm: Component | null,
expOrFn: string | (() => any), // 传入的更新函数
cb: Function,
options?: WatcherOptions | null,
isRenderWatcher?: boolean
) {
// 如果是渲染watcher,直接赋值给vm._watcher
if ((this.vm = vm) && isRenderWatcher) {
vm._watcher = this
}
// parse expression for getter
// 将我们传入的函数赋值给getter,即render函数
if (isFunction(expOrFn)) {
this.getter = expOrFn
} else {
// ...
}
// 调用get()
this.value = this.lazy ? undefined : this.get()
}
/**
* Evaluate the getter, and re-collect dependencies.
*/
get() {
pushTarget(this)
let value
const vm = this.vm
try {
// 即上面传入的render函数,并触发虚拟dom更新真实dom
value = this.getter.call(vm, vm)
} catch (e: any) {
// ...
} finally {
// "touch" every property so they are all tracked as
// dependencies for deep watching
if (this.deep) {
traverse(value)
}
popTarget()
this.cleanupDeps()
}
// 返回渲染后的真实dom
return value
}
// ...其他方法
}
整个beforemount到mounted过程中,主要做的工作:
1.渲染render函数称为虚拟dom
2.执行vm._update函数,将虚拟dom转化为真实dom
如果是beforeupdate到updated钩子之间,说明不是首次渲染,那么虚拟dom会有新旧两个。此时vm._update函数的作用就是对比新旧两个vnode,得出差异,更新需要更新的地方。
风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。