组件实例化与挂载流程
Vue组件通过 _init 初始化选项、注入、响应式、事件,$mount 编译模板、创建渲染Watcher、执行patch挂载DOM。
_init 初始化
JavaScript
Vue.prototype._init = function(options?: Object) {
const vm: Component = this
// 合并选项
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
// 初始化生命周期
initLifecycle(vm)
// 初始化事件系统
initEvents(vm)
// 初始化渲染系统
initRender(vm)
// 调用beforeCreate钩子
callHook(vm, 'beforeCreate')
// 初始化注入
initInjections(vm)
// 初始化响应式数据
initState(vm)
// 初始化提供
initProvide(vm)
// 调用created钩子
callHook(vm, 'created')
// 自动挂载
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}
按顺序初始化选项、生命周期、事件、渲染、注入、状态、提供,调用生命周期钩子。
initState 状态初始化
JavaScript
function initState(vm: Component) {
vm._watchers = []
const opts = vm.$options
if (opts.props) initProps(vm, opts.props)
if (opts.methods) initMethods(vm, opts.methods)
if (opts.data) initData(vm)
else observe(vm._data = {}, true)
if (opts.computed) initComputed(vm, opts.computed)
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch)
}
}
按props -> methods -> data -> computed -> watch顺序初始化。
initData 数据响应式
JavaScript
function initData(vm: Component) {
let data = vm.$options.data
data = vm._data = typeof data === 'function' ? getData(data, vm) : data || {}
// 代理到vm实例
const keys = Object.keys(data)
const props = vm.$options.props
const methods = vm.$options.methods
let i = keys.length
while (i--) {
const key = keys[i]
if (props && hasOwn(props, key)) {
warn(`props与data冲突`)
} else if (methods && hasOwn(methods, key)) {
warn(`methods与data冲突`)
} else {
proxy(vm, `_data`, key) // vm.key -> vm._data.key
}
}
// 观测数据
observe(data, true)
}
data代理到vm实例,避免每次访问 vm._data,然后观测数据实现响应式。
$mount 挂载
JavaScript
Vue.prototype.$mount = function(
el?: string | Element,
hydrating?: boolean
): Component {
el = el && query(el)
const options = this.$options
// 编译模板(仅runtime+compiler版本)
if (!options.render) {
let template = options.template
if (template) {
// 从template编译render函数
} else if (el) {
// 从el.outerHTML编译render函数
}
}
// 挂载核心逻辑
return mountComponent(this, el, hydrating)
}
runtime+compiler版本编译模板,runtime-only版本直接使用render函数。
mountComponent 挂载核心
JavaScript
function mountComponent(
vm: Component,
el: ?Element,
hydrating?: boolean
): Component {
vm.$el = el
// 创建更新组件
updateComponent = () => {
vm._update(vm._render(), hydrating)
}
// 创建渲染Watcher
new Watcher(vm, updateComponent, noop, {
before() {
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'beforeUpdate')
}
}
}, true)
hydrating = false
// 手动挂载实例调用mounted
if (vm.$vnode == null) {
vm._isMounted = true
callHook(vm, 'mounted')
}
return vm
}
创建渲染Watcher,执行 vm._render() 生成VNode,vm._update() patch到DOM。
_render 渲染函数执行
JavaScript
Vue.prototype._render = function(): VNode {
const vm: Component = this
const { render, _parentVnode } = vm.$options
vm.$scopedSlots = normalizeScopedSlots(
_parentVnode.data.scopedSlots,
vm.$slots
)
const vnode = render.call(vm._renderProxy, vm.$createElement)
vnode.parent = _parentVnode
return vnode
}
执行render函数返回VNode树,_renderProxy 提供访问不存在属性的警告。
_update DOM更新
JavaScript
Vue.prototype._update = function(vnode: VNode, hydrating?: boolean) {
const vm: Component = this
const prevEl = vm.$el
const prevVnode = vm._vnode
vm._vnode = vnode
if (!prevVnode) {
// 首次渲染
vm.$el = vm.__patch__(vm.$el, vnode, hydrating)
} else {
// 更新
vm.$el = vm.__patch__(prevVnode, vnode)
}
// 移除旧根节点引用
if (prevEl) {
prevEl.parentNode?.removeChild(prevEl)
}
}
首次渲染调用 __patch__ 挂载,更新时对比新旧VNode diff。
完整挂载流程
JavaScript
// 1. new Vue(options)
// -> _init()
// -> mergeOptions()
// -> initLifecycle/Events/Render
// -> beforeCreate
// -> initInjections/State/Provide
// -> created
// -> $mount(el)
// 2. $mount(el)
// -> 编译模板(如有)
// -> mountComponent()
// -> new Watcher(vm, updateComponent)
// -> watcher.get()
// -> updateComponent()
// -> vm._render() // 生成VNode
// -> vm._update() // patch挂载
// -> __patch__(el, vnode)
// 3. 首次渲染完成
// -> mounted
从 new Vue 到 mounted 钩子触发的完整流程。
要点总结
_init按顺序初始化选项、生命周期、事件、渲染、注入、状态initState按props -> methods -> data -> computed -> watch顺序初始化initData代理数据到vm实例,观测数据实现响应式$mount编译模板(仅runtime+compiler版本),调用mountComponentmountComponent创建渲染Watcher,执行updateComponent_render执行render函数生成VNode,_update执行patch挂载DOM- 完整流程:
new Vue->_init->$mount->mounted
📝 发现内容有误?点击此处直接编辑