介绍:
本篇文章我们将粗略的去了解,在 Vue 实例创建的过程中,分别执行了什么操作,为了照顾功底不深的小伙伴我们省去了部分方法实现的详细源码,有兴趣的可以自己去下载源码文档查看,或者关注我的后续文章
Vue
的结构和其执行顺序流程,让我们对 Vue 的使用更加行云流水new Vue()
的样式来调用,然后调用了该原型下的 _init()
方法,并且将我们传入的一系列数据传入,像 data, methods, computed, watch, 还有生命周期钩子等等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')
}
this._init(options)
}
initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)
export default Vue
new Vue()
执行前,先执行了以下这一系列函数,将该方法传入,我们分别看看他们干了什么 initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
ifecycleMixin(Vue)
renderMixin(Vue)
为Vue函数原型上添加了在构造器中执行的_init
函数
Vue.prototype._init = function (options) {
// 其中的代码我们稍后在研究
}
为Vue 添加了一系列实例方法和参数
涉及到了 观察者Watcher,将在下一篇文章中专门讲解
new Vue
参数中传入的 datadata
中的新添加数据不支持响应式的不足,由于响应式数据只发生在初始化 data 数据中export function stateMixin (Vue) {
const dataDef = {}
dataDef.get = function () { return this._data }
const propsDef = {}
propsDef.get = function () { return this._props }
Object.defineProperty(Vue.prototype, '$data', dataDef)
Object.defineProperty(Vue.prototype, '$props', propsDef)
Vue.prototype.$set = set
Vue.prototype.$delete = del
Vue.prototype.$watch = function (expOrFn,cb,options){
}
}
$emit
一同使用,相当与发布订阅模式$on
上定义的事件$on
上的事件 Vue.prototype.$on = function (event, fn): Component {
}
Vue.prototype.$once = function (event, fn){
}
Vue.prototype.$off = function (event, fn) {
}
Vue.prototype.$emit = function (event) {
}
这个阶段只干了三件事情,为Vue原型上增加两个生命周期方法和一个没有暴露的方法
其中涉及到了callHook钩子函数的执行,我们在下文中讲解
Vue
实例上的所有 watcher 更新beforeDestroy
钩子函数,解绑所有 Vue 实例中的连接,最后调用destroyed
钩子函数。node
,通过 diff 算法,更新新的dom 节点export function lifecycleMixin (Vue) {
Vue.prototype.$forceUpdate = function () {
const vm = this
if (vm._watcher) {
vm._watcher.update()
}
}
Vue.prototype.$destroy = function () {
const vm = this
callHook(vm, 'beforeDestroy')
// 删除所有 Vue 实例相关绑定
callHook(vm, 'destroyed')
}
Vue.prototype._update = function (vnode, hydrating) {
}
}
callback
队列中,在dom
更新之后自动执行function renderMixin (Vue: Class<Component>) {
// install runtime convenience helpers
installRenderHelpers(Vue.prototype)
Vue.prototype.$nextTick = function (fn: Function) {
return nextTick(fn, this)
}
Vue.prototype._render = function (): VNode {
}
}
在执行 Vue 构造方法之前,我们先初始化了一系列Vue 原型上的方法和数据,现在我们进入下一步来了解初始化的过程。
if (options && options._isComponent) {
initInternalComponent(vm, options)
} else {
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
}
beforeCreate
钩子执行前,data 中的数据还没有初始化为响应式,也没有添加到 vue 实例中,因此通过 this 访问不到 initLifecycle(vm) // 初始化生命周期
initEvents(vm) // 初始化事件
initRender(vm) // 初始化渲染树
callHook(vm, 'beforeCreate') // 执行钩子函数
initInjections(vm) // 初始化 inject,用于子组件接收父组件的传参
initState(vm) // 初始化 data 响应式,method,computed 等等
initProvide(vm) // 负责给子组件提供参数
callHook(vm, 'created') // 执行 created 钩子函数
在实例上定义了新的参数
ref
属性的 dom 节点 const options = vm.$options
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
初始化了一个 _event对象,用来存放通过 $on
调用的事件
vm._events = Object.create(null)
const listeners = vm.$options._parentListeners
if (listeners) {
updateComponentListeners(vm, listeners)
}
初始化渲染函数
default
prop
,除了class和style_c
,$createElement
函数来创建一个虚拟 node
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
const renderContext = parentVnode && parentVnode.context
vm.$slots = resolveSlots(options._renderChildren, renderContext)
vm.$scopedSlots = emptyObject
vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)
vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)
const parentData = parentVnode && parentVnode.data
defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, null, true)
defineReactive(vm, '$listeners', options._parentListeners || emptyObject, null, true)
}
我们来看下钩子函数内部的实现
invokeWithErrorHandling
函数 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)
}
invokeWithErrorHandling
里边发生了什么function invokeWithErrorHandling (handler,context,args,vm,info)
{
args ? handler.apply(context, args) : handler.call(context)
}
injec
t 的值inject/provide
提供的值为非响应式的 if (result) {
const result = resolveInject(vm.$options.inject, vm)
toggleObserving(false)
Object.keys(result).forEach(key => {
defineReactive(vm, key, result[key])
})
toggleObserving(true)
}
这个函数很简单,就是对传入的参数,方法,数据,computed,watch进行初始化,让我们看看他们内部是怎么一回事
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)
}
_props
下边,如果该实例不是根实例,则该参数不是响应式的 const propsData = vm.$options.propsData || {}
const props = vm._props = {}
const keys = vm.$options._propKeys = []
const isRoot = !vm.$parent
// root instance props should be converted
if (!isRoot) {
toggleObserving(false)
}
for (const key in propsOptions) {
keys.push(key)
const value = validateProp(key, propsOptions, propsData, vm)
defineReactive(props, key, value)
if (!(key in vm)) {
proxy(vm, `_props`, key)
}
}
toggleObserving(true)
methods
的方法的执行上下文绑定问 vm
,也就是this
指向method
function initMethods (vm: Component, methods: Object) {
const props = vm.$options.props
for (const key in methods) {
vm[key] = typeof methods[key] !== 'function' ? noop : bind(methods[key], vm)
}
}
Data
中的数据变为响应式的,并且判断该名字是否与methods
和props
中的重复,重复将报错function initData (vm: Component) {
let data = vm.$options.data
data = vm._data = typeof data === 'function'
? getData(data, vm)
//判断名字是否重复
observe(data, true /* asRootData */)
}
computed
中的键值生成一个观察者,并且将每一个computed
中的函数作为getter
和setter
绑定在vm
实例对应的键值上,获取该值时,将自身watcher
放置到Dep.targer
的位置,然后触发对应依赖值的getter
,将该watch放入自身依赖中,就形成了一个完整的观察者模式
,此处不做解释,我们将在之后文章中专门去讲观察者模式function initComputed (vm: Component, computed: Object) {
const watchers = vm._computedWatchers = Object.create(null)
// computed properties are just getters during SSR
const isSSR = isServerRendering()
for (const key in computed) {
const userDef = computed[key]
const getter = typeof userDef === 'function' ? userDef : userDef.get
if (!isSSR) {
// create internal watcher for the computed property.
watchers[key] = new Watcher(
vm,
getter || noop,
noop,
computedWatcherOptions
)
}
if (!(key in vm)) {
defineComputed(vm, key, userDef)
}
}
}
观察者
,并添加到对应的依赖上
createWatcher
中,又继续调用了 vm.$watch
,而在vm.$watch中有immediate
的判断,是否立即调用 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)
}
}
这个方法中将我们传入的provide放置在实例的_provided
中,方便 inject
向上查找该值
function initProvide (vm: Component) {
const provide = vm.$options.provide
if (provide) {
vm._provided = typeof provide === 'function'
? provide.call(vm)
: provide
}
}
在上边我们讲过了 callHook
的运行机制,这里我们就不多说,调用了 created
钩子函数
el
挂载到 dom 树中我们将在下一篇文章中详细讲述
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
至此,初始化阶段就全部结束了,下一步,让我们来学习挂载阶段。