简述
本文主要讨论new一个Vue对象的过程
构造函数
Vue的构造函数定义很简单,定义如下,关键调用了_init方法
文件路径:vue/src/core/instance/index.js
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')
}
// 调用_init方法
this._init(options)
}
_init()
vue实例的原型方法_init(options)是在initMixin(Vue)方法中定义的
initMixin(Vue)定义在:vue/src/core/instance/init.js文件中
_init(options)主要做了如下几件事情
- 如果options._isComponent是true也就是如果是组件的话,调用initInternalComponent方法初始化内部组件的options
- 如果不是组件,处理options,调用mergeOptions方法合并options
- 调用一系列初始化方法,和两个生命周期钩子
initLifecycle(vm)
// 初始化事件
initEvents(vm)
initRender(vm)
callHook(vm, 'beforeCreate')
initInjections(vm) // resolve injections before data/props
initState(vm)
initProvide(vm) // resolve provide after data/props
callHook(vm, 'created')
- 最终调用$mount方法进行初次渲染
initInternalComponent()
此方法主要处理子组件的options
initInternalComponent (vm: Component, options: InternalComponentOptions) {
// 获取组件的options
const opts = vm.$options = Object.create(vm.constructor.options)
// doing this because it's faster than dynamic enumeration.
const parentVnode = options._parentVnode
opts.parent = options.parent
opts._parentVnode = parentVnode
// 获得父vnode的组件options
const vnodeComponentOptions = parentVnode.componentOptions
// 赋值组件参数
opts.propsData = vnodeComponentOptions.propsData
opts._parentListeners = vnodeComponentOptions.listeners
opts._renderChildren = vnodeComponentOptions.children
opts._componentTag = vnodeComponentOptions.tag
// 如果有render函数
if (options.render) {
opts.render = options.render
opts.staticRenderFns = options.staticRenderFns
}
}
resolveConstructorOptions()
此方法的主要作用就是获得构造器的最新的options属性
// 解析构造器上的options
// 返回当前构造器最新的options属性
export function resolveConstructorOptions (Ctor: Class<Component>) {
// 获得构造器上的options,此options是在initGlobalAPI方法中定义的,Vue.options=xxx
let options = Ctor.options
// 如果当前组件是Vue.extend()构造出来的,Ctor.super=true,递归求得父组件的options
if (Ctor.super) {
// 一直递归获得最原始的那个Vue构造器的options
const superOptions = resolveConstructorOptions(Ctor.super)
// 之后的步骤主要是为了父构造器的options变化了,那么用Vue.extend()构造出来的构造器的options也应该改变
// Sub在创建定义的时候,赋值过一次options(详见Vue.extend实现)
// 缓存的构造器options
const cachedSuperOptions = Ctor.superOptions
if (superOptions !== cachedSuperOptions) {
// 如果父构造器的options改变了
// super option changed,
// need to resolve new options.
// 当前构造器的superOptions更新为新的
Ctor.superOptions = superOptions
// check if there are any late-modified/attached options (#4976)
const modifiedOptions = resolveModifiedOptions(Ctor)
// update base extend options
if (modifiedOptions) {
extend(Ctor.extendOptions, modifiedOptions)
}
options = Ctor.options = mergeOptions(superOptions, Ctor.extendOptions)
if (options.name) {
options.components[options.name] = Ctor
}
}
}
return options
}
initLifecycle(vm)
此方法主要初始化一些Vue实例(或子组件实例)的一些属性
// 此方法主要初始化一些Vue实例(或子组件实例)的一些属性
export function initLifecycle (vm: Component) {
// 获得options
const options = vm.$options
// locate first non-abstract parent
let parent = options.parent
// 如果parent(父组件)存在的话
// 父组件存在且不是抽象组件,如<keep-alive></keep-alive>就是一个抽象组件
if (parent && !options.abstract) {
while (parent.$options.abstract && parent.$parent) {
parent = parent.$parent
}
// 父组件实例的$children保存当前组件的实例
parent.$children.push(vm)
}
// 给vue实例的$parent赋值
vm.$parent = parent
// 根组件
vm.$root = parent ? parent.$root : vm
// 初始化 子组件数组
vm.$children = []
// 初始化 refs
vm.$refs = {}
// 初始化一些私有属性值 后续会用到
// 组件实例相应的 watcher 实例对象。
vm._watcher = null
// 表示keep-alive中组件状态,如被激活,该值为false,反之为true。
vm._inactive = null
// 也是表示keep-alive中组件状态的属性。
vm._directInactive = false
// 当前实例是否完成挂载(对应生命周期图示中的mounted)。
vm._isMounted = false
// 当前实例是否已经被销毁(对应生命周期图示中的destroyed)。
vm._isDestroyed = false
// 当前实例是否正在被销毁,还没有销毁完成(介于生命周期图示中deforeDestroy和destroyed之间)。
vm._isBeingDestroyed = false
}
initEvents(vm)
initEvent主要调用了updateComponentListeners()方法,updateComponentListeners调用了updateListeners()方法
updateListeners()方法主要作用就是将绑定在组件上的事件,保存至vm._events属性中,并且如果绑定的事件更新了,那么vm._events中的事件也会更新。
export function initEvents (vm: Component) {
// 初始化实例的_events属性
vm._events = Object.create(null)
// 初始化实例的_hasHookEvent属性
vm._hasHookEvent = false
// init parent attached events
// 拿到当前组件绑定的事件
const listeners = vm.$options._parentListeners
// 如果有绑定事件
if (listeners) {
// 更新当前组件的事件
updateComponentListeners(vm, listeners)
}
}
updateListeners传入了几个方法,add,remove,createOnceHandler 这几个方法其实调用的是 vm.$on , vm.$off, vm.$once方法 这三个方法定义在eventsMixin(Vue)方法中
export function updateComponentListeners (
vm: Component,
listeners: Object,
oldListeners: ?Object
) {
target = vm
updateListeners(listeners, oldListeners || {}, add, remove, createOnceHandler, vm)
target = undefined
}
- Vue.prototype.$on方法作用是给vue实例vm._events 赋值,组件绑定的事件都存在_events(数组)属性上
- Vue.prototype.$once方法的作用就是执行过一次的方法,就会被移除
- Vue.prototype.$off方法的作用就是将事件从_events(数组)属性中移除
- Vue.prototype.$emit方法的作用就是可以执行保存在_events属性中的事件
export function eventsMixin (Vue: Class<Component>) {
const hookRE = /^hook:/
// 监听当前实例上的自定义事件。事件可以由vm.$emit触发。回调函数会接收所有传入事件触发函数的额外参数。
Vue.prototype.$on = function (event: string | Array<string>, fn: Function): Component {
const vm: Component = this
// 如果是数组,递归调用
if (Array.isArray(event)) {
for (let i = 0, l = event.length; i < l; i++) {
vm.$on(event[i], fn)
}
} else {
// vm的_events属性保存事件,同名的事件都保存在一个数组里
(vm._events[event] || (vm._events[event] = [])).push(fn)
// optimize hook:event cost by using a boolean flag marked at registration
// instead of a hash lookup
if (hookRE.test(event)) {
// 如果匹配到,标识有钩子事件
vm._hasHookEvent = true
}
}
return vm
}
// 监听一个自定义事件,但是只触发一次。一旦触发之后,监听器就会被移除。
Vue.prototype.$once = function (event: string, fn: Function): Component {
const vm: Component = this
function on () {
// 执行on()的话,on会先被移除,再执行一次,也就是执行一次之后会被移除
vm.$off(event, on)
fn.apply(vm, arguments)
}
on.fn = fn
vm.$on(event, on)
return vm
}
Vue.prototype.$off = function (event?: string | Array<string>, fn?: Function): Component {
const vm: Component = this
// all
// 无参数,移除所有事件
if (!arguments.length) {
vm._events = Object.create(null)
return vm
}
// array of events
// 数组遍历递归移除
if (Array.isArray(event)) {
for (let i = 0, l = event.length; i < l; i++) {
vm.$off(event[i], fn)
}
return vm
}
// specific event
const cbs = vm._events[event]
// 不存在 直接返回
if (!cbs) {
return vm
}
// 如果fn参数没传,或者为false,移除所有事件
if (!fn) {
vm._events[event] = null
return vm
}
// specific handler
// 如果fn参数传了,只移除于fn相等的函数
let cb
let i = cbs.length
while (i--) {
cb = cbs[i]
if (cb === fn || cb.fn === fn) {
cbs.splice(i, 1)
break
}
}
return vm
}
Vue.prototype.$emit = function (event: string): Component {
const vm: Component = this
if (process.env.NODE_ENV !== 'production') {
const lowerCaseEvent = event.toLowerCase()
if (lowerCaseEvent !== event && vm._events[lowerCaseEvent]) {
tip(
`Event "${lowerCaseEvent}" is emitted in component ` +
`${formatComponentName(vm)} but the handler is registered for "${event}". ` +
`Note that HTML attributes are case-insensitive and you cannot use ` +
`v-on to listen to camelCase events when using in-DOM templates. ` +
`You should probably use "${hyphenate(event)}" instead of "${event}".`
)
}
}
let cbs = vm._events[event]
if (cbs) {
cbs = cbs.length > 1 ? toArray(cbs) : cbs
const args = toArray(arguments, 1)
const info = `event handler for "${event}"`
for (let i = 0, l = cbs.length; i < l; i++) {
invokeWithErrorHandling(cbs[i], vm, args, vm, info)
}
}
return vm
}
}
initRender(vm)
此方法主要做了如下几件事情
- 处理插槽vm.$slot属性,将插槽中的dom对应的vnode赋值给vm.$slot
- 定义vm._c方法
- 定义vm.$createElement方法
_c()和$createElement方法主要调用了createElement()方法,createElement方法主要作用是供render函数执行,然后生成vnode
_c() 和 $createElement区别就是 通过内部模板编译生成的render函数会执行_c(), 用户自定义的render函数会执行$createElement方法。
==模板编译成render函数和createElement()生成vode,会在专门的章节中讲解==
此处仅仅知道initRender(vm)主要定义了vm的_c和$createElement方法即可
// 初始化render相关属性和方法
export function initRender (vm: Component) {
// 初始化_vnode属性
vm._vnode = null // the root of the child tree
// 初始化_staticTrees属性
vm._staticTrees = null // v-once cached trees
// 获取options
const options = vm.$options
// 获得父组件的占位vnode,其实也就是当前组件的vnode
// 最外层的vm的options._parentVnode是undefined
const parentVnode = vm.$vnode = options._parentVnode // the placeholder node in parent tree
// renderContext === 父组件实例
const renderContext = parentVnode && parentVnode.context
// options._renderChildren是写在组件插槽中的dom编译后生成的vnode
// vm.$slots保存了组件插槽中dom对应的vnode
vm.$slots = resolveSlots(options._renderChildren, renderContext)
// 作用域插槽属性初始化
vm.$scopedSlots = emptyObject
// bind the createElement fn to this instance
// so that we get proper render context inside it.
// args order: tag, data, children, normalizationType, alwaysNormalize
// internal version is used by render functions compiled from templates
// 定义_c方法
// 通过template编译生成的render函数会执行的_c方法
vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)
// normalization is always applied for the public version, used in
// user-written render functions.
// 用户自定义会执行的render方法
vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)
// $attrs & $listeners are exposed for easier HOC creation.
// they need to be reactive so that HOCs using them are always updated
const parentData = parentVnode && parentVnode.data
/* istanbul ignore else */
if (process.env.NODE_ENV !== 'production') {
defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, () => {
!isUpdatingChildComponent && warn(`$attrs is readonly.`, vm)
}, true)
defineReactive(vm, '$listeners', options._parentListeners || emptyObject, () => {
!isUpdatingChildComponent && warn(`$listeners is readonly.`, vm)
}, true)
} else {
defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, null, true)
defineReactive(vm, '$listeners', options._parentListeners || emptyObject, null, true)
}
}