new Vue(options) 发生了什么?
new Vue(options)调用initMixin()函数中的_init函数进行一系列的初始化过程,调用的相关函数如下图:
Vue 的构造函数在 /src/core/instance/index.js 文件中,下面来对相关函数进行解读
// Vue 的构造函数
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')
}
// 调用 Vue.prototype._init 方法,该方法在 initMixin 中定义
this._init(options)
}
// 定义 Vue.prototype._init 方法
initMixin(Vue)
export default Vue
initMixin
定义了 Vue.prototype._init
方法,执行 new Vue() 时,会调用 _init 方法来实现一系列初始化操作,包括整个生命周期的流程、响应式系统流程的启动等
export function initMixin (Vue: Class<Component>) {
// 负责 Vue 的初始化过程
Vue.prototype._init = function (options?: Object) {
// vue里把vue实例都叫做vm
const vm: Component = this
// 每个 vue 实例都有一个 _uid,并且是依次递增的
vm._uid = uid++
// 避免自身被观察的标志
vm._isVue = true
// 合并选项
if (options && options._isComponent) {
// 每个子组件初始化时走这里,这里只做了一些性能优化
// 将组件配置对象上的一些深层次属性放到 vm.$options 选项中,以提高代码的执行效率
initInternalComponent(vm, options)
} else {
// 根组件走这里:选项合并,将全局配置选项合并到根组件的局部配置上 // 组件选项合并,其实发生在三个地方:
// 1. Vue.component(CompName, Comp) 做了选项合并,合并的 vue 内置的全局组件
// 和用户自己注册的全局组件,最终都会放到全局的 components 选项中
// 2. { components: { xxx } } 全部注册,执行编译器生成的 render 函数时做了合并 // 会合并全局配置项到组件局部配置项上
// 3. 这里的根组件的情况了
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
}
/* istanbul ignore else */
if (process.env.NODE_ENV !== 'production') {
initProxy(vm)
} else {
vm._renderProxy = vm
}
// expose real self
vm._self = vm
// 重点,整个初始化最重要的部分,也是核心
// 初始化vm实例中和生命周期相关的属性,设置了vm的 $parent,$root,$children,
// $refs,_watcher,__isMounted,_isDestroyed,_isbeing,Destroyed的默认值
initLifecycle(vm)
// 事件监听初始化
// 存储父组件绑定当前子组件的事件,保存到vm._events中
initEvents(vm)
// 初始化插槽,获取this.$slots,
// 定义 this._c, 即createElement方法,也就是平时使用的 h 函数
initRender(vm)
// 从vm.$options中拿到beforeCreate的钩子方法数组,
// 传入vm对象作为上下文(this),循环执行
callHook(vm, 'beforeCreate')
// 初始化 inject 选项,得到 result[key] = val 形式的配置对象,并作响应式处理
initInjections(vm)
// resolve injections before data/props
// 响应式原理的核心,对prop,method,data,computed,watch的初始化
initState(vm)
// 设置 vm._provided值,以便子组件逐级查找
// inject与provide配合实现了属性的多级传递
initProvide(vm)
// resolve provide after data/props
// 调用 created 声明周期钩子函数
callHook(vm, 'created')
// 如果存在 el 选项,自动执行 $mount
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}
}
initLifecycle
初始化了 $parent、$root、children、$refs、_watcher、_isMounted、_isDestroyed、_isBeingDestroyed 等函数
,该方法只是在Vue的私有属性上添加了默认值
export function initLifecycle (vm: Component) {
const options = vm.$options
// locate first non-abstract parent
let parent = options.parent
if (parent && !options.abstract) {
while (parent.$options.abstract && parent.$parent) {
parent = parent.$parent
}
parent.$children.push(vm)
}
vm.$parent = parent
vm.$root = parent ? parent.$root : vm
vm.$children = []
vm.$refs = {}
vm._watcher = null
vm._inactive = null
vm._directInactive = false
vm._isMounted = false
vm._isDestroyed = false
vm._isBeingDestroyed = false
}
initEvents
初始化一个存放事件的空对象
,只存放挂载在该组件上的事件,_hasHookEvent属性表示父组件是否将钩子函数绑定到该组件上,如果父组件绑定事件到该组件上则调用updateComponentListeners方法,最终调用updateListeners更新Listener
export function initEvents (vm: Component) {
vm._events = Object.create(null)
vm._hasHookEvent = false
// init parent attached events
const listeners = vm.$options._parentListeners
if (listeners) {
updateComponentListeners(vm, listeners)
}
}
initRender
初始化渲染
- 将组件的插槽编译成虚拟节点 DOM 树, 以列表的形式挂载到 vm 实例,初始化作用域插槽为空对象
- 将模板的编译函数(把模板编译成虚拟 DOM 树)挂载到 vm 的 _c 和 $createElement 属性
- 最后把父组件传递过来的
$attrs
和$listeners
定义成响应式的
export function initRender (vm: Component) {
vm._vnode = null // the root of the child tree
vm._staticTrees = null // v-once cached trees
const options = vm.$options
const parentVnode = vm.$vnode = options._parentVnode // the placeholder node in parent tree
const renderContext = parentVnode && parentVnode.context
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
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.
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)
}
}
callHook
完成渲染初始化,依次调用 beforeCreate、created 钩子函数
用户使用的 beforeCreate 、 created 等钩子在 Vue 中是以数组的形式保存的,可以看成是一个任务队列。 即每个生命周期钩子函数都是 beforeCreate:[fn1, fn2, fn3, ... , fnEnd] 这种结构, 当调用 callHook(vm, 'beforeCreate') 时, 以当前组件的 vm 为 this 上下文依次执行生命周期钩子函数中的每一个函数
export function callHook (vm: Component, hook: string) {
// #7573 disable dep collection when invoking lifecycle hooks
pushTarget()
const handlers = vm.$options[hook]
const info = `${hook} hook`
if (handlers) {
for (let i = 0, j = handlers.length; i < j; i++) {
invokeWithErrorHandling(handlers[i], vm, null, vm, info)
}
}
if (vm._hasHookEvent) {
vm.$emit('hook:' + hook)
}
popTarget()
}
pushTarget
pushTarget()函数的作用是往 targetStack 数组尾部压入(保存) Watcher 对象,并把对象赋值给 Dep.target。
popTarget
popTarget()函数的作用是把 targetStack 数组尾部最后一个 Watcher 对象弹出(删除),然后把数组最后一个对象赋值给 Dep.target
initProvide
将 provide 的对象设置到 Vue 实例的 _provided 属性上,resolveInject 方法用于解析 inject 的数据,return 的数据是对象类型
initInjections
inject 值的逐级查找和借助 defineReactive() 方法将查找到的 inject 值定义到当前的组件实例上
export function initInjections (vm: Component) {
// 从配置项上解析 inject 选项,最后得到 result[key] = val 的结果
const result = resolveInject(vm.$options.inject, vm)
if (result) {
toggleObserving(false)
Object.keys(result).forEach(key => {
/* istanbul ignore else */
if (process.env.NODE_ENV !== 'production') {
defineReactive(vm, key, result[key], () => {
warn(
`Avoid mutating an injected value directly since the changes will be ` +
`overwritten whenever the provided component re-renders. ` +
`injection being mutated: "${key}"`,
vm
)
})
} else {
// 对解析结果做响应式处理,将每个 key 代理到 vue 实例上
// 我们才能使用 this.key 这种方式
defineReactive(vm, key, result[key])
}
})
toggleObserving(true)
}
}
export function initProvide (vm: Component) {
const provide = vm.$options.provide
if (provide) {
vm._provided = typeof provide === 'function'
? provide.call(vm)
: provide
}
}
initState
对prop,method,data,computed,watch初始化
,将prop,method,data,computed,watch等属性代理到vm实例上
export 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 /* asRootData */)
}
if (opts.computed) initComputed(vm, opts.computed)
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch)
}
}
initProps
为 props 对象的每个属性设置响应式
(for...in 遍历props中的属性,调用defineReactive(props, key, value)方法实现响应式),并将其代理到 vm 实例上
initMethods
将 methods[key] 放到 vm 实例上,得到 vm[key] = methods[key]
initData
代理 data 对象上的属性到 vm 实例,为 data 对象的上数据设置响应式
initComputed
为 computed[key] 创建 watcher 实例,默认是懒执行,代理 computed[key] 到 vm 实例
function initComputed (vm: Component, computed: Object) {
// $flow-disable-line
const watchers = vm._computedWatchers = Object.create(null)
// computed properties are just getters during SSR
const isSSR = isServerRendering()
// 循环遍历,为每个 computed 配发 watcher
for (const key in computed) {
const userDef = computed[key]
const getter = typeof userDef === 'function' ? userDef : userDef.get
if (process.env.NODE_ENV !== 'production' && getter == null) {
warn(
`Getter is missing for computed property "${key}".`,
vm
)
}
if (!isSSR) {
// 每个 computed 都创建一个 watcher
// watcher 用来存储计算值,判断是否需要重新计算
watchers[key] = new Watcher(
vm,
getter || noop,
noop,
{ lazy: true }
)
}
// 判断是否有重名的属性
if (!(key in vm)) {
defineComputed(vm, key, userDef)
}
}
}
initWatch
遍历 watch 对象,调用 createWatcher 函数
function initWatch (vm: Component, watch: Object) {
for (const key in watch) {
const handler = watch[key]
if (Array.isArray(handler)) {
for (let i = 0; i < handler.length; i++) {
createWatcher(vm, key, handler[i])
}
} else {
createWatcher(vm, key, handler)
}
}
}
observe
响应式处理的真正入口,为对象创建观察者实例,如果对象已经被观察过,则返回已有的观察者实例,否则创建新的观察者实例,底层调用defineReactive函数(Object.defineProperty)实现对数据的监听。